Similar to #2486. This will be used for SDK-3181.
```
- WARN: ⚠️ Returning fake signature verification failure for 'GET /v1/subscribers/$RCAnonymousID:fc0508bea4a74a41bfc9a69db30d925f'
- ERROR: 😿‼️ Request failed signature verification. Request to /v1/subscribers/$RCAnonymousID%3Afc0508bea4a74a41bfc9a69db30d925f failed verification
```
When receiving 500 responses, `HTTPClient` was always calling
`ErrorResponse.from(_ data: Data)`, which tried to decode the content as
if it was JSON.
However, if the response had something completely different, it would
fail to decode.
This wasn't really a bug because `HTTPClient` was still forwarding the
right result, but now it will do so without even trying to parse it.
We were currently logging the status code retrieved from `ETagManager`,
which made it seem like etags weren't working.
Example:
> API request completed: GET /v1/subscribers/identify (200)
With this change, we now log the actual response status code so we can
verify the server is returning the correct status code:
> API request completed: GET /v1/subscribers/identify (304)
### Changes:
- Added `Purchases.configureInCustomEntitlementsComputationMode`
- Added `Purchases.switchUser(to:)`
- Disabled parts of the SDK in this new mode
- New example app for testing `customEntitlementsComputation` mode
---------
Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
Scenario:
- Response 1: 200 / `VerificationResult.failed`
- Response 2: 304 / `VerificationResult.verified`
Before this change, that was resulting in `VerificationResult.verified`,
even though it was using the unverified response body.
This fixes that so that we don't use the ETag for a response that failed
validation, even in `ResponseVerificationMode.informational`.
Essentially we don't want to modify any behavior on `.informational`
mode. Everything will continue behaving normally, and simply report
verification failures.
### 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
Instead of manually choosing when to include `nonce` in requests, this
data-driven approach ensures that all requests to endpoints that support
signature verification will make `HTTPClient` do the right thing.
See also #2277 for how `CustomerInfo` and `EntitlementInfo` will use
this.
Fixes [CSDK-629].
Depends on #2274 and #2267.
Entitlement verification is accomplished by preventing MiTM attacks
between the SDK and the RevenueCat server.
With verification enabled, the SDK ensures that the response created by
the server was not modified by a third-party, and the entitlements
received are exactly what was sent.
Entitlements are only verified if enabled using
`Configuration.Builder.with(entitlementVerificationMode:)`, which is
disabled by default.
### Changes:
- Added new `VerificationResult`, which replaces
`HTTPResponseVerificationResult`
- Renamed `Configuration.EntitlementVerificationLevel` to
`Configuration.EntitlementVerificationMode`
- `Configuration.with(entitlementVerificationMode:)` is now `public`
- Added `CustomerInfo.entitlementVerification`
- Added `EntitlementInfos.verification` and
`EntitlementInfo.verification`
- Added tests to cover serialization of these new values
- `CustomerInfoResponseHandler` copies the `ValidationResult` to
`CustomerInfo` (all operations that fetch `CustomerInfo` use this)
- Renamed "validation" to "verification" everywhere for consistency
- Finalized documentation
### TODO:
- [x] Figure out whether to use a separate type from
`HTTPResponseValidationResult` or rename that one
- [x] Figure out final API type and property names
- [x] Add tests for `CustomerInfo.copy(with:)`
- [x] Ensure validation result is encoded and decoded (for caching)
- [x] Add missing snapshots
[CSDK-629]:
https://revenuecats.atlassian.net/browse/CSDK-629?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Fixes [CSDK-634].
## Changes:
- Created `HTTPResponseValidationResult`
- Added `HTTPResponseValidationResult` and `HTTPClient.ResponseHeaders`
to `HTTPResponse`
- Added new `ErrorCode.signatureVerificationFailed`
- Extracted `SigningType` to create `MockSigning` to test `HTTPClient`
without bothering with signatures
- Refactored `ETagManager` to use `HTTPResponse`, making it easier to
pass through response headers and verification results
- Stored `HTTPResponseValidationResult` in `ETagManager` cache
- `HTTPClient` handles signatures from response based on
`SystemInfo.responseVerificationLevel`
- Renamed `HTTPRequest.createIntegrityEnforcedRequestRequest` to
`HTTPRequest.createWithResponseVerification`
- Added lots of tests for this new behavior in `HTTPClient`
- Added lots of new tests for `ETagManager`
[CSDK-634]:
https://revenuecats.atlassian.net/browse/CSDK-634?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
This breaks signature validation in #2267 because `PurchasesDiagnostics`
uses that endpoint with and without signature validation, so the first
time it makes a request, it stores the cached response without a
validated signature.
The best solution is to simply not send etags for this endpoint, since
it's used precisely to make sure that requests can be made with no
errors, and we wouldn't want false positives.
`DNSChecker.errorWithBlockedHostFromError` already checks
`isBlockedAPIError` when constructing this error, so we can simplify
this to avoid calling `DNSChecker.resolvedHost` twice.
Fixes [CSDK-16].
See
https://docs.google.com/document/d/1DDyed98RFpIvUwUPJcetc7X4fBhDzf-r1trC4g3zf4c/edit#
for reasoning and documentation.
### Motivation
The RevenueCat SDK has always used `UserDefaults.standard` as the
default value. The advantage of this was simplicity, however, this
brings a few challenges.
### Issues
- While `UserDefaults` is itself thread-safe, our users might be
observing `UserDefaults.defaults` changes via `KVO`,
`NotificationCenter`, or `@AppStorage` (see
https://github.com/RevenueCat/purchases-ios/issues/1527). We update
`UserDefaults` from background threads, and users might be getting
updates on those threads without handling thread-safety correctly.
- Users can accidentally delete data from the shared `UserDefaults` (see
https://rev.cat/userdefaults-crash).
Using a custom `UserDefaults` solves all these issues.
### Change
The new `UserDefaults.revenueCatSuite` is used when:
- Only if users aren’t overriding the `UserDefaults` parameter
- Only if `UserDefaults.standard` has no value for
`com.revenuecat.userdefaults.appUserID.new` (the user hasn't used the
SDK in that app before).
#### Logged during app start:
> PurchaseTester[35110:6323140] DEBUG: ℹ️ Configuring SDK using
RevenueCat's UserDefaults suite.
### Alternatives Considered
See
https://docs.google.com/document/d/1c2B4MNAFKs_DcFJ3OE_KnmryHCArBkjlZbAD9_kSXu0/edit#
We considered also migrating data for everyone into the new
`UserDefaults`.
This new approach is much simpler, less error prone, easier to
implement, and maintain.
The only downside is that existing users won’t benefit from this change,
but @aboedo correctly pointed out: if the app is working for them, no
reason to change anything.
So in this way, this becomes a purely forward-looking improvement.
[CSDK-16]:
https://revenuecats.atlassian.net/browse/CSDK-16?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
This is the last change from what was started with #1437. That PR introduced a few private error types, but for the errors returned in the public `APIs` we were still passing around `Error`s everywhere, which didn't provide any compile-time guarantees.
### Goals:
- Ensure that public APIs *only* return `ErrorCode`s wrapped with the additional `userInfo`, and not accidentally return other types like `SKError`, `BackendError`, `ErrorCode` directly, etc.
- Guarantee type-safety when passing errors around within our internal implementations instead of assuming certain thrown errors are of a particular type.
### Changes:
- Created `PurchasesError` for wrapping an `ErrorCode` with the additional `userInfo`.
- Changed returned errors from `Error` to `NSError`. This is a change in the API, but the new type is a more specific `Error` so it shouldn't lead to any changes in client apps.
- Because `PurchasesError` isn't implicitly convertible to `NSError`, this ensures that we don't return those values directly, and instead use `PurchasesError.asPublicError`.
- Changed `ErrorCodeConvertible` to a now more precise `PurchasesErrorConvertible`.
Thanks to this new type-safety this PR fixes at least 3 bugs where we were returning private errors instead of `ErrorCode`s.
### Testing:
Thanks to #1871 we know that the returned errors are still convertible to `ErrorCode` so users can `switch` over them.
Follow up to #1776. That PR added a lot of tests to cover error logging, and #1778 introduced a small error that wasn't covered: if attribute errors are empty, the `localizedDescription` could end up becoming `"[:]"`. This fixes that.
## Examples:
This was the underlying error returned (the output of `error.localizedDescription`):
#### Before:
> [:]
#### After:
> There was a credentials issue. Check the underlying error for more details.
And this is what was logged (notice that I also fixed `ErrorCode.invalidCredentialsError` to not log as an "Apple Error"):
#### Before:
> 🍎‼️There was a credentials issue. Check the underlying error for more details. [:]
#### After:
> 😿‼️ There was a credentials issue. Check the underlying error for more details.
* [CF-106] Fetch AdServices Token (#1519)
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>
* [CF-553] Post AdServices token (#1534)
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>
* Update ios 14 snapshots tets
* Reworked adservices to use new Purchases.shared.attribution API
* Updated with some comments from PR
* Add iAd code back (#1739)
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
* use new mapKeys dictionary extension for device cache
* update for some pr comments
* remove purchasestests
* generate missing snapshots
* fix deprecation messages to clarify method now called on shared instance
* Fix for inserting nonstring into userdefaults
* Add tests for bug
* fix lint
* Added availablility checks for tests and removed unused varialbes
* Whoops... wrong class
Co-authored-by: beylmk <maddie@revenuecat.com>
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>