Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions FirebaseAppCheck/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# v8.9.0 -- M106
- [fixed] Improved error handling logic. (#8798)

# v8.8.0 -- M105
- [added] Add support for bundle ID-based API Key Restrictions (#8678)

Expand Down
21 changes: 18 additions & 3 deletions FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h"
#import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h"
#import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h"
#import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h"
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h"

#import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h"
Expand Down Expand Up @@ -107,6 +108,7 @@ @interface FIRAppAttestProvider ()
@property(nonatomic, readonly) id<FIRAppAttestService> appAttestService;
@property(nonatomic, readonly) id<FIRAppAttestKeyIDStorageProtocol> keyIDStorage;
@property(nonatomic, readonly) id<FIRAppAttestArtifactStorageProtocol> artifactStorage;
@property(nonatomic, readonly) id<FIRAppCheckBackoffWrapperProtocol> backoffWrapper;

@property(nonatomic, nullable) FBLPromise<FIRAppCheckToken *> *ongoingGetTokenOperation;

Expand All @@ -119,13 +121,15 @@ @implementation FIRAppAttestProvider
- (instancetype)initWithAppAttestService:(id<FIRAppAttestService>)appAttestService
APIService:(id<FIRAppAttestAPIServiceProtocol>)APIService
keyIDStorage:(id<FIRAppAttestKeyIDStorageProtocol>)keyIDStorage
artifactStorage:(id<FIRAppAttestArtifactStorageProtocol>)artifactStorage {
artifactStorage:(id<FIRAppAttestArtifactStorageProtocol>)artifactStorage
backoffWrapper:(id<FIRAppCheckBackoffWrapperProtocol>)backoffWrapper {
self = [super init];
if (self) {
_appAttestService = appAttestService;
_APIService = APIService;
_keyIDStorage = keyIDStorage;
_artifactStorage = artifactStorage;
_backoffWrapper = backoffWrapper;
_queue = dispatch_queue_create("com.firebase.FIRAppAttestProvider", DISPATCH_QUEUE_SERIAL);
}
return self;
Expand Down Expand Up @@ -155,10 +159,13 @@ - (nullable instancetype)initWithApp:(FIRApp *)app {
appID:app.options.googleAppID
accessGroup:app.options.appGroupID];

FIRAppCheckBackoffWrapper *backoffWrapper = [[FIRAppCheckBackoffWrapper alloc] init];

return [self initWithAppAttestService:DCAppAttestService.sharedService
APIService:appAttestAPIService
keyIDStorage:keyIDStorage
artifactStorage:artifactStorage];
artifactStorage:artifactStorage
backoffWrapper:backoffWrapper];
#else // FIR_APP_ATTEST_SUPPORTED_TARGETS
return nil;
#endif // FIR_APP_ATTEST_SUPPORTED_TARGETS
Expand All @@ -185,7 +192,7 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
// Kick off a new handshake sequence only when there is not an ongoing
// handshake to avoid race conditions.
self.ongoingGetTokenOperation =
[self createGetTokenSequencePromise]
[self createGetTokenSequenceWithBackoffPromise]

// Release the ongoing operation promise on completion.
.then(^FIRAppCheckToken *(FIRAppCheckToken *token) {
Expand All @@ -201,6 +208,14 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
}];
}

- (FBLPromise<FIRAppCheckToken *> *)createGetTokenSequenceWithBackoffPromise {
return [self.backoffWrapper
applyBackoffToOperation:^FBLPromise *_Nonnull {
return [self createGetTokenSequencePromise];
}
errorHandler:[self.backoffWrapper defaultAppCheckProviderErrorHandler]];
}

- (FBLPromise<FIRAppCheckToken *> *)createGetTokenSequencePromise {
// Check attestation state to decide on the next steps.
return [self attestationState].thenOn(self.queue, ^id(FIRAppAttestProviderState *attestState) {
Expand Down
87 changes: 87 additions & 0 deletions FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <Foundation/Foundation.h>

@class FBLPromise<ValueType>;

NS_ASSUME_NONNULL_BEGIN

/// Backoff type. Backoff interval calculation depends on the type.
typedef NS_ENUM(NSUInteger, FIRAppCheckBackoffType) {
/// No backoff. Another retry is allowed straight away.
FIRAppCheckBackoffTypeNone,

/// Next retry will be allowed in 1 day (24 hours) after the failure.
FIRAppCheckBackoffType1Day,

/// A small backoff interval that exponentially increases after each consequent failure.
FIRAppCheckBackoffTypeExponential
};

/// Creates a promise for an operation to apply the backoff to.
typedef FBLPromise *_Nonnull (^FIRAppCheckBackoffOperationProvider)(void);

/// Converts an error to a backoff type.
typedef FIRAppCheckBackoffType (^FIRAppCheckBackoffErrorHandler)(NSError *error);

/// A block returning a date. Is used instead of `+[NSDate date]` for better testability of logic
/// dependent on the current time.
typedef NSDate *_Nonnull (^FIRAppCheckDateProvider)(void);

/// Defines API for an object that conditionally applies backoff to a given operation based on the
/// history of previous operation failures.
@protocol FIRAppCheckBackoffWrapperProtocol <NSObject>

/// Conditionally applies backoff to the given operation.
/// @param operationProvider A block that returns a new promise. The block will be called only when
/// the operation is allowed.
/// NOTE: We cannot accept just a promise because the operation will be started once the
/// promise has been instantiated, so we need to have a way to instantiate the promise only
/// when the operation is good to go. The provider block is the way we use.
/// @param errorHandler A block that receives an operation error as an input and returns the
/// appropriate backoff type. `defaultErrorHandler` provides a default implementation for Firebase
/// services.
/// @return A promise that is either:
/// - a promise returned by the promise provider if no backoff is required
/// - rejected if the backoff is needed
- (FBLPromise *)applyBackoffToOperation:(FIRAppCheckBackoffOperationProvider)operationProvider
errorHandler:(FIRAppCheckBackoffErrorHandler)errorHandler;

/// The default Firebase services error handler. It keeps track of network errors and
/// `FIRAppCheckHTTPError.HTTPResponse.statusCode.statusCode` value to return the appropriate
/// backoff type for the standard Firebase App Check backend response codes.
- (FIRAppCheckBackoffErrorHandler)defaultAppCheckProviderErrorHandler;

@end

/// Provides a backoff implementation. Keeps track of the operation successes and failures to either
/// create and perform the operation promise or fails with a backoff error when the backoff is
/// needed.
@interface FIRAppCheckBackoffWrapper : NSObject <FIRAppCheckBackoffWrapperProtocol>

/// Initializes the wrapper with `+[FIRAppCheckBackoffWrapper currentDateProvider]`.
- (instancetype)init;

- (instancetype)initWithDateProvider:(FIRAppCheckDateProvider)dateProvider
NS_DESIGNATED_INITIALIZER;

/// A date provider that returns `+[NSDate date]`.
+ (FIRAppCheckDateProvider)currentDateProvider;

@end

NS_ASSUME_NONNULL_END
Loading