diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java index bc8a921cff..6d21436950 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SubscriptionReceiptRequestResponseJob.java @@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.subscription.Subscriber; -import org.thoughtcrime.securesms.subscription.DonorBadgeNotifications; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription; import org.whispersystems.signalservice.api.subscriptions.SubscriberId; import org.whispersystems.signalservice.internal.ServiceResponse; @@ -45,10 +45,9 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { public static final Object MUTEX = new Object(); - private ReceiptCredentialRequestContext requestContext; - - private final SubscriberId subscriberId; - private final boolean isForKeepAlive; + private final ReceiptCredentialRequestContext requestContext; + private final SubscriberId subscriberId; + private final boolean isForKeepAlive; private static SubscriptionReceiptRequestResponseJob createJob(SubscriberId subscriberId, boolean isForKeepAlive) { return new SubscriptionReceiptRequestResponseJob( @@ -60,12 +59,28 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) .build(), - null, + generateRequestContext(), subscriberId, isForKeepAlive ); } + private static ReceiptCredentialRequestContext generateRequestContext() { + Log.d(TAG, "Generating request credentials context for token redemption...", true); + SecureRandom secureRandom = new SecureRandom(); + byte[] randomBytes = Util.getSecretBytes(ReceiptSerial.SIZE); + + try { + ReceiptSerial receiptSerial = new ReceiptSerial(randomBytes); + ClientZkReceiptOperations operations = ApplicationDependencies.getClientZkReceiptOperations(); + + return operations.createReceiptCredentialRequestContext(secureRandom, receiptSerial); + } catch (InvalidInputException | VerificationFailedException e) { + Log.e(TAG, "Failed to create credential.", e); + throw new AssertionError(e); + } + } + public static JobManager.Chain createSubscriptionContinuationJobChain() { return createSubscriptionContinuationJobChain(false); } @@ -83,7 +98,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { } private SubscriptionReceiptRequestResponseJob(@NonNull Parameters parameters, - @Nullable ReceiptCredentialRequestContext requestContext, + @NonNull ReceiptCredentialRequestContext requestContext, @NonNull SubscriberId subscriberId, boolean isForKeepAlive) { @@ -96,11 +111,8 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { @Override public @NonNull Data serialize() { Data.Builder builder = new Data.Builder().putBlobAsString(DATA_SUBSCRIBER_ID, subscriberId.getBytes()) - .putBoolean(DATA_IS_FOR_KEEP_ALIVE, isForKeepAlive); - - if (requestContext != null) { - builder.putBlobAsString(DATA_REQUEST_BYTES, requestContext.serialize()); - } + .putBoolean(DATA_IS_FOR_KEEP_ALIVE, isForKeepAlive) + .putBlobAsString(DATA_REQUEST_BYTES, requestContext.serialize()); return builder.build(); } @@ -139,21 +151,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { MultiDeviceSubscriptionSyncRequestJob.enqueue(); } - if (requestContext == null) { - Log.d(TAG, "Generating request credentials context for token redemption...", true); - SecureRandom secureRandom = new SecureRandom(); - byte[] randomBytes = new byte[ReceiptSerial.SIZE]; - - secureRandom.nextBytes(randomBytes); - - ReceiptSerial receiptSerial = new ReceiptSerial(randomBytes); - ClientZkReceiptOperations operations = ApplicationDependencies.getClientZkReceiptOperations(); - - requestContext = operations.createReceiptCredentialRequestContext(secureRandom, receiptSerial); - } else { - Log.d(TAG, "Already have credentials generated for this request. Retrying web service call...", true); - } - Log.d(TAG, "Submitting receipt credential request."); ServiceResponse response = ApplicationDependencies.getDonationsService() .submitReceiptCredentialRequest(subscriberId, requestContext.getRequest()) @@ -201,7 +198,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { return operations.createReceiptCredentialPresentation(receiptCredential); } catch (VerificationFailedException e) { Log.w(TAG, "getReceiptCredentialPresentation: encountered a verification failure in zk", e, true); - requestContext = null; throw new RetryableException(); } } @@ -213,7 +209,6 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { return operations.receiveReceiptCredential(requestContext, response); } catch (VerificationFailedException e) { Log.w(TAG, "getReceiptCredential: encountered a verification failure in zk", e, true); - requestContext = null; throw new RetryableException(); } } @@ -269,7 +264,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { * - expiration_time mod 86400 == 0 * - expiration_time is between now and 60 days from now */ - private boolean isCredentialValid(@NonNull ActiveSubscription.Subscription subscription, @NonNull ReceiptCredential receiptCredential) { + private static boolean isCredentialValid(@NonNull ActiveSubscription.Subscription subscription, @NonNull ReceiptCredential receiptCredential) { long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); long maxExpirationTime = now + TimeUnit.DAYS.toSeconds(60); boolean isSameLevel = subscription.getLevel() == receiptCredential.getReceiptLevel(); @@ -292,28 +287,30 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob { return e instanceof RetryableException; } - @VisibleForTesting - final static class RetryableException extends Exception { + @VisibleForTesting final static class RetryableException extends Exception { } public static class Factory implements Job.Factory { @Override public @NonNull SubscriptionReceiptRequestResponseJob create(@NonNull Parameters parameters, @NonNull Data data) { - SubscriberId subscriberId = SubscriberId.fromBytes(data.getStringAsBlob(DATA_SUBSCRIBER_ID)); - boolean isForKeepAlive = data.getBooleanOrDefault(DATA_IS_FOR_KEEP_ALIVE, false); + SubscriberId subscriberId = SubscriberId.fromBytes(data.getStringAsBlob(DATA_SUBSCRIBER_ID)); + boolean isForKeepAlive = data.getBooleanOrDefault(DATA_IS_FOR_KEEP_ALIVE, false); + byte[] requestContextBytes = data.getStringAsBlob(DATA_REQUEST_BYTES); - try { - if (data.hasString(DATA_REQUEST_BYTES)) { - byte[] blob = data.getStringAsBlob(DATA_REQUEST_BYTES); - ReceiptCredentialRequestContext requestContext = new ReceiptCredentialRequestContext(blob); - - return new SubscriptionReceiptRequestResponseJob(parameters, requestContext, subscriberId, isForKeepAlive); - } else { - return new SubscriptionReceiptRequestResponseJob(parameters, null, subscriberId, isForKeepAlive); + ReceiptCredentialRequestContext requestContext; + if (requestContextBytes == null) { + Log.i(TAG, "Generating a request context for a legacy instance of SubscriptionReceiptRequestResponseJob", true); + requestContext = generateRequestContext(); + } else { + try { + requestContext = new ReceiptCredentialRequestContext(requestContextBytes); + } catch (InvalidInputException e) { + Log.e(TAG, "Failed to generate request context from bytes", e); + throw new AssertionError(e); } - } catch (InvalidInputException e) { - throw new IllegalStateException(e); } + + return new SubscriptionReceiptRequestResponseJob(parameters, requestContext, subscriberId, isForKeepAlive); } } }