mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-06 05:14:50 +01:00
Implement blind donation permits system.
This commit is contained in:
+1
-1
@@ -174,7 +174,7 @@ object RecurringInAppPaymentRepository {
|
||||
InAppPaymentsRepository.getSubscriber(subscriberType)?.subscriberId ?: SubscriberId.generate()
|
||||
}
|
||||
|
||||
donationsService.putSubscription(subscriberId).resultOrThrow
|
||||
donationsService.createSubscriber(subscriberId).resultOrThrow
|
||||
|
||||
Log.d(TAG, "Successfully set SubscriberId exists on Signal service.", true)
|
||||
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.permits
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.donations.permits.DonationPermitError
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.signal.network.rest.RestStatusCodeError
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.whispersystems.signalservice.api.donations.DonationPermitProvider
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* App-side [DonationPermitProvider]: spends a permit and base64-encodes it for the `Donation-Permit` header,
|
||||
* translating an acquisition failure into a [RequestResult] the donations service can surface.
|
||||
*/
|
||||
object DonationPermits : DonationPermitProvider {
|
||||
|
||||
@WorkerThread
|
||||
override fun getDonationPermit(): RequestResult<String, RestStatusCodeError> {
|
||||
return AppDependencies.donationPermitsRepository
|
||||
.spendOrAcquirePermit()
|
||||
.fold(
|
||||
ifLeft = { it.toRequestResult() },
|
||||
ifRight = { RequestResult.Success(Base64.encodeWithPadding(it.serialize())) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun DonationPermitError.toRequestResult(): RequestResult<String, RestStatusCodeError> {
|
||||
return when (this) {
|
||||
is DonationPermitError.IssuerUnavailable -> {
|
||||
val statusCode = statusCode
|
||||
val cause = cause
|
||||
when {
|
||||
statusCode != null -> RequestResult.NonSuccess(RestStatusCodeError(statusCode, emptyMap(), null))
|
||||
cause is IOException -> RequestResult.RetryableNetworkError(cause)
|
||||
else -> RequestResult.ApplicationError(cause ?: IllegalStateException("Donation permit issuer unavailable"))
|
||||
}
|
||||
}
|
||||
DonationPermitError.VerificationFailed -> RequestResult.ApplicationError(IllegalStateException("Donation permit verification failed"))
|
||||
DonationPermitError.MalformedResponse -> RequestResult.ApplicationError(IllegalStateException("Malformed donation permit response"))
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.permits
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import org.signal.donations.permits.DonationPermitError
|
||||
import org.signal.donations.permits.DonationPermitIssuer
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
|
||||
/**
|
||||
* [DonationPermitIssuer] backed by [org.whispersystems.signalservice.api.donations.DonationsApi], translating a
|
||||
* non-success [RequestResult] into a [DonationPermitError].
|
||||
*/
|
||||
object NetworkDonationPermitIssuer : DonationPermitIssuer {
|
||||
|
||||
override fun issue(requestBytes: ByteArray): Either<DonationPermitError, ByteArray> {
|
||||
return when (val result = AppDependencies.donationsApi.createDonationPermits(requestBytes)) {
|
||||
is RequestResult.Success -> result.result.right()
|
||||
is RequestResult.NonSuccess -> DonationPermitError.IssuerUnavailable(statusCode = result.error.statusCode).left()
|
||||
is RequestResult.RetryableNetworkError -> DonationPermitError.IssuerUnavailable(cause = result.networkError).left()
|
||||
is RequestResult.ApplicationError -> DonationPermitError.IssuerUnavailable(cause = result.cause).left()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import org.signal.core.util.concurrent.LatestValueObservable
|
||||
import org.signal.core.util.contentproviders.BlobProvider
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.core.util.resettableLazy
|
||||
import org.signal.donations.permits.DonationPermitsRepository
|
||||
import org.signal.glide.SignalGlideDependencies
|
||||
import org.signal.libsignal.net.Network
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations
|
||||
@@ -415,6 +416,11 @@ object AppDependencies {
|
||||
val donationsApi: DonationsApi
|
||||
get() = networkModule.donationsApi
|
||||
|
||||
@JvmStatic
|
||||
val donationPermitsRepository: DonationPermitsRepository by lazy {
|
||||
provider.provideDonationPermitsRepository(signalServiceNetworkAccess.getConfiguration().zkGroupServerPublicParams)
|
||||
}
|
||||
|
||||
val keyTransparencyApi: KeyTransparencyApi
|
||||
get() = networkModule.keyTransparencyApi
|
||||
|
||||
@@ -488,6 +494,7 @@ object AppDependencies {
|
||||
fun provideExoPlayerPool(): ExoPlayerPool<ExoPlayer>
|
||||
fun provideAndroidCallAudioManager(): AudioManagerCompat
|
||||
fun provideDonationsService(donationsApi: DonationsApi): DonationsService
|
||||
fun provideDonationPermitsRepository(zkGroupServerPublicParams: ByteArray): DonationPermitsRepository
|
||||
fun provideProfileService(profileOperations: ClientZkProfileOperations, authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): ProfileService
|
||||
fun provideDeadlockDetector(): DeadlockDetector
|
||||
fun provideClientZkReceiptOperations(signalServiceConfiguration: SignalServiceConfiguration): ClientZkReceiptOperations
|
||||
@@ -507,6 +514,7 @@ object AppDependencies {
|
||||
fun provideUsernameApi(unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): UsernameApi
|
||||
fun provideCallingApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket, pushServiceSocket: PushServiceSocket): CallingApi
|
||||
fun providePaymentsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): PaymentsApi
|
||||
|
||||
fun provideCdsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): CdsApi
|
||||
fun provideRateLimitChallengeApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): RateLimitChallengeApi
|
||||
fun provideMessageApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket): MessageApi
|
||||
|
||||
+16
-3
@@ -22,10 +22,12 @@ import org.signal.core.util.billing.BillingApi;
|
||||
import org.signal.core.util.concurrent.DeadlockDetector;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.contentproviders.BlobProvider;
|
||||
import org.signal.donations.permits.DonationPermitsRepository;
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.zkgroup.GenericServerPublicParams;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.ServerPublicParams;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||
import org.signal.network.api.ArchiveApi;
|
||||
@@ -44,9 +46,12 @@ import org.signal.network.api.SvrBApi;
|
||||
import org.signal.network.api.UsernameApi;
|
||||
import org.signal.network.rest.SignalRestClient;
|
||||
import org.signal.network.service.MessageService;
|
||||
import org.signal.video.exo.ExoPlayerPool;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.permits.DonationPermits;
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.permits.NetworkDonationPermitIssuer;
|
||||
import org.thoughtcrime.securesms.crypto.AppAttachmentSecretStore;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalBaseIdentityKeyStore;
|
||||
@@ -108,9 +113,8 @@ import org.thoughtcrime.securesms.util.FrameRateTracker;
|
||||
import org.thoughtcrime.securesms.util.PreKeyBatcher;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.video.exo.SimpleExoPlayerPool;
|
||||
import org.signal.video.exo.ExoPlayerPool;
|
||||
import org.thoughtcrime.securesms.video.exo.GiphyMp4Cache;
|
||||
import org.thoughtcrime.securesms.video.exo.SimpleExoPlayerPool;
|
||||
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
@@ -505,7 +509,16 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
|
||||
@Override
|
||||
public @NonNull DonationsService provideDonationsService(@NonNull DonationsApi donationsApi) {
|
||||
return new DonationsService(donationsApi);
|
||||
return new DonationsService(donationsApi, DonationPermits.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull DonationPermitsRepository provideDonationPermitsRepository(@NonNull byte[] zkGroupServerPublicParams) {
|
||||
try {
|
||||
return new DonationPermitsRepository(NetworkDonationPermitIssuer.INSTANCE, new ServerPublicParams(zkGroupServerPublicParams));
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -463,6 +463,10 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
|
||||
clearLocalCredentials()
|
||||
}
|
||||
|
||||
if ((previous && !registered) || isAciChanged) {
|
||||
AppDependencies.donationPermitsRepository.clearPermits()
|
||||
}
|
||||
|
||||
if (registered && (!previous || isAciChanged)) {
|
||||
registeredAtTimestamp = System.currentTimeMillis()
|
||||
} else if (!registered) {
|
||||
|
||||
Reference in New Issue
Block a user