2021-04-21 21:01:11 -04:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright 2020 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 "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h"
|
|
|
|
|
|
|
2026-09-23 16:22:44 -07:00
|
|
|
|
#if __has_include(<AppCheckCore/AppCheckCore-Swift.h>)
|
|
|
|
|
|
#import <AppCheckCore/AppCheckCore-Swift.h>
|
|
|
|
|
|
#elif __has_include("AppCheckCore-Swift.h")
|
|
|
|
|
|
#import "AppCheckCore-Swift.h"
|
|
|
|
|
|
#else
|
|
|
|
|
|
@import AppCheckCore;
|
|
|
|
|
|
#endif
|
2023-11-07 18:54:45 -05:00
|
|
|
|
|
|
|
|
|
|
#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h"
|
2021-04-21 21:01:11 -04:00
|
|
|
|
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h"
|
2023-11-07 18:54:45 -05:00
|
|
|
|
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h"
|
2021-04-21 21:01:11 -04:00
|
|
|
|
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h"
|
2023-11-07 18:54:45 -05:00
|
|
|
|
#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h"
|
2026-11-06 19:23:51 -05:00
|
|
|
|
#import "FirebaseAppCheck/Sources/DebugProvider/FIRAppCheckDebugTokenConfigStore.h"
|
2021-04-21 21:01:11 -04:00
|
|
|
|
|
2022-03-22 13:36:33 -04:00
|
|
|
|
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
2021-04-21 21:01:11 -04:00
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
@interface FIRAppCheckDebugProvider ()
|
2023-11-07 18:54:45 -05:00
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, readonly) GACAppCheckDebugProvider *debugProvider;
|
2026-06-04 20:05:33 -04:00
|
|
|
|
@property(nonatomic, readonly, copy, nullable) NSString *projectID;
|
|
|
|
|
|
@property(nonatomic, readonly, copy) NSString *googleAppID;
|
2026-11-06 19:23:51 -05:00
|
|
|
|
/// Where `debugProvider` got the debug token it exchanges.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The only token `-initWithApp:` passes to `debugProvider` is one read from the Firebase CLI
|
|
|
|
|
|
/// config store on the host machine, so `GACAppCheckDebugTokenSourceProvided` means that token is
|
|
|
|
|
|
/// in use.
|
|
|
|
|
|
@property(nonatomic, readonly) GACAppCheckDebugTokenSource debugTokenSource;
|
2023-11-07 18:54:45 -05:00
|
|
|
|
|
2021-04-21 21:01:11 -04:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation FIRAppCheckDebugProvider
|
|
|
|
|
|
|
2026-06-04 20:05:33 -04:00
|
|
|
|
- (instancetype)initWithDebugProvider:(GACAppCheckDebugProvider *)debugProvider
|
|
|
|
|
|
projectID:(nullable NSString *)projectID
|
|
|
|
|
|
googleAppID:(NSString *)googleAppID {
|
2021-04-21 21:01:11 -04:00
|
|
|
|
self = [super init];
|
|
|
|
|
|
if (self) {
|
2023-11-07 18:54:45 -05:00
|
|
|
|
_debugProvider = debugProvider;
|
2026-06-04 20:05:33 -04:00
|
|
|
|
_projectID = [projectID copy];
|
|
|
|
|
|
_googleAppID = [googleAppID copy];
|
2026-11-06 19:23:51 -05:00
|
|
|
|
_debugTokenSource = debugProvider.debugTokenSource;
|
2026-08-07 01:10:19 +00:00
|
|
|
|
|
2026-11-06 19:23:51 -05:00
|
|
|
|
switch (_debugTokenSource) { // No `default:` so `-Wswitch` flags new AppCheckCore sources.
|
|
|
|
|
|
case GACAppCheckDebugTokenSourceLocal:
|
|
|
|
|
|
[self logDebugTokenMessageWithMessageCode:kFIRLoggerAppCheckMessageCodeDebugToken
|
|
|
|
|
|
prefix:@""];
|
|
|
|
|
|
break;
|
|
|
|
|
|
case GACAppCheckDebugTokenSourceProvided:
|
|
|
|
|
|
FIRLogNotice(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageCodeDebugTokenFromConfigStore,
|
|
|
|
|
|
@"Debug token loaded from ~/%@.",
|
|
|
|
|
|
FIRAppCheckDebugTokenConfigStore.relativePath);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case GACAppCheckDebugTokenSourceEnvironmentVariable:
|
|
|
|
|
|
// App Check Core logs that the environment variable is used; the local token is unused.
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2021-04-21 21:01:11 -04:00
|
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (nullable instancetype)initWithApp:(FIRApp *)app {
|
|
|
|
|
|
NSArray<NSString *> *missingOptionsFields =
|
|
|
|
|
|
[FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options];
|
|
|
|
|
|
if (missingOptionsFields.count > 0) {
|
2021-06-01 10:47:57 -04:00
|
|
|
|
FIRLogError(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageDebugProviderIncompleteFIROptions,
|
2021-04-21 21:01:11 -04:00
|
|
|
|
@"Cannot instantiate `FIRAppCheckDebugProvider` for app: %@. The following "
|
|
|
|
|
|
@"`FirebaseOptions` fields are missing: %@",
|
|
|
|
|
|
app.name, [missingOptionsFields componentsJoinedByString:@", "]);
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-06 19:23:51 -05:00
|
|
|
|
// In a Simulator, use the debug token provisioned by the Firebase CLI on the host machine (if
|
|
|
|
|
|
// any) instead of generating a new one that would need to be registered manually.
|
|
|
|
|
|
NSString *configStoreDebugToken =
|
|
|
|
|
|
[FIRAppCheckDebugTokenConfigStore debugTokenForGoogleAppID:app.options.googleAppID];
|
|
|
|
|
|
|
2023-11-07 18:54:45 -05:00
|
|
|
|
GACAppCheckDebugProvider *debugProvider =
|
|
|
|
|
|
[[GACAppCheckDebugProvider alloc] initWithServiceName:app.name
|
|
|
|
|
|
resourceName:app.resourceName
|
|
|
|
|
|
baseURL:nil
|
|
|
|
|
|
APIKey:app.options.APIKey
|
2026-11-06 19:23:51 -05:00
|
|
|
|
requestHooks:@[ [app.heartbeatLogger requestHook] ]
|
|
|
|
|
|
debugToken:configStoreDebugToken];
|
2021-04-21 21:01:11 -04:00
|
|
|
|
|
2026-06-04 20:05:33 -04:00
|
|
|
|
return [self initWithDebugProvider:debugProvider
|
|
|
|
|
|
projectID:app.options.projectID
|
|
|
|
|
|
googleAppID:app.options.googleAppID];
|
2021-04-21 21:01:11 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)currentDebugToken {
|
2023-11-07 18:54:45 -05:00
|
|
|
|
return [self.debugProvider currentDebugToken];
|
2021-04-21 21:01:11 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)localDebugToken {
|
2023-11-07 18:54:45 -05:00
|
|
|
|
return [self.debugProvider localDebugToken];
|
2021-04-21 21:01:11 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 20:05:33 -04:00
|
|
|
|
#pragma mark - Private Helpers
|
|
|
|
|
|
|
2026-08-07 01:10:19 +00:00
|
|
|
|
- (void)logDebugTokenMessageWithMessageCode:(NSString *)messageCode prefix:(NSString *)prefix {
|
2026-06-04 20:05:33 -04:00
|
|
|
|
if (self.projectID.length > 0 && self.googleAppID.length > 0) {
|
2026-08-07 01:10:19 +00:00
|
|
|
|
NSString *debugToken = self.localDebugToken;
|
|
|
|
|
|
if (!debugToken) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FIRLogWarning(
|
|
|
|
|
|
kFIRLoggerAppCheck, messageCode,
|
|
|
|
|
|
@"%@"
|
|
|
|
|
|
"To use this token for app debugging, register it with your project.\n\n"
|
|
|
|
|
|
"Firebase App Check debug token: %@\n\n"
|
|
|
|
|
|
"You can do so in the Firebase Console: \n"
|
|
|
|
|
|
"https://console.firebase.google.com/project/%@/appcheck/apps?selectedAppId=%@ \n\n"
|
|
|
|
|
|
"Or using the Firebase CLI: \n"
|
|
|
|
|
|
"firebase appcheck:debugtokens:create %@ --project %@ --app %@\n\n"
|
|
|
|
|
|
"Note: To keep your project secure, please revoke and delete this token using the \n"
|
|
|
|
|
|
"Firebase Console or the CLI (`firebase appcheck:debugtokens:delete`) when you finish "
|
|
|
|
|
|
"debugging.\n\n"
|
|
|
|
|
|
"Warning: This debug token is a secret and should not be shared or uploaded to source "
|
|
|
|
|
|
"code.\n\n"
|
|
|
|
|
|
"Debug Token Guide: https://firebase.google.com/docs/app-check/ios/debug-provider\n"
|
|
|
|
|
|
"Firebase CLI install instructions: https://firebase.google.com/docs/cli\n",
|
|
|
|
|
|
prefix, debugToken, self.projectID, self.googleAppID, debugToken, self.projectID,
|
|
|
|
|
|
self.googleAppID);
|
2026-06-04 20:05:33 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-06 19:23:51 -05:00
|
|
|
|
- (void)logDebugTokenExchangeError:(NSError *)error {
|
|
|
|
|
|
if (self.debugTokenSource == GACAppCheckDebugTokenSourceLocal) {
|
|
|
|
|
|
[self logDebugTokenMessageWithMessageCode:kFIRLoggerAppCheckMessageCodeDebugTokenExchangeFailed
|
|
|
|
|
|
prefix:@"Failed to exchange debug token. "];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSString *message = [self debugTokenExchangeFailureMessageForError:error];
|
|
|
|
|
|
if (!message) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
NSString *messageCode = self.debugTokenSource == GACAppCheckDebugTokenSourceProvided
|
|
|
|
|
|
? kFIRLoggerAppCheckMessageCodeConfigStoreDebugTokenExchangeFailed
|
|
|
|
|
|
: kFIRLoggerAppCheckMessageCodeDebugTokenExchangeFailed;
|
|
|
|
|
|
FIRLogWarning(kFIRLoggerAppCheck, messageCode, @"%@", message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the message to log when exchanging an environment variable or config store debug token
|
|
|
|
|
|
/// fails, or `nil` when there is nothing actionable to report (e.g., the server was unreachable).
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Unlike the local token's registration instructions, these messages never include the token.
|
|
|
|
|
|
- (nullable NSString *)debugTokenExchangeFailureMessageForError:(NSError *)error {
|
|
|
|
|
|
if ([error.domain isEqualToString:GACAppCheckErrors.errorDomain] &&
|
|
|
|
|
|
error.code == GACAppCheckErrorCodeServerUnreachable) {
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSString *projectArgument = self.projectID.length > 0
|
|
|
|
|
|
? [NSString stringWithFormat:@" --project %@", self.projectID]
|
|
|
|
|
|
: @"";
|
|
|
|
|
|
// App Check Core errors describe the failure in `NSLocalizedFailureReasonErrorKey`; using it
|
|
|
|
|
|
// directly avoids Foundation's generic "The operation couldn’t be completed." prefix.
|
|
|
|
|
|
NSString *errorDescription = error.localizedFailureReason ?: error.localizedDescription;
|
|
|
|
|
|
if (self.debugTokenSource == GACAppCheckDebugTokenSourceProvided) {
|
|
|
|
|
|
// The config store is only read at launch, so a new token requires relaunching the app.
|
|
|
|
|
|
return [NSString stringWithFormat:@"Failed to exchange the debug token loaded from ~/%@: %@\n\n"
|
|
|
|
|
|
@"If the token was rejected by the server, run "
|
|
|
|
|
|
@"`firebase appcheck:debugtokens:create --force%@ --app %@` "
|
|
|
|
|
|
@"to provision a new token, then relaunch the app.",
|
|
|
|
|
|
FIRAppCheckDebugTokenConfigStore.relativePath,
|
|
|
|
|
|
errorDescription, projectArgument, self.googleAppID];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (self.debugTokenSource == GACAppCheckDebugTokenSourceEnvironmentVariable) {
|
|
|
|
|
|
return [NSString
|
|
|
|
|
|
stringWithFormat:@"Failed to exchange the debug token from the `AppCheckDebugToken` or "
|
|
|
|
|
|
@"`FIRAAppCheckDebugToken` environment variable: %@\n\n"
|
|
|
|
|
|
@"Make sure the token is registered for app %@, for example with "
|
|
|
|
|
|
@"`firebase appcheck:debugtokens:create <token>%@ --app %@`.",
|
|
|
|
|
|
errorDescription, self.googleAppID, projectArgument, self.googleAppID];
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil;
|
2026-08-07 01:10:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-21 21:01:11 -04:00
|
|
|
|
#pragma mark - FIRAppCheckProvider
|
|
|
|
|
|
|
|
|
|
|
|
- (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
|
|
|
|
|
|
NSError *_Nullable error))handler {
|
2023-11-07 18:54:45 -05:00
|
|
|
|
[self.debugProvider getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
2026-11-06 19:23:51 -05:00
|
|
|
|
[self logDebugTokenExchangeError:error];
|
2023-11-07 18:54:45 -05:00
|
|
|
|
handler(nil, error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable,
|
|
|
|
|
|
NSError *_Nullable))handler {
|
|
|
|
|
|
[self.debugProvider getLimitedUseTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken,
|
|
|
|
|
|
NSError *_Nullable error) {
|
|
|
|
|
|
if (error) {
|
2026-11-06 19:23:51 -05:00
|
|
|
|
[self logDebugTokenExchangeError:error];
|
2023-11-07 18:54:45 -05:00
|
|
|
|
handler(nil, error);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil);
|
|
|
|
|
|
}];
|
2021-04-21 21:01:11 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|