Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement FIRAppCheck as a shim of GACAppCheck #11721

Merged
merged 15 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
2 changes: 1 addition & 1 deletion FirebaseAppCheck.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Pod::Spec.new do |s|
s.tvos.weak_framework = 'DeviceCheck'

# TODO(andrewheard): Update version number before merging into `master`.
s.dependency 'AppCheckCore', '~> 0.1.0-alpha'
s.dependency 'AppCheckCore', '0.1.0-alpha.9'

s.dependency 'FirebaseAppCheckInterop', '~> 10.16'
s.dependency 'FirebaseCore', '~> 10.0'
Expand Down
13 changes: 13 additions & 0 deletions FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
}];
}

- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable,
NSError *_Nullable))handler {
[self.appAttestProvider getLimitedUseTokenWithCompletion:^(
GACAppCheckToken *_Nullable internalToken, NSError *_Nullable error) {
if (error) {
handler(nil, error);
return;
}

handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil);
}];
}

@end

NS_ASSUME_NONNULL_END
244 changes: 69 additions & 175 deletions FirebaseAppCheck/Sources/Core/FIRAppCheck.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,9 @@

#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h"

@import AppCheckCore;
@import FirebaseAppCheckInterop;

#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif

#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckErrors.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProviderFactory.h"
Expand All @@ -34,9 +29,7 @@
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h"
#import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h"
#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h"
#import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h"
#import "FirebaseAppCheck/Sources/Core/FIRInternalAppCheckProvider.h"

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -55,22 +48,16 @@

static id<FIRAppCheckProviderFactory> _providerFactory;

static const NSTimeInterval kTokenExpirationThreshold = 5 * 60; // 5 min.

static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";

@interface FIRAppCheck () <FIRAppCheckInterop>
@interface FIRAppCheck () <FIRAppCheckInterop, GACAppCheckTokenDelegate>
@property(class, nullable) id<FIRAppCheckProviderFactory> providerFactory;

@property(nonatomic, readonly) NSString *appName;
@property(nonatomic, readonly) id<FIRAppCheckProvider> appCheckProvider;
@property(nonatomic, readonly) id<FIRAppCheckStorageProtocol> storage;
@property(nonatomic, readonly) NSNotificationCenter *notificationCenter;
@property(nonatomic, readonly) FIRAppCheckSettings *settings;
@property(nonatomic, readonly) GACAppCheck *appCheckCore;

@property(nonatomic, readonly, nullable) id<FIRAppCheckTokenRefresherProtocol> tokenRefresher;

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

@implementation FIRAppCheck
Expand Down Expand Up @@ -98,47 +85,40 @@ - (nullable instancetype)initWithApp:(FIRApp *)app {
return nil;
}

NSString *serviceName = [self serviceNameForApp:app];
NSString *resourceName = [self resourceNameForApp:app];
id<GACAppCheckProvider> appCheckCoreProvider =
[[FIRInternalAppCheckProvider alloc] initWithAppCheckProvider:appCheckProvider];
FIRAppCheckSettings *settings =
[[FIRAppCheckSettings alloc] initWithApp:app
userDefault:[NSUserDefaults standardUserDefaults]
mainBundle:[NSBundle mainBundle]];
FIRAppCheckTokenRefreshResult *refreshResult =
[[FIRAppCheckTokenRefreshResult alloc] initWithStatusNever];
FIRAppCheckTokenRefresher *tokenRefresher =
[[FIRAppCheckTokenRefresher alloc] initWithRefreshResult:refreshResult settings:settings];

FIRAppCheckStorage *storage = [[FIRAppCheckStorage alloc] initWithAppName:app.name
appID:app.options.googleAppID
accessGroup:app.options.appGroupID];
GACAppCheck *appCheckCore = [[GACAppCheck alloc] initWithServiceName:serviceName
resourceName:resourceName
appCheckProvider:appCheckCoreProvider
settings:settings
tokenDelegate:self
keychainAccessGroup:app.options.appGroupID];

return [self initWithAppName:app.name
appCheckCore:appCheckCore
appCheckProvider:appCheckProvider
storage:storage
tokenRefresher:tokenRefresher
notificationCenter:NSNotificationCenter.defaultCenter
settings:settings];
}

