Skip to content

Commit

Permalink
Ensure capability check occurs for gift recipients.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-signal committed Jan 10, 2023
1 parent 53e347a commit 84d0283
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
package org.thoughtcrime.securesms.badges.gifts.flow

import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadgeAmounts
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadges
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.ProfileUtil
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
import java.io.IOException
import java.util.Currency
import java.util.Locale

Expand Down Expand Up @@ -50,39 +42,4 @@ class GiftFlowRepository {
.flatMap { it.flattenResult() }
.map { it.getGiftBadgeAmounts() }
}

/**
* Verifies that the given recipient is a supported target for a gift.
*
* TODO[alex] - this needs to be incorporated into the correct flows.
*/
fun verifyRecipientIsAllowedToReceiveAGift(badgeRecipient: RecipientId): Completable {
return Completable.fromAction {
Log.d(TAG, "Verifying badge recipient $badgeRecipient", true)
val recipient = Recipient.resolved(badgeRecipient)

if (recipient.isSelf) {
Log.d(TAG, "Cannot send a gift to self.", true)
throw DonationError.GiftRecipientVerificationError.SelectedRecipientDoesNotSupportGifts
}

if (recipient.isGroup || recipient.isDistributionList || recipient.registered != RecipientTable.RegisteredState.REGISTERED) {
Log.w(TAG, "Invalid badge recipient $badgeRecipient. Verification failed.", true)
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
}

try {
val profile = ProfileUtil.retrieveProfileSync(ApplicationDependencies.getApplication(), recipient, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL)
if (!profile.profile.capabilities.isGiftBadges) {
Log.w(TAG, "Badge recipient does not support gifting. Verification failed.", true)
throw DonationError.GiftRecipientVerificationError.SelectedRecipientDoesNotSupportGifts
} else {
Log.d(TAG, "Badge recipient supports gifting. Verification successful.", true)
}
} catch (e: IOException) {
Log.w(TAG, "Failed to retrieve profile for recipient.", e, true)
throw DonationError.GiftRecipientVerificationError.FailedToFetchProfile(e)
}
}.subscribeOn(Schedulers.io())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.JobTracker
import org.thoughtcrime.securesms.jobs.BoostReceiptRequestResponseJob
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.ProfileUtil
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.internal.push.DonationProcessor
import java.io.IOException
import java.util.Currency
import java.util.Locale
import java.util.concurrent.CountDownLatch
Expand All @@ -38,6 +42,36 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
Single.error(DonationError.getPaymentSetupError(errorSource, throwable, paymentSourceType))
}
}

fun verifyRecipientIsAllowedToReceiveAGift(badgeRecipient: RecipientId): Completable {
return Completable.fromAction {
Log.d(TAG, "Verifying badge recipient $badgeRecipient", true)
val recipient = Recipient.resolved(badgeRecipient)

if (recipient.isSelf) {
Log.d(TAG, "Cannot send a gift to self.", true)
throw DonationError.GiftRecipientVerificationError.SelectedRecipientDoesNotSupportGifts
}

if (recipient.isGroup || recipient.isDistributionList || recipient.registered != RecipientTable.RegisteredState.REGISTERED) {
Log.w(TAG, "Invalid badge recipient $badgeRecipient. Verification failed.", true)
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
}

try {
val profile = ProfileUtil.retrieveProfileSync(ApplicationDependencies.getApplication(), recipient, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL)
if (!profile.profile.capabilities.isGiftBadges) {
Log.w(TAG, "Badge recipient does not support gifting. Verification failed.", true)
throw DonationError.GiftRecipientVerificationError.SelectedRecipientDoesNotSupportGifts
} else {
Log.d(TAG, "Badge recipient supports gifting. Verification successful.", true)
}
} catch (e: IOException) {
Log.w(TAG, "Failed to retrieve profile for recipient.", e, true)
throw DonationError.GiftRecipientVerificationError.FailedToFetchProfile(e)
}
}.subscribeOn(Schedulers.io())
}
}

fun getBoosts(): Single<Map<Currency, List<Boost>>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,20 @@ class PayPalPaymentInProgressViewModel(
) {
Log.d(TAG, "Proceeding with one-time payment pipeline...", true)
store.update { DonationProcessorStage.PAYMENT_PIPELINE }
val verifyUser = if (request.donateToSignalType == DonateToSignalType.GIFT) {
OneTimeDonationRepository.verifyRecipientIsAllowedToReceiveAGift(request.recipientId)
} else {
Completable.complete()
}

disposables += payPalRepository
.createOneTimePaymentIntent(
amount = request.fiat,
badgeRecipient = request.recipientId,
badgeLevel = request.level
disposables += verifyUser
.andThen(
payPalRepository
.createOneTimePaymentIntent(
amount = request.fiat,
badgeRecipient = request.recipientId,
badgeLevel = request.level
)
)
.flatMap(routeToPaypalConfirmation)
.flatMap { result ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,13 @@ class StripePaymentInProgressViewModel(
Log.w(TAG, "Beginning one-time payment pipeline...", true)

val amount = request.fiat
val verifyUser = if (request.donateToSignalType == DonateToSignalType.GIFT) {
OneTimeDonationRepository.verifyRecipientIsAllowedToReceiveAGift(request.recipientId)
} else {
Completable.complete()
}

val continuePayment: Single<StripeIntentAccessor> = stripeRepository.continuePayment(amount, request.recipientId, request.level, paymentSourceProvider.paymentSourceType)
val continuePayment: Single<StripeIntentAccessor> = verifyUser.andThen(stripeRepository.continuePayment(amount, request.recipientId, request.level, paymentSourceProvider.paymentSourceType))
val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider.paymentSource, ::Pair)

disposables += intentAndSource.flatMapCompletable { (paymentIntent, paymentSource) ->
Expand Down

0 comments on commit 84d0283

Please sign in to comment.