Add server public key check before purchases

This commit is contained in:
Jonas Lochmann 2020-06-08 02:00:00 +02:00
parent 5a17045551
commit 13c97bc41e
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
7 changed files with 57 additions and 34 deletions

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 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
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
}
}
}

View file

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

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 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")
}
}

View file

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

View file

@ -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,7 +66,10 @@ class PurchaseModel(application: Application): AndroidViewModel(application) {
else
CanDoPurchaseStatus.NoForUnknownReason
if (canDoPurchase == CanDoPurchaseStatus.Yes) {
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 {
@ -83,6 +87,7 @@ class PurchaseModel(application: Application): AndroidViewModel(application) {
)
}
}
}
} else if (canDoPurchase == CanDoPurchaseStatus.NotDueToOldPurchase) {
statusInternal.value = PurchaseFragmentExistingPaymentError
} else {
@ -110,3 +115,4 @@ object PurchaseFragmentErrorBillingNotSupportedByAppVariant: PurchaseFragmentUnr
object PurchaseFragmentNetworkError: PurchaseFragmentRecoverableError()
object PurchaseFragmentExistingPaymentError: PurchaseFragmentUnrecoverableError()
object PurchaseFragmentServerRejectedError: PurchaseFragmentUnrecoverableError()
object PurchaseFragmentServerHasDifferentPublicKey: PurchaseFragmentUnrecoverableError()

View file

@ -41,6 +41,7 @@
Sie haben bereits bezahlt. Sie können die Vollversion erst kurz vor dem Ablaufen (oder danach) verlängern
</string>
<string name="purchase_error_server_rejected">Der Server akzeptiert momentan keine Bezahlungen</string>
<string name="purchase_error_server_different_key">Der Server unterstützt Bezahlungen, aber nicht über diesen App-Build</string>
<string name="purchase_done_title">Bezahlung abgeschlossen</string>
<string name="purchase_done_text">Betätigen Sie die Zurück-Taste, um zur App zurückzukehren</string>

View file

@ -41,6 +41,7 @@
You can only extend the premium version if it expired or shortly before that.
</string>
<string name="purchase_error_server_rejected">The server does not accept payments currently</string>
<string name="purchase_error_server_different_key">The server does support payments, but not using this app build</string>
<string name="purchase_done_title">Purchase succeeded</string>
<string name="purchase_done_text">Press back to go back to the App</string>