- (instancetype)initWithAppName:(NSString *)appName
appCheckCore:(GACAppCheck *)appCheckCore
appCheckProvider:(id<FIRAppCheckProvider>)appCheckProvider
storage:(id<FIRAppCheckStorageProtocol>)storage
tokenRefresher:(id<FIRAppCheckTokenRefresherProtocol>)tokenRefresher
notificationCenter:(NSNotificationCenter *)notificationCenter
settings:(FIRAppCheckSettings *)settings {
self = [super init];
if (self) {
_appName = appName;
_appCheckProvider = appCheckProvider;
_storage = storage;
_tokenRefresher = tokenRefresher;
_appCheckCore = appCheckCore;
_notificationCenter = notificationCenter;
_settings = settings;

__auto_type __weak weakSelf = self;
tokenRefresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
__auto_type strongSelf = weakSelf;
[strongSelf periodicTokenRefreshWithCompletion:completion];
};
}
return self;
}
Expand Down Expand Up @@ -166,26 +146,28 @@ + (nullable instancetype)appCheckWithApp:(FIRApp *)firebaseApp {
- (void)tokenForcingRefresh:(BOOL)forcingRefresh
completion:(void (^)(FIRAppCheckToken *_Nullable token,
NSError *_Nullable error))handler {
[self retrieveOrRefreshTokenForcingRefresh:forcingRefresh]
.then(^id _Nullable(FIRAppCheckToken *token) {
handler(token, nil);
return token;
})
.catch(^(NSError *_Nonnull error) {
handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:error]);
});
[self.appCheckCore
tokenForcingRefresh:forcingRefresh
completion:^(GACAppCheckTokenResult *result) {
if (result.error) {
handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:result.error]);
return;
}

handler([[FIRAppCheckToken alloc] initWithInternalToken:result.token], nil);
}];
}

- (void)limitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
NSError *_Nullable error))handler {
[self limitedUseToken]
.then(^id _Nullable(FIRAppCheckToken *token) {
handler(token, nil);
return token;
})
.catch(^(NSError *_Nonnull error) {
handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:error]);
});
[self.appCheckCore limitedUseTokenWithCompletion:^(GACAppCheckTokenResult *result) {
if (result.error) {
handler(nil, [FIRAppCheckErrorUtil publicDomainErrorWithError:result.error]);
return;
}

handler([[FIRAppCheckToken alloc] initWithInternalToken:result.token], nil);
}];
}

+ (void)setAppCheckProviderFactory:(nullable id<FIRAppCheckProviderFactory>)factory {
Expand Down Expand Up @@ -218,33 +200,27 @@ + (void)setProviderFactory:(nullable id<FIRAppCheckProviderFactory>)providerFact

- (void)getTokenForcingRefresh:(BOOL)forcingRefresh
completion:(FIRAppCheckTokenHandlerInterop)handler {
[self retrieveOrRefreshTokenForcingRefresh:forcingRefresh]
.then(^id _Nullable(FIRAppCheckToken *token) {
FIRAppCheckTokenResult *result = [[FIRAppCheckTokenResult alloc] initWithToken:token.token
error:nil];
handler(result);
return result;
})
.catch(^(NSError *_Nonnull error) {
FIRAppCheckTokenResult *result =
[[FIRAppCheckTokenResult alloc] initWithToken:kDummyFACTokenValue error:error];
handler(result);
});
[self.appCheckCore
tokenForcingRefresh:forcingRefresh
completion:^(GACAppCheckTokenResult *internalResult) {
FIRAppCheckToken *token =
[[FIRAppCheckToken alloc] initWithInternalToken:internalResult.token];
FIRAppCheckTokenResult *tokenResult =
[[FIRAppCheckTokenResult alloc] initWithToken:token.token
error:internalResult.error];

handler(tokenResult);
}];
}

- (void)getLimitedUseTokenWithCompletion:(FIRAppCheckTokenHandlerInterop)handler {
[self limitedUseToken]
.then(^id _Nullable(FIRAppCheckToken *token) {
FIRAppCheckTokenResult *result = [[FIRAppCheckTokenResult alloc] initWithToken:token.token
error:nil];
handler(result);
return result;
})
.catch(^(NSError *_Nonnull error) {
FIRAppCheckTokenResult *result =
[[FIRAppCheckTokenResult alloc] initWithToken:kDummyFACTokenValue error:error];
handler(result);
});
[self.appCheckCore limitedUseTokenWithCompletion:^(GACAppCheckTokenResult *internalResult) {
FIRAppCheckToken *token = [[FIRAppCheckToken alloc] initWithInternalToken:internalResult.token];
FIRAppCheckTokenResult *tokenResult =
[[FIRAppCheckTokenResult alloc] initWithToken:token.token error:internalResult.error];

handler(tokenResult);
}];
}

- (nonnull NSString *)tokenDidChangeNotificationName {
Expand All @@ -259,105 +235,12 @@ - (nonnull NSString *)notificationTokenKey {
return kFIRAppCheckTokenNotificationKey;
}

#pragma mark - FAA token cache

- (FBLPromise<FIRAppCheckToken *> *)retrieveOrRefreshTokenForcingRefresh:(BOOL)forcingRefresh {
return [FBLPromise do:^id _Nullable {
if (self.ongoingRetrieveOrRefreshTokenPromise == nil) {
// Kick off a new operation only when there is not an ongoing one.
self.ongoingRetrieveOrRefreshTokenPromise =
[self createRetrieveOrRefreshTokenPromiseForcingRefresh:forcingRefresh]

// Release the ongoing operation promise on completion.
.then(^FIRAppCheckToken *(FIRAppCheckToken *token) {
self.ongoingRetrieveOrRefreshTokenPromise = nil;
return token;
})
.recover(^NSError *(NSError *error) {
self.ongoingRetrieveOrRefreshTokenPromise = nil;
return error;
});
}
return self.ongoingRetrieveOrRefreshTokenPromise;
}];
}
#pragma mark - GACAppCheckTokenDelegate

- (FBLPromise<FIRAppCheckToken *> *)createRetrieveOrRefreshTokenPromiseForcingRefresh:
(BOOL)forcingRefresh {
return [self getCachedValidTokenForcingRefresh:forcingRefresh].recover(
^id _Nullable(NSError *_Nonnull error) {
return [self refreshToken];
});
}

- (FBLPromise<FIRAppCheckToken *> *)getCachedValidTokenForcingRefresh:(BOOL)forcingRefresh {
if (forcingRefresh) {
FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
[rejectedPromise reject:[FIRAppCheckErrorUtil cachedTokenNotFound]];
return rejectedPromise;
}

return [self.storage getToken].then(^id(FIRAppCheckToken *_Nullable token) {
if (token == nil) {
return [FIRAppCheckErrorUtil cachedTokenNotFound];
}

BOOL isTokenExpiredOrExpiresSoon =
[token.expirationDate timeIntervalSinceNow] < kTokenExpirationThreshold;
if (isTokenExpiredOrExpiresSoon) {
return [FIRAppCheckErrorUtil cachedTokenExpired];
}

return token;
});
}

- (FBLPromise<FIRAppCheckToken *> *)refreshToken {
return [FBLPromise
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appCheckProvider getTokenWithCompletion:handler];
}]
.then(^id _Nullable(FIRAppCheckToken *_Nullable token) {
return [self.storage setToken:token];
})
.then(^id _Nullable(FIRAppCheckToken *_Nullable token) {
// TODO: Make sure the self.tokenRefresher is updated only once. Currently the timer will be
// updated twice in the case when the refresh triggered by self.tokenRefresher, but it
// should be fine for now as it is a relatively cheap operation.
__auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
initWithStatusSuccessAndExpirationDate:token.expirationDate
receivedAtDate:token.receivedAtDate];
[self.tokenRefresher updateWithRefreshResult:refreshResult];
[self postTokenUpdateNotificationWithToken:token];
return token;
});
}

