Skip to content

Commit 2ec4dbd

Browse files
App Check: optimize auto-refresh (#8232)
* App Attest provider: attestation sequence (#7971) * App Attest provider: attestation sequence (#761) * App Attest draft WIP * FIRAppAttestProvider initializers * ./scripts/style.sh * FIRAppAttestProvider implementation draft * Basic FIRAppAttestProviderTests and fixes * style * testGetTokenWhenAppAttestIsNotSupported * More FIRAppAttestProviderTests * Cleanup * Remove unused file * Availability annotations on DCAppAttestService category. * Guard FIRAppAttestProvider with #if TARGET_OS_IOS * Formatting * Fix SPM * app_check.yaml: Add diagnostics SPM builds * fix yaml * Fix Firebase-Package scheme bad merge * Fix typo * FIRAppAttestProvider: hide default init * FIRAppAttestKeyIDStorage: methods placeholders * Comments * Fix updated block definition * Implement app attest key ID storage (#8014) * Implement FIRAppAttestKeyIDStorage * Add FIRAppAttestKeyIDStorageTests * Review [Draft] * Style * Docs updates * Docs updates 2 * Review [Draft] 2 * Improve tests * Improve test readability * Improve test readability 2 * App Check App Attest workflow updates: initial handshake (#8032) * Handshake adjustments (WIP) * Introduce FIRAppAttestProviderState * WIP: calculate attestation state * WIP: calculate attestation state 2 * formatting * Comments and moving code around * Fix init in tests * Fix state calculation flow * Cleanup state calculation and fix tests. * Cleanup and fixes. * Comments * formatting * Fix import * Typo fixes and additional comments * FIRAppAttestInitialHandshakeResponse API * Cleanup state calculation using FBLPromiseAwait * Cleanup * style * FIRAppAttestArtifactStorage implementation and tests (#8041) * Update comments * FIRAppAttestArtifactStorage implementation and tests * Fix init * API docs * Clean up storage in tests * Comments * Disable Keychain dependent tests for SPM * Implement App Attest `getRandomChallenge` (#8033) * Initial implementation * Parse response body for challenge and stub test cases * Review [Draft] * Avoid encoding challenge again * Add tests * Revert "Avoid encoding challenge again" and add TODO This reverts commit 69eb00d. * Document tests; Add test * Tests: Add URL validation check * Review * Define Exchange AppAttest Assertion for FAC token API (#8058) * App Check App Attest: attestation request (#8059) * App Attest provider API integration WIP * update tests * Draft attestation response parsing * Attestation request draft * style * AppAttest Attestation API tests draft * Error cases tests * style * Cleanup and API docs * Merge fix * Fix OCMock imports * Fix nullability modifier * Formatting * comments * App Check App Attest initial handshake adjustments (#8067) * calculatre sha256 of random challenge for attestation * Test app adjustments * cleanup * use trailing closures in the test app * Implement API for ExchangeAppAttestAssertionRequest endpoint (#8065) * Implement assertion exchange * Tweak existing tests * Add tests * Rename JSON to better match gRPC message * Add HTTPBody helper * Review * Review 2 * Review 3 * App Check App Attest assertion flow (#8083) * App Attest assertion workflow draft * send request * assertion flow tests * style * App Check: store App Attest artifact per key ID (#8097) * Update artifact storage API and tests * Artifact storage implementation update * Save artifact for a key ID * Style * typos * App Check: prevent concurrent token requests (#8117) * App Attest multiple get token method invocation tests * Ensure a single App Attest handshake sequence at the time * FIRAppCheckTests: get token request merging tests * FIRAppCheck: Ensure a single get token operation at the time * formatting * Test new request after merged requests * Release finished operation promise * Style * Typos * typo * Request merging tests for error cases * formatting * Changelog * App Check App Attest: handle attestation rejection (#8170) * Remove/update outdated TODOs * [WIP] Attestation rejection handling draft * style * retry tests draft * reset key ID before retry * Reset attestation * test error and fixes * style * More details in the name * Some debug logging * style * Use specific codes for log messages * style * Add FIRAppAttestProvider.h the umbrella header * Add receivedAtDate property to the FAC token * Update tests to check receivedAtDate field where important * [WIP] Use FIRAppCheckTokenRefreshResult instead in the refresher API * [WIP] Fix refgresher usage * Update tests with new API, keep old logic * Update tests with new logic * WIP * WIP * Initial refresh tests and fixes. * Add #import <TargetConditionals.h> where needed. * Formatting * Cleanup and test updates * style * Comments * Cleanup and comments * Fix catalyst * Changelog * Changelog version fix * Typo * Cleanup * Typo * Imports order Co-authored-by: Nick Cooke <[email protected]>
1 parent b58ea53 commit 2ec4dbd

23 files changed

+524
-180
lines changed

FirebaseAppCheck/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# 8.1.0 -- M97
1+
# 8.2.0 -- M98
22
- [added] Apple's App Attest attestation provider support. (#8133)
3+
- [changed] Token auto-refresh optimizations. (#8232)
34
# v8.0.0 -- M95
45
- [added] Firebase abuse reduction support SDK. (#7928, #7937, #7948)

FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
#import <TargetConditionals.h>
18+
1719
// Currently DCAppAttestService is available on iOS only.
1820
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST // Catalyst should be possible with Xcode 12.5+
1921

FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#import "FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h"
1818

1919
// Currently DCAppAttestService is available on iOS only.
20-
#if TARGET_OS_IOS
20+
#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST // Catalyst should be possible with Xcode 12.5+
2121

2222
@implementation DCAppAttestService (FIRAppAttestService)
2323

FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckToken+APIResponse.h"
18+
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
1819

1920
#if __has_include(<FBLPromises/FBLPromises.h>)
2021
#import <FBLPromises/FBLPromises.h>
@@ -81,7 +82,7 @@ - (nullable instancetype)initWithResponseDict:(NSDictionary<NSString *, id> *)re
8182

8283
NSDate *expirationDate = [requestDate dateByAddingTimeInterval:secondsToLive];
8384

84-
return [self initWithToken:token expirationDate:expirationDate];
85+
return [self initWithToken:token expirationDate:expirationDate receivedAtDate:requestDate];
8586
}
8687

8788
@end

FirebaseAppCheck/Sources/Core/FIRAppCheck.m

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424

2525
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h"
2626
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProviderFactory.h"
27-
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h"
2827

2928
#import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h"
3029
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h"
3130
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
31+
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
3232
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h"
3333
#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h"
34+
#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h"
3435
#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h"
3536

3637
#import "FirebaseAppCheck/Sources/Interop/FIRAppCheckInterop.h"
@@ -124,10 +125,10 @@ - (nullable instancetype)initWithApp:(FIRApp *)app {
124125
[[FIRAppCheckSettings alloc] initWithApp:app
125126
userDefault:[NSUserDefaults standardUserDefaults]
126127
mainBundle:[NSBundle mainBundle]];
128+
FIRAppCheckTokenRefreshResult *refreshResult =
129+
[[FIRAppCheckTokenRefreshResult alloc] initWithStatusNever];
127130
FIRAppCheckTokenRefresher *tokenRefresher =
128-
[[FIRAppCheckTokenRefresher alloc] initWithTokenExpirationDate:[NSDate date]
129-
tokenExpirationThreshold:kTokenExpirationThreshold
130-
settings:settings];
131+
[[FIRAppCheckTokenRefresher alloc] initWithRefreshResult:refreshResult settings:settings];
131132

132133
FIRAppCheckStorage *storage = [[FIRAppCheckStorage alloc] initWithAppName:app.name
133134
appID:app.options.googleAppID
@@ -306,7 +307,10 @@ - (nonnull NSString *)notificationTokenKey {
306307
// TODO: Make sure the self.tokenRefresher is updated only once. Currently the timer will be
307308
// updated twice in the case when the refresh triggered by self.tokenRefresher, but it
308309
// should be fine for now as it is a relatively cheap operation.
309-
[self.tokenRefresher updateTokenExpirationDate:token.expirationDate];
310+
__auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
311+
initWithStatusSuccessAndExpirationDate:token.expirationDate
312+
receivedAtDate:token.receivedAtDate];
313+
[self.tokenRefresher updateWithRefreshResult:refreshResult];
310314
[self postTokenUpdateNotificationWithToken:token];
311315
return token;
312316
});
@@ -317,11 +321,15 @@ - (nonnull NSString *)notificationTokenKey {
317321
- (void)periodicTokenRefreshWithCompletion:(FIRAppCheckTokenRefreshCompletion)completion {
318322
[self retrieveOrRefreshTokenForcingRefresh:NO]
319323
.then(^id _Nullable(FIRAppCheckToken *_Nullable token) {
320-
completion(YES, token.expirationDate);
324+
__auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
325+
initWithStatusSuccessAndExpirationDate:token.expirationDate
326+
receivedAtDate:token.receivedAtDate];
327+
completion(refreshResult);
321328
return nil;
322329
})
323330
.catch(^(NSError *error) {
324-
completion(NO, nil);
331+
__auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] initWithStatusFailure];
332+
completion(refreshResult);
325333
});
326334
}
327335

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h"
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
@interface FIRAppCheckToken ()
24+
25+
/// A date when the Firebase App Check token was received in the device's local time.
26+
@property(nonatomic) NSDate *receivedAtDate;
27+
28+
/// The designated initializer.
29+
/// @param token A Firebase App Check token.
30+
/// @param expirationDate A Firebase App Check token expiration date in the device local time.
31+
/// @param receivedAtDate A date when the Firebase App Check token was received in the device's
32+
/// local time.
33+
- (instancetype)initWithToken:(NSString *)token
34+
expirationDate:(NSDate *)expirationDate
35+
receivedAtDate:(NSDate *)receivedAtDate NS_DESIGNATED_INITIALIZER;
36+
37+
@end
38+
39+
NS_ASSUME_NONNULL_END

FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,28 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h"
17+
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
18+
19+
NS_ASSUME_NONNULL_BEGIN
1820

1921
@implementation FIRAppCheckToken
2022

21-
- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate {
23+
- (instancetype)initWithToken:(NSString *)token
24+
expirationDate:(NSDate *)expirationDate
25+
receivedAtDate:(NSDate *)receivedAtDate {
2226
self = [super init];
2327
if (self) {
2428
_token = [token copy];
2529
_expirationDate = expirationDate;
30+
_receivedAtDate = receivedAtDate;
2631
}
2732
return self;
2833
}
2934

35+
- (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate {
36+
return [self initWithToken:token expirationDate:expirationDate receivedAtDate:[NSDate date]];
37+
}
38+
3039
@end
40+
41+
NS_ASSUME_NONNULL_END

FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.m

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@
1616

1717
#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken+FIRAppCheckToken.h"
1818

19-
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h"
19+
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
2020

2121
@implementation FIRAppCheckStoredToken (FIRAppCheckToken)
2222

2323
- (void)updateWithToken:(FIRAppCheckToken *)token {
2424
self.token = token.token;
2525
self.expirationDate = token.expirationDate;
26+
self.receivedAtDate = token.receivedAtDate;
2627
}
2728

2829
- (FIRAppCheckToken *)appCheckToken {
29-
return [[FIRAppCheckToken alloc] initWithToken:self.token expirationDate:self.expirationDate];
30+
return [[FIRAppCheckToken alloc] initWithToken:self.token
31+
expirationDate:self.expirationDate
32+
receivedAtDate:self.receivedAtDate];
3033
}
3134

3235
@end

FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ NS_ASSUME_NONNULL_BEGIN
2222

2323
@interface FIRAppCheckStoredToken : NSObject <NSSecureCoding>
2424

25-
/// FAA token.
26-
@property(nonatomic, copy) NSString *token;
27-
/// FAA token expiration date in the device local time.
28-
@property(nonatomic, strong) NSDate *expirationDate;
25+
/// The Firebase App Check token.
26+
@property(nonatomic, copy, nullable) NSString *token;
27+
28+
/// The Firebase App Check token expiration date in the device local time.
29+
@property(nonatomic, strong, nullable) NSDate *expirationDate;
30+
31+
/// The date when the Firebase App Check token was received in the device's local time.
32+
@property(nonatomic, strong, nullable) NSDate *receivedAtDate;
2933

3034
/// The version of local storage.
3135
@property(nonatomic, readonly) NSInteger storageVersion;

FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStoredToken.m

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818

1919
static NSString *const kTokenKey = @"token";
2020
static NSString *const kExpirationDateKey = @"expirationDate";
21+
static NSString *const kReceivedAtDateKey = @"receivedAtDate";
2122
static NSString *const kStorageVersionKey = @"storageVersion";
2223

23-
static const NSInteger kStorageVersion = 1;
24+
static const NSInteger kStorageVersion = 2;
2425

2526
NS_ASSUME_NONNULL_BEGIN
2627

@@ -37,19 +38,21 @@ + (BOOL)supportsSecureCoding {
3738
- (void)encodeWithCoder:(NSCoder *)coder {
3839
[coder encodeObject:self.token forKey:kTokenKey];
3940
[coder encodeObject:self.expirationDate forKey:kExpirationDateKey];
41+
[coder encodeObject:self.receivedAtDate forKey:kReceivedAtDateKey];
4042
[coder encodeInteger:self.storageVersion forKey:kStorageVersionKey];
4143
}
4244

4345
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
4446
self = [super init];
4547
if (self) {
46-
NSInteger storageVersion = [coder decodeIntegerForKey:kStorageVersionKey];
47-
if (storageVersion > kStorageVersion) {
48+
NSInteger decodedStorageVersion = [coder decodeIntegerForKey:kStorageVersionKey];
49+
if (decodedStorageVersion > kStorageVersion) {
4850
// TODO: Log a message.
4951
}
5052

5153
_token = [coder decodeObjectOfClass:[NSString class] forKey:kTokenKey];
5254
_expirationDate = [coder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey];
55+
_receivedAtDate = [coder decodeObjectOfClass:[NSDate class] forKey:kReceivedAtDateKey];
5356
}
5457
return self;
5558
}

0 commit comments

Comments
 (0)