From 7c6c418a79cca29882c2099207caa13e1bdd7c42 Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Mon, 26 Dec 2022 12:31:03 +0100 Subject: [PATCH] Rewrite 'api' to Kotlin --- .../main/java/com/github/gotify/api/Api.java | 21 ---- .../main/java/com/github/gotify/api/Api.kt | 21 ++++ .../com/github/gotify/api/ApiException.java | 44 -------- .../com/github/gotify/api/ApiException.kt | 34 ++++++ .../java/com/github/gotify/api/Callback.java | 83 -------------- .../java/com/github/gotify/api/Callback.kt | 91 +++++++++++++++ .../java/com/github/gotify/api/CertUtils.java | 106 ------------------ .../java/com/github/gotify/api/CertUtils.kt | 92 +++++++++++++++ .../com/github/gotify/api/ClientFactory.java | 50 --------- .../com/github/gotify/api/ClientFactory.kt | 66 +++++++++++ 10 files changed, 304 insertions(+), 304 deletions(-) delete mode 100644 app/src/main/java/com/github/gotify/api/Api.java create mode 100644 app/src/main/java/com/github/gotify/api/Api.kt delete mode 100644 app/src/main/java/com/github/gotify/api/ApiException.java create mode 100644 app/src/main/java/com/github/gotify/api/ApiException.kt delete mode 100644 app/src/main/java/com/github/gotify/api/Callback.java create mode 100644 app/src/main/java/com/github/gotify/api/Callback.kt delete mode 100644 app/src/main/java/com/github/gotify/api/CertUtils.java create mode 100644 app/src/main/java/com/github/gotify/api/CertUtils.kt delete mode 100644 app/src/main/java/com/github/gotify/api/ClientFactory.java create mode 100644 app/src/main/java/com/github/gotify/api/ClientFactory.kt diff --git a/app/src/main/java/com/github/gotify/api/Api.java b/app/src/main/java/com/github/gotify/api/Api.java deleted file mode 100644 index 0bac2e3..0000000 --- a/app/src/main/java/com/github/gotify/api/Api.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.gotify.api; - -import java.io.IOException; -import retrofit2.Call; -import retrofit2.Response; - -public class Api { - public static T execute(Call call) throws ApiException { - try { - Response response = call.execute(); - - if (response.isSuccessful()) { - return response.body(); - } else { - throw new ApiException(response); - } - } catch (IOException e) { - throw new ApiException(e); - } - } -} diff --git a/app/src/main/java/com/github/gotify/api/Api.kt b/app/src/main/java/com/github/gotify/api/Api.kt new file mode 100644 index 0000000..af05190 --- /dev/null +++ b/app/src/main/java/com/github/gotify/api/Api.kt @@ -0,0 +1,21 @@ +package com.github.gotify.api + +import java.io.IOException +import retrofit2.Call + +object Api { + @Throws(ApiException::class) + fun execute(call: Call): T? { + try { + val response = call.execute() + + if (response.isSuccessful) { + return response.body() + } else { + throw ApiException(response) + } + } catch (e: IOException) { + throw ApiException(e) + } + } +} diff --git a/app/src/main/java/com/github/gotify/api/ApiException.java b/app/src/main/java/com/github/gotify/api/ApiException.java deleted file mode 100644 index bf73da1..0000000 --- a/app/src/main/java/com/github/gotify/api/ApiException.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.gotify.api; - -import java.io.IOException; -import java.util.Locale; -import retrofit2.Response; - -public final class ApiException extends Exception { - - private String body; - private int code; - - ApiException(Response response) { - super("Api Error", null); - try { - this.body = response.errorBody() != null ? response.errorBody().string() : ""; - } catch (IOException e) { - this.body = "Error while getting error body :("; - } - this.code = response.code(); - } - - ApiException(Throwable cause) { - super("Request failed.", cause); - this.body = ""; - this.code = 0; - } - - public String body() { - return body; - } - - public int code() { - return code; - } - - @Override - public String toString() { - return String.format( - Locale.ENGLISH, - "Code(%d) Response: %s", - code(), - body().substring(0, Math.min(body().length(), 200))); - } -} diff --git a/app/src/main/java/com/github/gotify/api/ApiException.kt b/app/src/main/java/com/github/gotify/api/ApiException.kt new file mode 100644 index 0000000..1ff726c --- /dev/null +++ b/app/src/main/java/com/github/gotify/api/ApiException.kt @@ -0,0 +1,34 @@ +package com.github.gotify.api + +import java.io.IOException +import java.util.* +import retrofit2.Response + +internal class ApiException : Exception { + var body: String = "" + private set + var code: Int + private set + + constructor(response: Response<*>) : super("Api Error", null) { + body = try { + if (response.errorBody() != null) response.errorBody()!!.string() else "" + } catch (e: IOException) { + "Error while getting error body :(" + } + code = response.code() + } + + constructor(cause: Throwable?) : super("Request failed.", cause) { + code = 0 + } + + override fun toString(): String { + return String.format( + Locale.ENGLISH, + "Code(%d) Response: %s", + code, + body.substring(0, body.length.coerceAtMost(200)) + ) + } +} diff --git a/app/src/main/java/com/github/gotify/api/Callback.java b/app/src/main/java/com/github/gotify/api/Callback.java deleted file mode 100644 index 7b26292..0000000 --- a/app/src/main/java/com/github/gotify/api/Callback.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.github.gotify.api; - -import android.app.Activity; -import com.github.gotify.log.Log; -import retrofit2.Call; -import retrofit2.Response; - -public class Callback { - private final SuccessCallback onSuccess; - private final ErrorCallback onError; - - private Callback(SuccessCallback onSuccess, ErrorCallback onError) { - this.onSuccess = onSuccess; - this.onError = onError; - } - - public static retrofit2.Callback callInUI( - Activity context, SuccessCallback onSuccess, ErrorCallback onError) { - return call( - (data) -> context.runOnUiThread(() -> onSuccess.onSuccess(data)), - (e) -> context.runOnUiThread(() -> onError.onError(e))); - } - - public static retrofit2.Callback call() { - return call((e) -> {}, (e) -> {}); - } - - public static retrofit2.Callback call( - SuccessCallback onSuccess, ErrorCallback onError) { - return new RetrofitCallback<>(merge(of(onSuccess, onError), errorCallback())); - } - - private static Callback of(SuccessCallback onSuccess, ErrorCallback onError) { - return new Callback<>(onSuccess, onError); - } - - private static Callback errorCallback() { - return new Callback<>((ignored) -> {}, (error) -> Log.e("Error while api call", error)); - } - - private static Callback merge(Callback left, Callback right) { - return new Callback<>( - (data) -> { - left.onSuccess.onSuccess(data); - right.onSuccess.onSuccess(data); - }, - (error) -> { - left.onError.onError(error); - right.onError.onError(error); - }); - } - - public interface SuccessCallback { - void onSuccess(T data); - } - - public interface ErrorCallback { - void onError(ApiException t); - } - - private static final class RetrofitCallback implements retrofit2.Callback { - - private Callback callback; - - private RetrofitCallback(Callback callback) { - this.callback = callback; - } - - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - callback.onSuccess.onSuccess(response.body()); - } else { - callback.onError.onError(new ApiException(response)); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - callback.onError.onError(new ApiException(t)); - } - } -} diff --git a/app/src/main/java/com/github/gotify/api/Callback.kt b/app/src/main/java/com/github/gotify/api/Callback.kt new file mode 100644 index 0000000..9064a93 --- /dev/null +++ b/app/src/main/java/com/github/gotify/api/Callback.kt @@ -0,0 +1,91 @@ +package com.github.gotify.api + +import android.app.Activity +import com.github.gotify.log.Log +import retrofit2.Call +import retrofit2.Response + +internal class Callback private constructor( + private val onSuccess: SuccessCallback, + private val onError: ErrorCallback +) { + fun interface SuccessCallback { + fun onSuccess(data: T?) + } + + fun interface ErrorCallback { + fun onError(t: ApiException) + } + + private class RetrofitCallback(callback: Callback) : retrofit2.Callback { + private val callback: Callback + + init { + this.callback = callback + } + + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + callback.onSuccess.onSuccess(response.body()) + } else { + callback.onError.onError(ApiException(response)) + } + } + + override fun onFailure(call: Call, t: Throwable) { + callback.onError.onError(ApiException(t)) + } + } + + companion object { + fun callInUI( + context: Activity, + onSuccess: SuccessCallback, + onError: ErrorCallback + ): retrofit2.Callback { + return call( + { + context.runOnUiThread { + onSuccess.onSuccess(it) + } + }, + { + context.runOnUiThread { + onError.onError(it) + } + }) + } + + fun call(): retrofit2.Callback { + return call({},{}) + } + + fun call(onSuccess: SuccessCallback, onError: ErrorCallback): retrofit2.Callback { + return RetrofitCallback(merge(of(onSuccess, onError), errorCallback())) + } + + private fun of( + onSuccess: SuccessCallback, + onError: ErrorCallback + ): Callback { + return Callback(onSuccess, onError) + } + + private fun errorCallback(): Callback { + return Callback({}, { Log.e("Error while api call", it) }) + } + + private fun merge(left: Callback, right: Callback): Callback { + return Callback( + { + left.onSuccess.onSuccess(it) + right.onSuccess.onSuccess(it) + }, + { + left.onError.onError(it) + right.onError.onError(it) + } + ) + } + } +} diff --git a/app/src/main/java/com/github/gotify/api/CertUtils.java b/app/src/main/java/com/github/gotify/api/CertUtils.java deleted file mode 100644 index 0ca7010..0000000 --- a/app/src/main/java/com/github/gotify/api/CertUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.github.gotify.api; - -import android.annotation.SuppressLint; -import com.github.gotify.SSLSettings; -import com.github.gotify.Utils; -import com.github.gotify.log.Log; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Collection; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import okhttp3.OkHttpClient; - -public class CertUtils { - private static final X509TrustManager trustAll = - new X509TrustManager() { - @SuppressLint("TrustAllX509TrustManager") - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) {} - - @SuppressLint("TrustAllX509TrustManager") - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) {} - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[] {}; - } - }; - - public static Certificate parseCertificate(String cert) { - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); - - return certificateFactory.generateCertificate(Utils.stringToInputStream(cert)); - } catch (Exception e) { - throw new IllegalArgumentException("certificate is invalid"); - } - } - - public static void applySslSettings(OkHttpClient.Builder builder, SSLSettings settings) { - // Modified from ApiClient.applySslSettings in the client package. - - try { - if (!settings.validateSSL) { - SSLContext context = SSLContext.getInstance("TLS"); - context.init( - new KeyManager[] {}, new TrustManager[] {trustAll}, new SecureRandom()); - builder.sslSocketFactory(context.getSocketFactory(), trustAll); - builder.hostnameVerifier((a, b) -> true); - return; - } - - if (settings.cert != null) { - TrustManager[] trustManagers = certToTrustManager(settings.cert); - - if (trustManagers != null && trustManagers.length > 0) { - SSLContext context = SSLContext.getInstance("TLS"); - context.init(new KeyManager[] {}, trustManagers, new SecureRandom()); - builder.sslSocketFactory( - context.getSocketFactory(), (X509TrustManager) trustManagers[0]); - } - } - } catch (Exception e) { - // We shouldn't have issues since the cert is verified on login. - Log.e("Failed to apply SSL settings", e); - } - } - - private static TrustManager[] certToTrustManager(String cert) throws GeneralSecurityException { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - Collection certificates = - certificateFactory.generateCertificates(Utils.stringToInputStream(cert)); - if (certificates.isEmpty()) { - throw new IllegalArgumentException("expected non-empty set of trusted certificates"); - } - KeyStore caKeyStore = newEmptyKeyStore(); - int index = 0; - for (Certificate certificate : certificates) { - String certificateAlias = "ca" + Integer.toString(index++); - caKeyStore.setCertificateEntry(certificateAlias, certificate); - } - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(caKeyStore); - return trustManagerFactory.getTrustManagers(); - } - - private static KeyStore newEmptyKeyStore() throws GeneralSecurityException { - try { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, null); - return keyStore; - } catch (IOException e) { - throw new AssertionError(e); - } - } -} diff --git a/app/src/main/java/com/github/gotify/api/CertUtils.kt b/app/src/main/java/com/github/gotify/api/CertUtils.kt new file mode 100644 index 0000000..220a793 --- /dev/null +++ b/app/src/main/java/com/github/gotify/api/CertUtils.kt @@ -0,0 +1,92 @@ +package com.github.gotify.api + +import android.annotation.SuppressLint +import com.github.gotify.SSLSettings +import com.github.gotify.Utils +import com.github.gotify.log.Log +import java.io.IOException +import java.security.GeneralSecurityException +import java.security.KeyStore +import java.security.SecureRandom +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import javax.net.ssl.* +import okhttp3.OkHttpClient + +internal object CertUtils { + @SuppressLint("CustomX509TrustManager") + private val trustAll: X509TrustManager = object : X509TrustManager { + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(chain: Array, authType: String) {} + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(chain: Array, authType: String) {} + + override fun getAcceptedIssuers(): Array = arrayOf() + } + + fun parseCertificate(cert: String): Certificate { + try { + val certificateFactory = CertificateFactory.getInstance("X509") + return certificateFactory.generateCertificate(Utils.stringToInputStream(cert)) + } catch (e: Exception) { + throw IllegalArgumentException("certificate is invalid") + } + } + + fun applySslSettings(builder: OkHttpClient.Builder, settings: SSLSettings) { + // Modified from ApiClient.applySslSettings in the client package. + try { + if (!settings.validateSSL) { + val context = SSLContext.getInstance("TLS") + context.init(arrayOf(), arrayOf(trustAll), SecureRandom()) + builder.sslSocketFactory(context.socketFactory, trustAll) + builder.hostnameVerifier { _, _ -> true } + return + } + if (settings.cert != null) { + val trustManagers = certToTrustManager(settings.cert) + if (trustManagers.isNotEmpty()) { + val context = SSLContext.getInstance("TLS") + context.init(arrayOf(), trustManagers, SecureRandom()) + builder.sslSocketFactory( + context.socketFactory, + trustManagers[0] as X509TrustManager + ) + } + } + } catch (e: Exception) { + // We shouldn't have issues since the cert is verified on login. + Log.e("Failed to apply SSL settings", e) + } + } + + @Throws(GeneralSecurityException::class) + private fun certToTrustManager(cert: String): Array { + val certificateFactory = CertificateFactory.getInstance("X.509") + val certificates = certificateFactory.generateCertificates(Utils.stringToInputStream(cert)) + require(!certificates.isEmpty()) { "expected non-empty set of trusted certificates" } + + val caKeyStore = newEmptyKeyStore() + certificates.forEachIndexed { index, certificate -> + val certificateAlias = "ca$index" + caKeyStore.setCertificateEntry(certificateAlias, certificate) + } + val trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(caKeyStore) + return trustManagerFactory.trustManagers + } + + @Throws(GeneralSecurityException::class) + private fun newEmptyKeyStore(): KeyStore { + return try { + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) + keyStore.load(null, null) + keyStore + } catch (e: IOException) { + throw AssertionError(e) + } + } +} diff --git a/app/src/main/java/com/github/gotify/api/ClientFactory.java b/app/src/main/java/com/github/gotify/api/ClientFactory.java deleted file mode 100644 index 1e10e7c..0000000 --- a/app/src/main/java/com/github/gotify/api/ClientFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.github.gotify.api; - -import com.github.gotify.SSLSettings; -import com.github.gotify.Settings; -import com.github.gotify.client.ApiClient; -import com.github.gotify.client.api.UserApi; -import com.github.gotify.client.api.VersionApi; -import com.github.gotify.client.auth.ApiKeyAuth; -import com.github.gotify.client.auth.HttpBasicAuth; - -public class ClientFactory { - public static com.github.gotify.client.ApiClient unauthorized( - String baseUrl, SSLSettings sslSettings) { - return defaultClient(new String[0], baseUrl + "/", sslSettings); - } - - public static ApiClient basicAuth( - String baseUrl, SSLSettings sslSettings, String username, String password) { - ApiClient client = defaultClient(new String[] {"basicAuth"}, baseUrl + "/", sslSettings); - HttpBasicAuth auth = (HttpBasicAuth) client.getApiAuthorizations().get("basicAuth"); - auth.setUsername(username); - auth.setPassword(password); - return client; - } - - public static ApiClient clientToken(String baseUrl, SSLSettings sslSettings, String token) { - ApiClient client = - defaultClient(new String[] {"clientTokenHeader"}, baseUrl + "/", sslSettings); - ApiKeyAuth tokenAuth = (ApiKeyAuth) client.getApiAuthorizations().get("clientTokenHeader"); - tokenAuth.setApiKey(token); - return client; - } - - public static VersionApi versionApi(String baseUrl, SSLSettings sslSettings) { - return unauthorized(baseUrl, sslSettings).createService(VersionApi.class); - } - - public static UserApi userApiWithToken(Settings settings) { - return clientToken(settings.url(), settings.sslSettings(), settings.token()) - .createService(UserApi.class); - } - - private static ApiClient defaultClient( - String[] authentications, String baseUrl, SSLSettings sslSettings) { - ApiClient client = new ApiClient(authentications); - CertUtils.applySslSettings(client.getOkBuilder(), sslSettings); - client.getAdapterBuilder().baseUrl(baseUrl); - return client; - } -} diff --git a/app/src/main/java/com/github/gotify/api/ClientFactory.kt b/app/src/main/java/com/github/gotify/api/ClientFactory.kt new file mode 100644 index 0000000..571ca94 --- /dev/null +++ b/app/src/main/java/com/github/gotify/api/ClientFactory.kt @@ -0,0 +1,66 @@ +package com.github.gotify.api + +import com.github.gotify.SSLSettings +import com.github.gotify.Settings +import com.github.gotify.client.ApiClient +import com.github.gotify.client.api.UserApi +import com.github.gotify.client.api.VersionApi +import com.github.gotify.client.auth.ApiKeyAuth +import com.github.gotify.client.auth.HttpBasicAuth + +internal object ClientFactory { + private fun unauthorized(baseUrl: String, sslSettings: SSLSettings + ): ApiClient { + return defaultClient(arrayOfNulls(0), "$baseUrl/", sslSettings) + } + + fun basicAuth( + baseUrl: String, + sslSettings: SSLSettings, + username: String?, + password: String? + ): ApiClient { + val client = defaultClient( + arrayOf("basicAuth"), + "$baseUrl/", sslSettings + ) + val auth = client.apiAuthorizations["basicAuth"] as HttpBasicAuth? + auth!!.username = username + auth.password = password + return client + } + + fun clientToken( + baseUrl: String, + sslSettings: SSLSettings, + token: String? + ): ApiClient { + val client = defaultClient( + arrayOf("clientTokenHeader"), + "$baseUrl/", sslSettings + ) + val tokenAuth = client.apiAuthorizations["clientTokenHeader"] as ApiKeyAuth? + tokenAuth!!.apiKey = token + return client + } + + fun versionApi(baseUrl: String, sslSettings: SSLSettings): VersionApi? { + return unauthorized(baseUrl, sslSettings).createService(VersionApi::class.java) + } + + fun userApiWithToken(settings: Settings): UserApi? { + return clientToken(settings.url(), settings.sslSettings(), settings.token()) + .createService(UserApi::class.java) + } + + private fun defaultClient( + authentications: Array, + baseUrl: String, + sslSettings: SSLSettings + ): ApiClient { + val client = ApiClient(authentications) + CertUtils.applySslSettings(client.okBuilder, sslSettings) + client.adapterBuilder.baseUrl(baseUrl) + return client + } +}