EntitlementInfo: add a grace period limit to outdated entitlements#2288
Conversation
Codecov Report
@@ Coverage Diff @@
## main #2288 +/- ##
==========================================
+ Coverage 86.50% 86.51% +0.01%
==========================================
Files 188 188
Lines 12715 12743 +28
==========================================
+ Hits 10999 11025 +26
- Misses 1716 1718 +2
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
|
Don't merge this yet, we need to solve one issue with |
|
That will be fixed by #2310. |
|
Lmk if you need this reviewed then! |
|
Need to finish #2310 before this. |
282ea47 to
5e11954
Compare
### Changes: - Added `HTTPResponseBody.copy(with newRequestDate:)` to be able to modify responses from the header request date - `CustomerInfo` implements this method to modify its request date - Added `HTTPResponse.requestDate` to be able to keep track of the request date from the server - Added `VerificationResult.from(cache:response:)` to determine the most restrictive verification result based on what's cached or checked from a response - Added `HTTPResponse.copy(with:)` to modify the verification result of a response using the previous method - `HTTPClient` now uses the most restrictive verification result - `HTTPClient` updates request date from server responses or cached responses (unless verification failed). This was the missing piece for #2288. ### Tests: - Verify that `ETagManager` does not use an ETag if verification was previously not enabled - Verify that `ETagManager` returns the request date from the server when returning a cached response - Verify that `HTTPClient` updates the request date from the server or from a cached response - Verify that `HTTPClient` does not update request date if verification failed - Tests for `HTTPResponse` request date parsing - Test for `CustomerInfoResponseHandler` updating request date - Tests for `CustomerInfo.copy(with newRequestDate:)` ### Other smaller changes: - Moved `CustomerInfo.asData()` into `Encodable.asJSONEncodedData()` - Renamed `requestTime` to `requestDate` everywhere for consistency
d50f942 to
7a2204d
Compare
5e11954 to
95b6442
Compare
There was a problem hiding this comment.
JSON requires ISO8601 dates with no milliseconds.
Nothing else uses this, and technically this is more correct as noMilliseconds is implemented with only .withInternetDateTime.
There was a problem hiding this comment.
Slightly more precise test.
908c8d0 to
c4a0746
Compare
|
|
||
| func testCopyWithNewRequestDateUpdatesEntitlements() throws { | ||
| expect(self.customerInfo.entitlements.active).to(haveCount(2)) | ||
| expect(self.customerInfo.entitlements["old_pro"]?.isActive) == false |
There was a problem hiding this comment.
This test was added in #2310.
old_pro no longer works because of the new grace period, so I've added a new expired_pro that expired not too far in the past.
|
@tonidero this is ready now. |
| let referenceDate: Date = requestDate ?? Date() | ||
| return expirationDate.timeIntervalSince(referenceDate) >= 0 | ||
| let (referenceDate, inGracePeriod) = Self.referenceDate(for: requestDate) | ||
| let isActive = expirationDate.timeIntervalSince(referenceDate) >= 0 |
There was a problem hiding this comment.
I wonder if the grace period should apply to the expirationDate instead of to the request date... I think I could go either way so this is fine for now.
### Purpose: This is a prerequisite for #2288. The new grace period means that if we didn't update the date from the cached responses, entitlements would become stale and expired after 3 days. ### Changes: - Added `HTTPResponseBody.copy(with newRequestDate:)` to be able to modify responses from the header request date - `CustomerInfo` implements this method to modify its request date - Added `HTTPResponse.requestDate` to be able to keep track of the request date from the server - Added `VerificationResult.from(cache:response:)` to determine the most restrictive verification result based on what's cached or checked from a response - Added `HTTPResponse.copy(with:)` to modify the verification result of a response using the previous method - `HTTPClient` now uses the most restrictive verification result - `HTTPClient` updates request date from server responses or cached responses (unless verification failed). This was the missing piece for #2288. ### Tests: - Verify that `ETagManager` does not use an ETag if verification was previously not enabled - Verify that `ETagManager` returns the request date from the server when returning a cached response - Verify that `HTTPClient` updates the request date from the server or from a cached response - Verify that `HTTPClient` does not update request date if verification failed - Tests for `HTTPResponse` request date parsing - Test for `CustomerInfoResponseHandler` updating request date - Tests for `CustomerInfo.copy(with newRequestDate:)` ### Other smaller changes: - Moved `CustomerInfo.asData()` into `Encodable.asJSONEncodedData()` - Renamed `requestTime` to `requestDate` everywhere for consistency
This prevents users from keeping an outdated active entitlement forever. One could start a free trial then block all further requests from the backend in order to keep that active.
c4a0746 to
b3fe95c
Compare
tonidero
left a comment
There was a problem hiding this comment.
@NachoSoto I noticed we didn't update the logic in CustomerInfo in https://github.com/RevenueCat/purchases-ios/blob/main/Sources/Identity/CustomerInfo.swift#L359 . We probably should update it there as well?
| private extension EntitlementInfo { | ||
|
|
||
| class func isDateActive(expirationDate: Date?, forRequestDate requestDate: Date?) -> Bool { | ||
| static func isDateActive(expirationDate: Date?, for requestDate: Date?) -> Bool { |
There was a problem hiding this comment.
Also, while working on the Android side, I noticed we could assume we always have a requestDate, so this wouldn't be nullable. I think it would be the same in iOS, but not sure if I'm missing something.
There was a problem hiding this comment.
I think you're right!
See #2288 (comment) This didn't need to be `Optional` since it's always there.
See #2288 (comment) This didn't need to be `Optional` since it's always there.
|
@tonidero great catch! I'm going to make |
### Description Completes [SDK-2895](https://linear.app/revenuecat/issue/SDK-2895/create-a-grace-period-for-entitlements-when-the-backend-is-offline). Android equivalent of: RevenueCat/purchases-ios#2288 In this PR we are adding a 3 day grace period to entitlements expiration dates.
### Description Completes [SDK-2895](https://linear.app/revenuecat/issue/SDK-2895/create-a-grace-period-for-entitlements-when-the-backend-is-offline). Android equivalent of: RevenueCat/purchases-ios#2288 In this PR we are adding a 3 day grace period to entitlements expiration dates.
### New Features * New `ErrorCode.signatureVerificationFailed` which will be used for an upcoming feature ### Bugfixes * `Purchases.deinit`: don't reset `Purchases.proxyURL` (#2346) via NachoSoto (@NachoSoto) <details> <summary><b>Other Changes</b></summary> * Introduced `Configuration.EntitlementVerificationMode` and `VerificationResult` (#2277) via NachoSoto (@NachoSoto) * `PurchasesDiagnostics`: added step to verify signature verification (#2267) via NachoSoto (@NachoSoto) * `HTTPClient`: added signature validation and introduced `ErrorCode.signatureVerificationFailed` (#2272) via NachoSoto (@NachoSoto) * `ETagManager`: don't use ETags if response verification failed (#2347) via NachoSoto (@NachoSoto) * `Integration Tests`: removed `@preconcurrency import` (#2464) via NachoSoto (@NachoSoto) * Clean up: moved `ReceiptParserTests-Info.plist` out of root (#2460) via NachoSoto (@NachoSoto) * Update `CHANGELOG` (#2461) via NachoSoto (@NachoSoto) * Update `SwiftSnapshotTesting` (#2453) via NachoSoto (@NachoSoto) * Fixed docs (#2432) via Kaunteya Suryawanshi (@kaunteya) * Remove unnecessary line break (#2435) via Andy Boedo (@aboedo) * `ProductEntitlementMapping`: enabled entitlement mapping fetching (#2425) via NachoSoto (@NachoSoto) * `BackendPostReceiptDataTests`: increased timeout to fix flaky test (#2426) via NachoSoto (@NachoSoto) * Updated requirements to drop Xcode 13.x support (#2419) via NachoSoto (@NachoSoto) * `Integration Tests`: fixed flaky errors when loading offerings (#2420) via NachoSoto (@NachoSoto) * `PurchaseTester`: fixed compilation for `internal` entitlement verification (#2417) via NachoSoto (@NachoSoto) * `ETagManager`/`HTTPClient`: sending new `X-RC-Last-Refresh-Time` header (#2373) via NachoSoto (@NachoSoto) * `ETagManager`: don't send validation time if not present (#2490) via NachoSoto (@NachoSoto) * SwiftUI Sample Project: Refactor Package terms method to a computed property (#2405) via Joseph Kokenge (@JOyo246) * Clean up v3 load shedder integration tests (#2402) via Andy Boedo (@aboedo) * Fix iOS 12 compilation (#2394) via NachoSoto (@NachoSoto) * Added new `VerificationResult.verifiedOnDevice` (#2379) via NachoSoto (@NachoSoto) * `PurchaseTester`: fix memory leaks (#2392) via Keita Watanabe (@kitwtnb) * Integration tests: add scheduled job (#2389) via Andy Boedo (@aboedo) * Add lane for running iOS v3 load shedder integration tests (#2388) via Andy Boedo (@aboedo) * iOS v3 load shedder integration tests (#2387) via Andy Boedo (@aboedo) * `Offline Entitlements`: created `LoadShedderIntegrationTests` (#2362) via NachoSoto (@NachoSoto) * Purchases.configure: log warning if attempting to use a static appUserID (#2385) via Mark Villacampa (@MarkVillacampa) * `SubscriberAttributesManagerIntegrationTests`: fixed flaky failures (#2381) via NachoSoto (@NachoSoto) * `@DefaultDecodable.Now`: fixed flaky test (#2374) via NachoSoto (@NachoSoto) * `PurchaseTesterSwiftUI`: fixed iOS compilation (#2376) via NachoSoto (@NachoSoto) * `SubscriberAttributesManagerIntegrationTests`: fixed potential race condition (#2380) via NachoSoto (@NachoSoto) * `Offline Entitlements`: create `CustomerInfo` from offline entitlements (#2358) via NachoSoto (@NachoSoto) * Added `@DefaultDecodable.Now` (#2372) via NachoSoto (@NachoSoto) * `HTTPClient`: debug log when performing redirects (#2371) via NachoSoto (@NachoSoto) * `HTTPClient`: new flag to force server errors (#2370) via NachoSoto (@NachoSoto) * `OfferingsManager`: fixed Xcode 13.x build (#2369) via NachoSoto (@NachoSoto) * `Offline Entitlements`: store `ProductEntitlementMapping` in cache (#2355) via NachoSoto (@NachoSoto) * `Offline Entitlements`: added support for fetching `ProductEntitlementMappingResponse` in `OfflineEntitlementsAPI` (#2353) via NachoSoto (@NachoSoto) * `Offline Entitlements`: created `ProductEntitlementMapping` (#2365) via NachoSoto (@NachoSoto) * Implemented `NetworkError.isServerDown` (#2367) via NachoSoto (@NachoSoto) * `ETagManager`: added test for 304 responses with no etag (#2360) via NachoSoto (@NachoSoto) * `TestLogHandler`: increased default capacity (#2357) via NachoSoto (@NachoSoto) * `OfferingsManager`: moved log to common method to remove hardcoded string (#2363) via NachoSoto (@NachoSoto) * `Offline Entitlements`: created `ProductEntitlementMappingResponse` (#2351) via NachoSoto (@NachoSoto) * `HTTPClient`: added test for 2xx response for request with etag (#2361) via NachoSoto (@NachoSoto) * `PurchaseTesterSwiftUI` improvements (#2345) via NachoSoto (@NachoSoto) * `ConfigureStrings`: fixed double-space typo (#2344) via NachoSoto (@NachoSoto) * `ETagManagerTests`: fixed tests on iOS 12 (#2349) via NachoSoto (@NachoSoto) * `DeviceCache`: simplified constructor (#2354) via NachoSoto (@NachoSoto) * `Trusted Entitlements`: changed all APIs to `internal` (#2350) via NachoSoto (@NachoSoto) * `VerificationResult.notRequested`: removed caching reference (#2337) via NachoSoto (@NachoSoto) * Finished signature verification `HTTPClient` tests (#2333) via NachoSoto (@NachoSoto) * `Configuration.Builder.with(entitlementVerificationMode:)`: improved documentation (#2334) via NachoSoto (@NachoSoto) * `ETagManager`: don't ignore failed etags with `Signing.VerificationMode.informational` (#2331) via NachoSoto (@NachoSoto) * `IdentityManager`: clear `ETagManager` and `DeviceCache` if verification is enabled but cached `CustomerInfo` is not (#2330) via NachoSoto (@NachoSoto) * Made `Configuration.EntitlementVerificationMode.enforced` unavailable (#2329) via NachoSoto (@NachoSoto) * Refactor: reorganized files in new Security and Misc folders (#2326) via NachoSoto (@NachoSoto) * `CustomerInfo`: use same grace period logic for active subscriptions (#2327) via NachoSoto (@NachoSoto) * `HTTPClient`: don't verify 4xx/5xx responses (#2322) via NachoSoto (@NachoSoto) * `EntitlementInfo`: request date is not optional (#2325) via NachoSoto (@NachoSoto) * `CustomerInfo`: removed `entitlementVerification` (#2320) via NachoSoto (@NachoSoto) * Renamed `VerificationResult.notVerified` to `.notRequested` (#2321) via NachoSoto (@NachoSoto) * `EntitlementInfo`: add a grace period limit to outdated entitlements (#2288) via NachoSoto (@NachoSoto) * Update `CustomerInfo.requestDate` from 304 responses (#2310) via NachoSoto (@NachoSoto) * `Signing`: added request time & eTag to signature verification (#2309) via NachoSoto (@NachoSoto) * `HTTPClient`: changed header search to be case-insensitive (#2308) via NachoSoto (@NachoSoto) * `HTTPClient`: automatically add `nonce` based on `HTTPRequest.Path` (#2286) via NachoSoto (@NachoSoto) * `PurchaseTester`: added ability to reload `CustomerInfo` with a custom `CacheFetchPolicy` (#2312) via NachoSoto (@NachoSoto) * Fix issue where underlying error information for product fetch errors was not printed in log. (#2281) via Chris Vasselli (@chrisvasselli) * `PurchaseTester`: added ability to set `Configuration.EntitlementVerificationMode` (#2290) via NachoSoto (@NachoSoto) * SwiftUI: Paywall View should respond to changes on the UserView model (#2297) via ConfusedVorlon (@ConfusedVorlon) * Deprecate `usesStoreKit2IfAvailable` (#2293) via Andy Boedo (@aboedo) * `Signing`: updated to use production public key (#2274) via NachoSoto (@NachoSoto) </details> --------- Co-authored-by: RCGitBot <dev+RCGitBot@revenuecat.com>
### New Features * New `ErrorCode.signatureVerificationFailed` which will be used for an upcoming feature ### Bugfixes * `Purchases.deinit`: don't reset `Purchases.proxyURL` (RevenueCat#2346) via NachoSoto (@NachoSoto) <details> <summary><b>Other Changes</b></summary> * Introduced `Configuration.EntitlementVerificationMode` and `VerificationResult` (RevenueCat#2277) via NachoSoto (@NachoSoto) * `PurchasesDiagnostics`: added step to verify signature verification (RevenueCat#2267) via NachoSoto (@NachoSoto) * `HTTPClient`: added signature validation and introduced `ErrorCode.signatureVerificationFailed` (RevenueCat#2272) via NachoSoto (@NachoSoto) * `ETagManager`: don't use ETags if response verification failed (RevenueCat#2347) via NachoSoto (@NachoSoto) * `Integration Tests`: removed `@preconcurrency import` (RevenueCat#2464) via NachoSoto (@NachoSoto) * Clean up: moved `ReceiptParserTests-Info.plist` out of root (RevenueCat#2460) via NachoSoto (@NachoSoto) * Update `CHANGELOG` (RevenueCat#2461) via NachoSoto (@NachoSoto) * Update `SwiftSnapshotTesting` (RevenueCat#2453) via NachoSoto (@NachoSoto) * Fixed docs (RevenueCat#2432) via Kaunteya Suryawanshi (@kaunteya) * Remove unnecessary line break (RevenueCat#2435) via Andy Boedo (@aboedo) * `ProductEntitlementMapping`: enabled entitlement mapping fetching (RevenueCat#2425) via NachoSoto (@NachoSoto) * `BackendPostReceiptDataTests`: increased timeout to fix flaky test (RevenueCat#2426) via NachoSoto (@NachoSoto) * Updated requirements to drop Xcode 13.x support (RevenueCat#2419) via NachoSoto (@NachoSoto) * `Integration Tests`: fixed flaky errors when loading offerings (RevenueCat#2420) via NachoSoto (@NachoSoto) * `PurchaseTester`: fixed compilation for `internal` entitlement verification (RevenueCat#2417) via NachoSoto (@NachoSoto) * `ETagManager`/`HTTPClient`: sending new `X-RC-Last-Refresh-Time` header (RevenueCat#2373) via NachoSoto (@NachoSoto) * `ETagManager`: don't send validation time if not present (RevenueCat#2490) via NachoSoto (@NachoSoto) * SwiftUI Sample Project: Refactor Package terms method to a computed property (RevenueCat#2405) via Joseph Kokenge (@JOyo246) * Clean up v3 load shedder integration tests (RevenueCat#2402) via Andy Boedo (@aboedo) * Fix iOS 12 compilation (RevenueCat#2394) via NachoSoto (@NachoSoto) * Added new `VerificationResult.verifiedOnDevice` (RevenueCat#2379) via NachoSoto (@NachoSoto) * `PurchaseTester`: fix memory leaks (RevenueCat#2392) via Keita Watanabe (@kitwtnb) * Integration tests: add scheduled job (RevenueCat#2389) via Andy Boedo (@aboedo) * Add lane for running iOS v3 load shedder integration tests (RevenueCat#2388) via Andy Boedo (@aboedo) * iOS v3 load shedder integration tests (RevenueCat#2387) via Andy Boedo (@aboedo) * `Offline Entitlements`: created `LoadShedderIntegrationTests` (RevenueCat#2362) via NachoSoto (@NachoSoto) * Purchases.configure: log warning if attempting to use a static appUserID (RevenueCat#2385) via Mark Villacampa (@MarkVillacampa) * `SubscriberAttributesManagerIntegrationTests`: fixed flaky failures (RevenueCat#2381) via NachoSoto (@NachoSoto) * `@DefaultDecodable.Now`: fixed flaky test (RevenueCat#2374) via NachoSoto (@NachoSoto) * `PurchaseTesterSwiftUI`: fixed iOS compilation (RevenueCat#2376) via NachoSoto (@NachoSoto) * `SubscriberAttributesManagerIntegrationTests`: fixed potential race condition (RevenueCat#2380) via NachoSoto (@NachoSoto) * `Offline Entitlements`: create `CustomerInfo` from offline entitlements (RevenueCat#2358) via NachoSoto (@NachoSoto) * Added `@DefaultDecodable.Now` (RevenueCat#2372) via NachoSoto (@NachoSoto) * `HTTPClient`: debug log when performing redirects (RevenueCat#2371) via NachoSoto (@NachoSoto) * `HTTPClient`: new flag to force server errors (RevenueCat#2370) via NachoSoto (@NachoSoto) * `OfferingsManager`: fixed Xcode 13.x build (RevenueCat#2369) via NachoSoto (@NachoSoto) * `Offline Entitlements`: store `ProductEntitlementMapping` in cache (RevenueCat#2355) via NachoSoto (@NachoSoto) * `Offline Entitlements`: added support for fetching `ProductEntitlementMappingResponse` in `OfflineEntitlementsAPI` (RevenueCat#2353) via NachoSoto (@NachoSoto) * `Offline Entitlements`: created `ProductEntitlementMapping` (RevenueCat#2365) via NachoSoto (@NachoSoto) * Implemented `NetworkError.isServerDown` (RevenueCat#2367) via NachoSoto (@NachoSoto) * `ETagManager`: added test for 304 responses with no etag (RevenueCat#2360) via NachoSoto (@NachoSoto) * `TestLogHandler`: increased default capacity (RevenueCat#2357) via NachoSoto (@NachoSoto) * `OfferingsManager`: moved log to common method to remove hardcoded string (RevenueCat#2363) via NachoSoto (@NachoSoto) * `Offline Entitlements`: created `ProductEntitlementMappingResponse` (RevenueCat#2351) via NachoSoto (@NachoSoto) * `HTTPClient`: added test for 2xx response for request with etag (RevenueCat#2361) via NachoSoto (@NachoSoto) * `PurchaseTesterSwiftUI` improvements (RevenueCat#2345) via NachoSoto (@NachoSoto) * `ConfigureStrings`: fixed double-space typo (RevenueCat#2344) via NachoSoto (@NachoSoto) * `ETagManagerTests`: fixed tests on iOS 12 (RevenueCat#2349) via NachoSoto (@NachoSoto) * `DeviceCache`: simplified constructor (RevenueCat#2354) via NachoSoto (@NachoSoto) * `Trusted Entitlements`: changed all APIs to `internal` (RevenueCat#2350) via NachoSoto (@NachoSoto) * `VerificationResult.notRequested`: removed caching reference (RevenueCat#2337) via NachoSoto (@NachoSoto) * Finished signature verification `HTTPClient` tests (RevenueCat#2333) via NachoSoto (@NachoSoto) * `Configuration.Builder.with(entitlementVerificationMode:)`: improved documentation (RevenueCat#2334) via NachoSoto (@NachoSoto) * `ETagManager`: don't ignore failed etags with `Signing.VerificationMode.informational` (RevenueCat#2331) via NachoSoto (@NachoSoto) * `IdentityManager`: clear `ETagManager` and `DeviceCache` if verification is enabled but cached `CustomerInfo` is not (RevenueCat#2330) via NachoSoto (@NachoSoto) * Made `Configuration.EntitlementVerificationMode.enforced` unavailable (RevenueCat#2329) via NachoSoto (@NachoSoto) * Refactor: reorganized files in new Security and Misc folders (RevenueCat#2326) via NachoSoto (@NachoSoto) * `CustomerInfo`: use same grace period logic for active subscriptions (RevenueCat#2327) via NachoSoto (@NachoSoto) * `HTTPClient`: don't verify 4xx/5xx responses (RevenueCat#2322) via NachoSoto (@NachoSoto) * `EntitlementInfo`: request date is not optional (RevenueCat#2325) via NachoSoto (@NachoSoto) * `CustomerInfo`: removed `entitlementVerification` (RevenueCat#2320) via NachoSoto (@NachoSoto) * Renamed `VerificationResult.notVerified` to `.notRequested` (RevenueCat#2321) via NachoSoto (@NachoSoto) * `EntitlementInfo`: add a grace period limit to outdated entitlements (RevenueCat#2288) via NachoSoto (@NachoSoto) * Update `CustomerInfo.requestDate` from 304 responses (RevenueCat#2310) via NachoSoto (@NachoSoto) * `Signing`: added request time & eTag to signature verification (RevenueCat#2309) via NachoSoto (@NachoSoto) * `HTTPClient`: changed header search to be case-insensitive (RevenueCat#2308) via NachoSoto (@NachoSoto) * `HTTPClient`: automatically add `nonce` based on `HTTPRequest.Path` (RevenueCat#2286) via NachoSoto (@NachoSoto) * `PurchaseTester`: added ability to reload `CustomerInfo` with a custom `CacheFetchPolicy` (RevenueCat#2312) via NachoSoto (@NachoSoto) * Fix issue where underlying error information for product fetch errors was not printed in log. (RevenueCat#2281) via Chris Vasselli (@chrisvasselli) * `PurchaseTester`: added ability to set `Configuration.EntitlementVerificationMode` (RevenueCat#2290) via NachoSoto (@NachoSoto) * SwiftUI: Paywall View should respond to changes on the UserView model (RevenueCat#2297) via ConfusedVorlon (@ConfusedVorlon) * Deprecate `usesStoreKit2IfAvailable` (RevenueCat#2293) via Andy Boedo (@aboedo) * `Signing`: updated to use production public key (RevenueCat#2274) via NachoSoto (@NachoSoto) </details> --------- Co-authored-by: RCGitBot <dev+RCGitBot@revenuecat.com>
This prevents users from keeping an outdated active entitlement forever.
One could start a free trial then block all further requests from the backend in order to keep that active.
When this check fails, a new warning log is generated:
Depends on #2310