From 13c97bc41e6311d28fa10630cda85b48bf509ddd Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 8 Jun 2020 02:00:00 +0200 Subject: [PATCH] Add server public key check before purchases --- .../sync/network/CanDoPurchaseStatus.kt | 38 +++++++++++++++---- .../android/sync/network/api/HttpServerApi.kt | 12 +----- .../ui/payment/ActivityPurchaseModel.kt | 4 +- .../android/ui/payment/PurchaseFragment.kt | 1 + .../android/ui/payment/PurchaseModel.kt | 34 ++++++++++------- .../main/res/values-de/strings-purchase.xml | 1 + app/src/main/res/values/strings-purchase.xml | 1 + 7 files changed, 57 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/io/timelimit/android/sync/network/CanDoPurchaseStatus.kt b/app/src/main/java/io/timelimit/android/sync/network/CanDoPurchaseStatus.kt index d3d1217..515f08c 100644 --- a/app/src/main/java/io/timelimit/android/sync/network/CanDoPurchaseStatus.kt +++ b/app/src/main/java/io/timelimit/android/sync/network/CanDoPurchaseStatus.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 2019 - 2020 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,17 +15,41 @@ */ package io.timelimit.android.sync.network -enum class CanDoPurchaseStatus { - Yes, NotDueToOldPurchase, NoForUnknownReason +import android.util.Base64 +import android.util.JsonReader + +sealed class CanDoPurchaseStatus { + class Yes(val publicKey: ByteArray?): CanDoPurchaseStatus() + object NoForUnknownReason: CanDoPurchaseStatus() + object NotDueToOldPurchase: CanDoPurchaseStatus() } object CanDoPurchaseParser { + private const val CAN_DO_PURCHASE = "canDoPurchase" + private const val GPLAY_PUBLIC_KEY = "googlePlayPublicKey" private const val YES = "yes" private const val NO_DUE_TO_OLD_PURCHASE = "no due to old purchase" - fun parse(value: String) = when(value) { - YES -> CanDoPurchaseStatus.Yes - NO_DUE_TO_OLD_PURCHASE -> CanDoPurchaseStatus.NotDueToOldPurchase - else -> CanDoPurchaseStatus.NoForUnknownReason + fun parse(reader: JsonReader): CanDoPurchaseStatus { + reader.beginObject() + + var canDoPurchaseStatus: String? = null + var publicKey: ByteArray? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + CAN_DO_PURCHASE -> canDoPurchaseStatus = reader.nextString() + GPLAY_PUBLIC_KEY -> publicKey = Base64.decode(reader.nextString(), 0) + else -> reader.skipValue() + } + } + + reader.endObject() + + return when (canDoPurchaseStatus!!) { + YES -> CanDoPurchaseStatus.Yes(publicKey) + NO_DUE_TO_OLD_PURCHASE -> CanDoPurchaseStatus.NotDueToOldPurchase + else -> CanDoPurchaseStatus.NoForUnknownReason + } } } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt b/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt index d606f0b..8605f4a 100644 --- a/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt +++ b/app/src/main/java/io/timelimit/android/sync/network/api/HttpServerApi.kt @@ -476,18 +476,8 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi { return Threads.network.executeAndWait { val reader = JsonReader(response.body()!!.charStream()) - var status: CanDoPurchaseStatus? = null - reader.beginObject() - while (reader.hasNext()) { - when (reader.nextName()) { - "canDoPurchase" -> status = CanDoPurchaseParser.parse(reader.nextString()) - else -> reader.skipValue() - } - } - reader.endObject() - - status!! + CanDoPurchaseParser.parse(reader) } } } diff --git a/app/src/main/java/io/timelimit/android/ui/payment/ActivityPurchaseModel.kt b/app/src/main/java/io/timelimit/android/ui/payment/ActivityPurchaseModel.kt index 120d6b0..0786e63 100644 --- a/app/src/main/java/io/timelimit/android/ui/payment/ActivityPurchaseModel.kt +++ b/app/src/main/java/io/timelimit/android/ui/payment/ActivityPurchaseModel.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 Jonas Lochmann + * TimeLimit Copyright 2019 - 2020 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -194,7 +194,7 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat return@runAsync } - if (server.api.canDoPurchase(server.deviceAuthToken) != CanDoPurchaseStatus.Yes) { + if (!(server.api.canDoPurchase(server.deviceAuthToken) is CanDoPurchaseStatus.Yes)) { throw IOException("can not do purchase right now") } } diff --git a/app/src/main/java/io/timelimit/android/ui/payment/PurchaseFragment.kt b/app/src/main/java/io/timelimit/android/ui/payment/PurchaseFragment.kt index 606510b..79192bc 100644 --- a/app/src/main/java/io/timelimit/android/ui/payment/PurchaseFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/payment/PurchaseFragment.kt @@ -90,6 +90,7 @@ class PurchaseFragment : Fragment(), FragmentWithCustomTitle { PurchaseFragmentNetworkError -> getString(R.string.error_network) PurchaseFragmentExistingPaymentError -> getString(R.string.purchase_error_existing_payment) PurchaseFragmentServerRejectedError -> getString(R.string.purchase_error_server_rejected) + PurchaseFragmentServerHasDifferentPublicKey -> getString(R.string.purchase_error_server_different_key) } binding.showRetryButton = when (fragmentStatus) { diff --git a/app/src/main/java/io/timelimit/android/ui/payment/PurchaseModel.kt b/app/src/main/java/io/timelimit/android/ui/payment/PurchaseModel.kt index f14e90f..8ded58f 100644 --- a/app/src/main/java/io/timelimit/android/ui/payment/PurchaseModel.kt +++ b/app/src/main/java/io/timelimit/android/ui/payment/PurchaseModel.kt @@ -16,6 +16,7 @@ package io.timelimit.android.ui.payment import android.app.Application +import android.util.Base64 import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import io.timelimit.android.BuildConfig @@ -65,22 +66,26 @@ class PurchaseModel(application: Application): AndroidViewModel(application) { else CanDoPurchaseStatus.NoForUnknownReason - if (canDoPurchase == CanDoPurchaseStatus.Yes) { - val checkout = Checkout.forApplication(application.billing) + if (canDoPurchase is CanDoPurchaseStatus.Yes) { + if (canDoPurchase.publicKey?.equals(Base64.decode(BuildConfig.googlePlayKey, 0)) == false) { + statusInternal.value = PurchaseFragmentServerHasDifferentPublicKey + } else { + val checkout = Checkout.forApplication(application.billing) - checkout.startAsync().use { - if (!it.billingSupported) { - statusInternal.value = PurchaseFragmentErrorBillingNotSupportedByDevice - } else { - val skus = it.requests.getSkusAsync( - ProductTypes.IN_APP, - PurchaseIds.BUY_SKUS - ) + checkout.startAsync().use { + if (!it.billingSupported) { + statusInternal.value = PurchaseFragmentErrorBillingNotSupportedByDevice + } else { + val skus = it.requests.getSkusAsync( + ProductTypes.IN_APP, + PurchaseIds.BUY_SKUS + ) - statusInternal.value = PurchaseFragmentReady( - monthPrice = skus.getSku(PurchaseIds.SKU_MONTH)?.price.toString(), - yearPrice = skus.getSku(PurchaseIds.SKU_YEAR)?.price.toString() - ) + statusInternal.value = PurchaseFragmentReady( + monthPrice = skus.getSku(PurchaseIds.SKU_MONTH)?.price.toString(), + yearPrice = skus.getSku(PurchaseIds.SKU_YEAR)?.price.toString() + ) + } } } } else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) { @@ -110,3 +115,4 @@ object PurchaseFragmentErrorBillingNotSupportedByAppVariant: PurchaseFragmentUnr object PurchaseFragmentNetworkError: PurchaseFragmentRecoverableError() object PurchaseFragmentExistingPaymentError: PurchaseFragmentUnrecoverableError() object PurchaseFragmentServerRejectedError: PurchaseFragmentUnrecoverableError() +object PurchaseFragmentServerHasDifferentPublicKey: PurchaseFragmentUnrecoverableError() \ No newline at end of file diff --git a/app/src/main/res/values-de/strings-purchase.xml b/app/src/main/res/values-de/strings-purchase.xml index 20528df..eebb1a8 100644 --- a/app/src/main/res/values-de/strings-purchase.xml +++ b/app/src/main/res/values-de/strings-purchase.xml @@ -41,6 +41,7 @@ Sie haben bereits bezahlt. Sie können die Vollversion erst kurz vor dem Ablaufen (oder danach) verlängern Der Server akzeptiert momentan keine Bezahlungen + Der Server unterstützt Bezahlungen, aber nicht über diesen App-Build Bezahlung abgeschlossen Betätigen Sie die Zurück-Taste, um zur App zurückzukehren diff --git a/app/src/main/res/values/strings-purchase.xml b/app/src/main/res/values/strings-purchase.xml index a9b2acd..a0d9d95 100644 --- a/app/src/main/res/values/strings-purchase.xml +++ b/app/src/main/res/values/strings-purchase.xml @@ -41,6 +41,7 @@ You can only extend the premium version if it expired or shortly before that. The server does not accept payments currently + The server does support payments, but not using this app build Purchase succeeded Press back to go back to the App