Files

124 lines
4.6 KiB
Objective-C
Raw Permalink Normal View History

/*
* Copyright 2026 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/DebugProvider/FIRAppCheckDebugTokenConfigStore.h"
#import <TargetConditionals.h>
#import <errno.h>
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
NS_ASSUME_NONNULL_BEGIN
#if TARGET_OS_SIMULATOR
/// Environment variable set by the Simulator to the host user's home directory.
static NSString *const kSimulatorHostHomeEnvKey = @"SIMULATOR_HOST_HOME";
/// Top-level key of the config store file mapping Google App IDs to debug tokens.
static NSString *const kTokensKey = @"tokens";
#endif // TARGET_OS_SIMULATOR
@implementation FIRAppCheckDebugTokenConfigStore
+ (NSString *)relativePath {
return @".config/configstore/firebase-appcheck.json";
}
#if TARGET_OS_SIMULATOR
/// Returns whether `error`, from reading the config store file, means that the file does not exist.
///
/// Besides a missing file, this includes a path component that is a regular file rather than a
/// directory (e.g., `~/.config`), since the config store cannot exist at that path either.
+ (BOOL)isMissingConfigStoreError:(nullable NSError *)error {
if ([error.domain isEqualToString:NSCocoaErrorDomain] &&
error.code == NSFileReadNoSuchFileError) {
return YES;
}
NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (![underlyingError isKindOfClass:[NSError class]]) {
return NO;
}
return [underlyingError.domain isEqualToString:NSPOSIXErrorDomain] &&
underlyingError.code == ENOTDIR;
}
#endif // TARGET_OS_SIMULATOR
+ (nullable NSString *)debugTokenForGoogleAppID:(NSString *)googleAppID {
return [self debugTokenForGoogleAppID:googleAppID
environment:[NSProcessInfo processInfo].environment];
}
+ (nullable NSString *)debugTokenForGoogleAppID:(NSString *)googleAppID
environment:
(NSDictionary<NSString *, NSString *> *)environment {
#if TARGET_OS_SIMULATOR
NSString *hostHome = environment[kSimulatorHostHomeEnvKey];
if (hostHome.length == 0 || googleAppID.length == 0) {
return nil;
}
NSURL *fileURL = [[NSURL fileURLWithPath:hostHome
isDirectory:YES] URLByAppendingPathComponent:self.relativePath
isDirectory:NO];
NSError *readError;
NSData *data = [NSData dataWithContentsOfURL:fileURL options:0 error:&readError];
if (!data) {
// A missing file (or a path where it cannot exist) means the Firebase CLI flow is not in use.
if (![self isMissingConfigStoreError:readError]) {
FIRLogWarning(kFIRLoggerAppCheck,
kFIRLoggerAppCheckMessageCodeDebugTokenConfigStoreUnreadable,
@"Failed to read App Check debug tokens from ~/%@: %@", self.relativePath,
readError.localizedDescription);
}
return nil;
}
NSError *parseError;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (![json isKindOfClass:[NSDictionary class]]) {
FIRLogWarning(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageCodeDebugTokenConfigStoreUnreadable,
@"Failed to parse App Check debug tokens from ~/%@: %@", self.relativePath,
parseError.localizedDescription ?: @"expected a JSON object");
return nil;
}
NSDictionary *tokens = json[kTokensKey];
if (!tokens) {
// A config store without `tokens` (e.g., `{}` after the last token was removed) is valid.
return nil;
}
if (![tokens isKindOfClass:[NSDictionary class]]) {
FIRLogWarning(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageCodeDebugTokenConfigStoreUnreadable,
@"Failed to parse App Check debug tokens from ~/%@: `%@` is not a JSON object",
self.relativePath, kTokensKey);
return nil;
}
NSString *token = tokens[googleAppID];
if (![token isKindOfClass:[NSString class]] || token.length == 0) {
return nil;
}
return token;
#else
return nil;
#endif // TARGET_OS_SIMULATOR
}
@end
NS_ASSUME_NONNULL_END