- (FBLPromise<FIRAppCheckToken *> *)limitedUseToken {
return
[FBLPromise wrapObjectOrErrorCompletion:^(
FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appCheckProvider getTokenWithCompletion:handler];
}].then(^id _Nullable(FIRAppCheckToken *_Nullable token) {
return token;
});
}

#pragma mark - Token auto refresh

- (void)periodicTokenRefreshWithCompletion:(FIRAppCheckTokenRefreshCompletion)completion {
[self retrieveOrRefreshTokenForcingRefresh:NO]
.then(^id _Nullable(FIRAppCheckToken *_Nullable token) {
__auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
initWithStatusSuccessAndExpirationDate:token.expirationDate
receivedAtDate:token.receivedAtDate];
completion(refreshResult);
return nil;
})
.catch(^(NSError *error) {
__auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc] initWithStatusFailure];
completion(refreshResult);
});
- (void)tokenDidUpdate:(nonnull GACAppCheckToken *)token
serviceName:(nonnull NSString *)serviceName {
FIRAppCheckToken *appCheckToken = [[FIRAppCheckToken alloc] initWithInternalToken:token];
[self postTokenUpdateNotificationWithToken:appCheckToken];
}

#pragma mark - Token update notification
Expand All @@ -371,6 +254,17 @@ - (void)postTokenUpdateNotificationWithToken:(FIRAppCheckToken *)token {
}];
}

#pragma mark - Helpers

- (NSString *)serviceNameForApp:(FIRApp *)app {
return [NSString stringWithFormat:@"FirebaseApp:%@", app.name];
}

- (NSString *)resourceNameForApp:(FIRApp *)app {
return [NSString
stringWithFormat:@"projects/%@/apps/%@", app.options.projectID, app.options.googleAppID];
}

@end

NS_ASSUME_NONNULL_END
2 changes: 2 additions & 0 deletions FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN
/// @param token The internal App Check token to be converted into a Firebase App Check token.
- (instancetype)initWithInternalToken:(GACAppCheckToken *)token;

- (GACAppCheckToken *)internalToken;

@end

NS_ASSUME_NONNULL_END
6 changes: 6 additions & 0 deletions FirebaseAppCheck/Sources/Core/FIRAppCheckToken.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ - (instancetype)initWithInternalToken:(GACAppCheckToken *)token {
receivedAtDate:token.receivedAtDate];
}

- (GACAppCheckToken *)internalToken {
return [[GACAppCheckToken alloc] initWithToken:self.token
expirationDate:self.expirationDate
receivedAtDate:self.receivedAtDate];
}

@end

NS_ASSUME_NONNULL_END
Loading
Loading