Update CustomerInfo.requestDate from 304 responses#2310
Conversation
8070e78 to
fb31138
Compare
0b95ec2 to
405c654
Compare
fb31138 to
07fff48
Compare
CustomerInfo: get requestDate from HTTPResponse headerCustomerInfo.requestTime from 304 responses
e228f72 to
59cdce7
Compare
f15a7d0 to
648de65
Compare
CustomerInfo.requestTime from 304 responsesCustomerInfo.requestDate from 304 responses
335d4e2 to
ff74ab4
Compare
58e5366 to
843b6c3
Compare
843b6c3 to
d50f942
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
Codecov Report
@@ Coverage Diff @@
## main #2310 +/- ##
==========================================
+ Coverage 86.41% 86.45% +0.04%
==========================================
Files 187 188 +1
Lines 12630 12714 +84
==========================================
+ Hits 10914 10992 +78
- Misses 1716 1722 +6
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
tonidero
left a comment
There was a problem hiding this comment.
Just a possible edge case I was thinking about but looking great!
| let path: HTTPRequest.Path = .mockPath | ||
| let mockedResponse = BodyWithDate(data: "test", requestDate: Date().addingTimeInterval(-3000000)) | ||
| let encodedResponse = try mockedResponse.asJSONEncodedData() | ||
| let requestDate = Date().addingTimeInterval(-100000) |
There was a problem hiding this comment.
What do these numbers mean? We could also hardcode a date for this and the mockedResponse request date to make it more deterministic.
There was a problem hiding this comment.
It's just a simpler way to make a date that's not "now" to avoid false negatives.
|
Can you add a test in case the server returns a 500 error? Want to ensure that is not treated as verification failure even if the date header is not present. |
| requestDate: requestDate, | ||
| verificationResult: self.verificationResult |
There was a problem hiding this comment.
We should only update the requestDate if the verification result of the NOT_MODIFIED response is green, right?
There was a problem hiding this comment.
And do we persist this date change in the cache?
Imagine:
- 200 OK, valid signature, with request time t=1. SDK gets (valid signature, t=1)
- 304 NOT_MODIFIED, valid signature, with request time t=2. SDK gets (valid signature, t=2)
- Timeout or error 500... What the SDK will retrieve t=1 or t=2?. Should be (valid signature, t=2)
There was a problem hiding this comment.
Similarly:
- 200 OK, valid signature with request time t=1. SDK gets (valid signature, t=1)
- 304 NOT_MODIFIED, INVALID signature, with request time t=2. SDK should get (valid signature, t=1)
- Timeout or error 500, SDK should get (valid signature, t=1)
And:
- 200 OK valid signature, t=1. SDK gets (valid signature, t=1)
- 200 OK INVALID SIGNATURE, t=2. SDK gets (valid signature, t=1)
- Timeout or error 500, SDK should get (valid signature, t=1)
There was a problem hiding this comment.
Nacho can correct me but I believe we currently cache the request time not at the response level but at the model level. Right now we only cache it for the CustomerInfo model so we only store it in those models. We don't update the response cache on not modified responses either
There was a problem hiding this comment.
Timeout or error 500... What the SDK will retrieve t=1 or t=2?. Should be (valid signature, t=2)
Short answer is t2.
@bisho you're conflating 2 separate layers of caching (which is normal because you're not familiar with the codebase).
If the server fails to return a 304 (500 or timeout), HTTPClient won't return the cached data.
That caching would be handled by DeviceCache, which would receive the updated request time.
There was a problem hiding this comment.
We should only update the requestDate if the verification result of the NOT_MODIFIED response is green, right?
That I think I have a test for but let me check.
There was a problem hiding this comment.
304 NOT_MODIFIED, INVALID signature, with request time t=2. SDK should get (valid signature, t=1)
Oh that's a case I didn't consider.
Are we sure we want to do that? That would break an app without any indication that this is happening.
There was a problem hiding this comment.
We should only update the requestDate if the verification result of the NOT_MODIFIED response is green, right?
That's already done:
fileprivate func copyWithNewRequestDate() -> Self {
// Update request time from server unless it failed verification.
guard self.verificationResult != .failed, let requestDate = self.requestDate else { return self }There was a problem hiding this comment.
304 NOT_MODIFIED, INVALID signature, with request time t=2. SDK should get (valid signature, t=1)
Oh that's a case I didn't consider.
Are we sure we want to do that? That would break an app without any indication that this is happening.
The thing is that you can't use t=2 as it is not verified. And making the cache not verified in whole defeats the purpose of the cache. Note that there are legit cases of tampering (you are in an airport wifi that intercepts HTTPs. So you can't assume it is a bad actor and use t=2 but making it fail validation. We have a cache, use it until you can. The only case for using t=2 is if there app has disabled verification.
There was a problem hiding this comment.
I agree that we can't use t=2, otherwise that opens us up to an easy attack.
But I'm not sure about the part about considering any result that we have "verified" if we get a signature that we can't verify - even if it's from a 304. Like, "someone told us to use what's in cache but we don't really trust them" seems like a clear use case for "not verified".
I get that we're not entirely sure if any valid tampering will happen in real life, but... this is all happening at the application level, right? Like, there really shouldn't be any modified content in the response content, datetime or request uuid that a public wifi modifies to have us fail validation.
It feels like we might be setting ourselves up for one of those use cases that takes a long time to explain to someone, while just saying "if you got an invalid signature, you get an unverified entitlement" seems a lot simpler
|
|
||
| /// - Returns: the most restrictive ``VerificationResult`` based on the cached verification and | ||
| /// the response verification. | ||
| static func from(cache cachedResult: Self, response responseResult: Self) -> Self { |
There was a problem hiding this comment.
Oh, I see that here you will return the response as not valid if a 304 is not valid. I don't think we should do this. We should act as unable to fetch. There is a legit situation for this, imagine you are in a airport wifi that intercepts and mangles requests. It should not break your cache.
I think I still have trouble with the logic for validation being in two places.
I would rather do something like:
- For validation errors (either 200, 403...), if there is cache, treat this as a 500, just return cache
- For 403 update request time in cache only if cache was checked valid and 304 was valid too (304 invalid would be covered in the above case anyway). Then return cache.
- For valid 200, update cache, return fresh data
Pseudocode:
# On requests:
if cache and (cacheValidationOk or SDKValidationDisabled):
request_with_cache_etag()
else:
request_without_etag()
def get_response_or_cache():
# On responses:
wrong_response = response_not_modified and (we_did_not_sent_etag or returned_etag_mistmatch)
return FailedRequest # This should not happen, we only expect not modified if we have a etag cached reponse. If the cache data is not trusted we should NOT send etag always to force a refetch (unless SDKValidationDisabled).
if failedRequest or wrong_response:
if cache:
return cache
else:
return FailedRequest
trust_response = responseValidationOk or SDKValidationDisabled
if trust_response:
if not_modified:
update_cache_request_time()
# While we might trust the response if validation is disabled, we still track
# wether the cached data is valid or not, and it will be only valid if cache
# and requests are both valid
update_cache_validation_to(responseValidationOk and cacheValidationOk)
return cache
else:
store_new_response_in_cache()
update_cache_validation_to(responseValidationOk)
return response
elif cacheValidationOk:
# If we are not trusting response, prefer valid cache always
return cache
else:
# We do not trust response nor cache
if not_modified:
return cache
else:
store_new_response_in_cache()
update_cache_validation_to(validationFailed)
return response
This code will handle properly the cache and validation status. In some cases this will return responses with validation failures, but it will try its best to use valid cache over a failed validation response (unless we are using the disable validation mode, in which case uses all responses valid or not (but still tracks validation state irregardless).
Above this, you can put the enforcer layer for strict mode. Having it separate simplifies the request / cache mangling layer.
response = get_response_or_cache()
if SDKValidationStrictMode and not response.isValid:
return failedValidation
return response
There was a problem hiding this comment.
It took me a full hour write what I was thinking was going to be simple pseudocode. This request handling logic is truly complicated XDDD
Perhaps it is better to sync. Ping me if you are available.
There was a problem hiding this comment.
Oh, I see that here you will return the response as not valid if a 304 is not valid. I don't think we should do this. We should act as unable to fetch. There is a legit situation for this, imagine you are in an airport wifi that intercepts and mangles requests. It should not break your cache.
I think I still have trouble with the logic for validation being in two places.
I would rather do something like:
For validation errors (either 200, 403...), if there is cache, treat this as a 500, just return cache
That's not changed. As per my other comment, that would be handled by DeviceCache. HTTPClient doesn't return a valid response if there is a 500.
For 403 update request time in cache only if cache was checked valid and 304 was valid too (304 invalid would be covered in the above case anyway). Then return cache.
For valid 200, update cache, return fresh data
That's how it works 👍🏻
We already have a test for that and it's still passing 👍🏻 ( |
|
I think I handled all comments, but let me know if I missed something. |
|
This fixes #2470. |
### 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>
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:
HTTPResponseBody.copy(with newRequestDate:)to be able to modify responses from the header request dateCustomerInfoimplements this method to modify its request dateHTTPResponse.requestDateto be able to keep track of the request date from the serverVerificationResult.from(cache:response:)to determine the most restrictive verification result based on what's cached or checked from a responseHTTPResponse.copy(with:)to modify the verification result of a response using the previous methodHTTPClientnow uses the most restrictive verification resultHTTPClientupdates request date from server responses or cached responses (unless verification failed). This was the missing piece forEntitlementInfo: add a grace period limit to outdated entitlements #2288.Tests:
ETagManagerdoes not use an ETag if verification was previously not enabledETagManagerreturns the request date from the server when returning a cached responseHTTPClientupdates the request date from the server or from a cached responseHTTPClientdoes not update request date if verification failedHTTPResponserequest date parsingCustomerInfoResponseHandlerupdating request dateCustomerInfo.copy(with newRequestDate:)Other smaller changes:
CustomerInfo.asData()intoEncodable.asJSONEncodedData()requestTimetorequestDateeverywhere for consistency