Commit Graph
25 Commits
Author SHA1 Message Date
NachoSoto da9ba397a3 Update CustomerInfo.requestDate from 304 responses (#2310)
### 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
2023-03-01 09:24:32 -08:00
NachoSoto b707f8abc8 Signing: added request time & eTag to signature verification (#2309)
See https://github.com/RevenueCat/khepri/pull/5300
2023-02-27 09:07:23 -08:00
NachoSoto c5f963e4fa HTTPClient: changed header search to be case-insensitive (#2308) 2023-02-22 11:30:20 -08:00
NachoSoto 457ba021d9 HTTPClient: automatically add nonce based on HTTPRequest.Path (#2286)
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.
2023-02-22 09:20:25 -08:00
NachoSoto 84eed758c1 Introduced Configuration.EntitlementVerificationMode and VerificationResult (#2277)
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
2023-02-17 16:49:21 -08:00
NachoSoto dbe93dbbf4 PurchasesDiagnostics: added step to verify signature verification (#2267)
⚠️ 🎉 This also changes integration tests to use
`EntitlementVerificationLevel.enforced` so that integration tests fail
if signatures are invalid.

#### Depends on:
- https://github.com/RevenueCat/khepri/pull/5191
- https://github.com/RevenueCat/khepri/pull/5204
- #2214
- #2215 
- #2216
- #2272

_Marking this as `feat`ure because it contains a new error in
`PurchasesDiagnostics.Error`_
2023-02-16 10:27:04 -07:00
NachoSoto f05aabd176 HTTPClient: added signature validation and introduced ErrorCode.signatureVerificationFailed (#2272)
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
2023-02-16 03:06:59 -08:00
NachoSoto 1b2f405db1 HTTPRequest.Path.health: don't cache using ETagManager (#2278)
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.
2023-02-13 07:02:18 -08:00
NachoSoto 88a78411f6 HTTPClient: added support for sending X-Nonce (#2214)
Fixes [CSDK-631].


[CSDK-631]:
https://revenuecats.atlassian.net/browse/CSDK-631?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2023-02-08 09:05:20 -08:00
NachoSoto 2a73f2f735 DNSChecker: simplified NetworkError initialization (#2166)
`DNSChecker.errorWithBlockedHostFromError` already checks
`isBlockedAPIError` when constructing this error, so we can simplify
this to avoid calling `DNSChecker.resolvedHost` twice.
2022-12-21 08:25:23 -08:00
NachoSoto 587f65bdd3 DNSChecker: added log with resolved host (#2167) 2022-12-20 07:31:02 -08:00
NachoSoto dc41f03232 HTTPClient: replaced X-StoreKit2-Setting with X-StoreKit2-Enabled (#2118)
This will be used for https://github.com/RevenueCat/khepri/pull/4843, to
fix [SDKONCALL-160] and #2020.

`X-StoreKit2-Setting` only told us if the apps had that setting enabled.
However, when running on an older device, `SK2` would still be disabled.

[SDKONCALL-160]:
https://revenuecats.atlassian.net/browse/SDKONCALL-160?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-12-09 10:39:09 -08:00
NachoSoto bdebc8591e Exposed SystemInfo.observerMode to simplify code (#2064) 2022-11-23 11:22:29 -06:00
NachoSoto ecce3ff982 Changed default UserDefaults from .standard to our own Suite (#2046)
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
2022-11-22 11:32:09 -08:00
NachoSoto 7dee94974f HTTPClient: added log for failed requests (#2048) 2022-11-14 09:15:25 -08:00
NachoSoto 66fee52cd9 ErrorResponse.asBackendError: serialize attribute errors as NSDictionary (#2034) 2022-11-11 08:05:04 -08:00
NachoSoto 74c29936b8 ErrorCode.unknownBackendError: include original error code (#2032) 2022-11-11 08:02:28 -08:00
NachoSoto f06a8ce647 Created InternalAPI for "health" request (#1971) 2022-10-20 13:03:15 -05:00
NachoSoto 5ea158fad8 HTTPClient: added X-Client-Bundle-ID and logged on SDK initialization (#1883) 2022-09-05 15:10:44 -07:00
NachoSoto d4ea390970 Introduced type-safe PurchasesError errors and fixed some incorrect returned error types (#1879)
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.
2022-09-05 13:46:07 -07:00
NachoSoto 0bff3f65fe Sendable support (#1795)
Fixes [CSDK-379] and https://github.com/RevenueCat/purchases-ios/issues/1041#issuecomment-1206445939

[CSDK-379]: https://revenuecats.atlassian.net/browse/CSDK-379?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

### Main changes:
- `Sendable` conformances to all types that are thread-safe
- `@unchecked Sendable` for types that the compiler can't enforce, but with documentation as to why
- Made classes that aren't mocked `final` (note that this doesn't change the API, because none of these were `open` to begin with)
- Made some classes thread-safe that weren't (like `DeviceCache`)

For the non-`final` `class`es, I've actually managed to compile the SDK with all of them as `final` and `Sendable`, to make sure that making them `@unchecked Sendable` didn't hide any other issues.

### Future improvements:
- https://twitter.com/nachosoto/status/1557139992056500224?s=21&t=arWdvEzTIFANBvwqQ0vPiA
- https://twitter.com/nachosoto/status/1557141944777592832?s=21&t=arWdvEzTIFANBvwqQ0vPiA
- https://twitter.com/nachosoto/status/1557143374922059776?s=21&t=arWdvEzTIFANBvwqQ0vPiA

### Depends on:
- #1794
- #1804
- #1806
- #1807
- #1808
- #1813
- #1822
- #1823
- #1824
- #1825
- #1826
2022-08-31 13:30:38 -07:00
NachoSoto 56246ae101 ErrorResponse: don't add attribute errors to message if empty (#1844)
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.
2022-08-17 11:26:10 -07:00
343ba7eaf4 AdServices (#1727)
* [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>
2022-08-08 07:42:42 -05:00
NachoSoto e58a00b4be CustomerInfoResponseHandler: return CustomerInfo instead of error if the response was successful (#1778)
Fixes [SDKONCALL-62].
The behavior in version 3.x was that we would return a `CustomerInfo` AND the attribute errors. [The test in that version](https://github.com/RevenueCat/purchases-ios/blob/3.14.2/PurchasesTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift#L370-L404) however only verified that the error was returned.

When we converted `(Value, Error)` callbacks to using `Result` (#1387), I assumed this was an error. Because of that incomplete test, we didn't notice this wasn't the desired behavior.

If the backend returns a 200, it's assumed that the errors aren't more important than the correct `CustomerInfo`.

The new implementation simply logs the attribute errors (which is now tested), and returns the `CustomerInfo`:
```
😿‼️ One or more of the attributes sent could not be saved. ["$email": "email is not in valid format"]
```

[SDKONCALL-62]: https://revenuecats.atlassian.net/browse/SDKONCALL-62?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-07-29 10:04:26 -07:00
Joshua Liebowitz 858bef3f9b Extract Offerings-related things into OfferingsAPI (#1706)
Extract Offerings-related things into OfferingsAPI
2022-06-16 14:00:40 -07:00