feat: Only log local debug tokens when not yet registered (#95)

Co-authored-by: Nick Cooke <nickcooke@google.com>
This commit is contained in:
Andrew HeardandNick Cooke authored and GitHub committed 2026-06-10 11:30:20 -04:00
1 parent f6b4582ed1
commit bb4002485f
4 files changed
+187 -15

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- os: macos-15
xcode: Xcode_16.4
- os: macos-26
xcode: Xcode_26.1
xcode: Xcode_26.2
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@@ -36,18 +36,34 @@ static NSString *const kDebugTokenEnvKey = @"AppCheckDebugToken";
static NSString *const kFirebaseDebugTokenEnvKey = @"FIRAAppCheckDebugToken";
static NSString *const kDebugTokenUserDefaultsKey = @"GACAppCheckDebugToken";
// The base key for registration status.
// NOTE: Do not use this key directly. Use `registeredUserDefaultsKeyForServiceName:resourceName:`
// to obtain the namespaced key.
static NSString *const kDebugTokenRegisteredUserDefaultsKey = @"GACAppCheckDebugTokenRegistered";
@interface GACAppCheckDebugProvider ()
@property(nonatomic, readonly) id<GACAppCheckDebugProviderAPIServiceProtocol> APIService;
@property(nonatomic, readonly, nullable, copy) NSString *debugTokenEnvValue;
@property(nonatomic, readonly, copy) NSString *registeredUserDefaultsKey;
+ (NSString *)registeredUserDefaultsKeyForServiceName:(NSString *)serviceName
resourceName:(NSString *)resourceName;
@end
static NSString *_Nullable EnvironmentVariableDebugToken(NSString *registeredUserDefaultsKey);
@implementation GACAppCheckDebugProvider
- (instancetype)initWithAPIService:(id<GACAppCheckDebugProviderAPIServiceProtocol>)APIService {
- (instancetype)initWithAPIService:(id<GACAppCheckDebugProviderAPIServiceProtocol>)APIService
serviceName:(NSString *)serviceName
resourceName:(NSString *)resourceName {
self = [super init];
if (self) {
_APIService = APIService;
_debugTokenEnvValue = EnvironmentVariableDebugToken();
_registeredUserDefaultsKey =
[[[self class] registeredUserDefaultsKeyForServiceName:serviceName
resourceName:resourceName] copy];
_debugTokenEnvValue = EnvironmentVariableDebugToken(_registeredUserDefaultsKey);
}
return self;
}
@@ -70,7 +86,9 @@ static NSString *const kDebugTokenUserDefaultsKey = @"GACAppCheckDebugToken";
[[GACAppCheckDebugProviderAPIService alloc] initWithAPIService:APIService
resourceName:resourceName];
return [self initWithAPIService:debugAPIService];
return [self initWithAPIService:debugAPIService
serviceName:serviceName
resourceName:resourceName];
}
- (NSString *)currentDebugToken {
@@ -108,6 +126,7 @@ static NSString *const kDebugTokenUserDefaultsKey = @"GACAppCheckDebugToken";
return [self.APIService appCheckTokenWithDebugToken:debugToken limitedUse:limitedUse];
})
.then(^id(GACAppCheckToken *appCheckToken) {
[[GULUserDefaults standardUserDefaults] setBool:YES forKey:self.registeredUserDefaultsKey];
handler(appCheckToken, nil);
return nil;
})
@@ -115,6 +134,10 @@ static NSString *const kDebugTokenUserDefaultsKey = @"GACAppCheckDebugToken";
NSString *logMessage = [NSString
stringWithFormat:@"Failed to exchange debug token to app check token: %@", error];
GACAppCheckLogDebug(GACLoggerAppCheckMessageDebugProviderFailedExchange, logMessage);
if (error.code != GACAppCheckErrorCodeServerUnreachable) {
[[GULUserDefaults standardUserDefaults]
removeObjectForKey:self.registeredUserDefaultsKey];
}
handler(nil, error);
});
}
@@ -133,7 +156,19 @@ static NSString *GenerateAndStoreDebugToken(void) {
return token;
}
static NSString *_Nullable EnvironmentVariableDebugToken(void) {
+ (NSString *)registeredUserDefaultsKeyForServiceName:(NSString *)serviceName
resourceName:(NSString *)resourceName {
NSString *safeServiceName = serviceName.length > 0 ? serviceName : @"default";
NSString *safeResourceName = [resourceName stringByReplacingOccurrencesOfString:@"/"
withString:@"_"];
if (safeResourceName.length == 0) {
safeResourceName = @"default";
}
return [NSString stringWithFormat:@"%@_%@_%@", kDebugTokenRegisteredUserDefaultsKey,
safeServiceName, safeResourceName];
}
static NSString *_Nullable EnvironmentVariableDebugToken(NSString *registeredUserDefaultsKey) {
NSDictionary<NSString *, NSString *> *environment = [[NSProcessInfo processInfo] environment];
NSString *envVariableValue = environment[kDebugTokenEnvKey];
NSString *firebaseEnvVariableValue = environment[kFirebaseDebugTokenEnvKey];
@@ -175,9 +210,14 @@ static NSString *_Nullable EnvironmentVariableDebugToken(void) {
return firebaseEnvVariableValue;
} else {
// Print only a locally generated token to avoid a valid token leak on CI.
GACAppCheckLog(GACLoggerAppCheckMessageLocalDebugToken, GACAppCheckLogLevelWarning,
[NSString stringWithFormat:@"App Check debug token: '%@'.", LocalDebugToken()]);
BOOL isRegistered =
[[GULUserDefaults standardUserDefaults] boolForKey:registeredUserDefaultsKey];
if (!isRegistered) {
// Print only a locally generated token to avoid a valid token leak on CI.
GACAppCheckLog(
GACLoggerAppCheckMessageLocalDebugToken, GACAppCheckLogLevelWarning,
[NSString stringWithFormat:@"App Check debug token: '%@'.", LocalDebugToken()]);
}
return nil;
}
@@ -22,15 +22,24 @@
#import "AppCheckCore/Sources/DebugProvider/API/GACAppCheckDebugProviderAPIService.h"
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckDebugProvider.h"
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckErrors.h"
#import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
static NSString *const kDebugTokenEnvKey = @"AppCheckDebugToken";
static NSString *const kFirebaseDebugTokenEnvKey = @"FIRAAppCheckDebugToken";
static NSString *const kDebugTokenUserDefaultsKey = @"GACAppCheckDebugToken";
static NSString *const kDebugTokenRegisteredUserDefaultsKey = @"GACAppCheckDebugTokenRegistered";
@interface GACAppCheckDebugProvider (Tests)
- (instancetype)initWithAPIService:(id<GACAppCheckDebugProviderAPIServiceProtocol>)APIService;
- (instancetype)initWithAPIService:(id<GACAppCheckDebugProviderAPIServiceProtocol>)APIService
serviceName:(NSString *)serviceName
resourceName:(NSString *)resourceName;
+ (NSString *)registeredUserDefaultsKeyForServiceName:(NSString *)serviceName
resourceName:(NSString *)resourceName;
@property(nonatomic, readonly, copy) NSString *registeredUserDefaultsKey;
@end
@@ -51,7 +60,10 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
self.processInfoMock = OCMPartialMock([NSProcessInfo processInfo]);
self.fakeAPIService = OCMProtocolMock(@protocol(GACAppCheckDebugProviderAPIServiceProtocol));
self.provider = [[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService];
self.provider =
[[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService
serviceName:@"test-service"
resourceName:@"projects/test-project/apps/test-app"];
}
- (void)tearDown {
@@ -59,6 +71,8 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
[self.processInfoMock stopMocking];
self.processInfoMock = nil;
[[GULUserDefaults standardUserDefaults] removeObjectForKey:kDebugTokenUserDefaultsKey];
[[GULUserDefaults standardUserDefaults] removeObjectForKey:kDebugTokenRegisteredUserDefaultsKey];
[super tearDown];
}
#pragma mark - Debug token generating/storing
@@ -69,7 +83,10 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
NSString *envToken = @"env token";
OCMExpect([self.processInfoMock processInfo]).andReturn(self.processInfoMock);
OCMExpect([self.processInfoMock environment]).andReturn(@{kDebugTokenEnvKey : envToken});
self.provider = [[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService];
self.provider =
[[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService
serviceName:@"test-service"
resourceName:@"projects/test-project/apps/test-app"];
XCTAssertEqualObjects([self.provider currentDebugToken], envToken);
}
@@ -82,7 +99,10 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
OCMExpect([self.processInfoMock environment])
.andReturn(
(@{kDebugTokenEnvKey : envToken, kFirebaseDebugTokenEnvKey : @"firebase env token"}));
self.provider = [[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService];
self.provider =
[[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService
serviceName:@"test-service"
resourceName:@"projects/test-project/apps/test-app"];
XCTAssertEqualObjects([self.provider currentDebugToken], envToken);
}
@@ -95,7 +115,10 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
OCMExpect([self.processInfoMock environment]).andReturn((@{
kFirebaseDebugTokenEnvKey : envToken
}));
self.provider = [[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService];
self.provider =
[[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService
serviceName:@"test-service"
resourceName:@"projects/test-project/apps/test-app"];
XCTAssertEqualObjects([self.provider currentDebugToken], envToken);
}
@@ -106,7 +129,10 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
OCMExpect([self.processInfoMock environment])
.andReturn(
(@{kDebugTokenEnvKey : envToken, kFirebaseDebugTokenEnvKey : @"firebase env token"}));
self.provider = [[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService];
self.provider =
[[GACAppCheckDebugProvider alloc] initWithAPIService:self.fakeAPIService
serviceName:@"test-service"
resourceName:@"projects/test-project/apps/test-app"];
XCTAssertEqualObjects([self.provider currentDebugToken], envToken);
}
@@ -225,6 +251,111 @@ typedef void (^GACAppCheckTokenValidationBlock)(GACAppCheckToken *_Nullable toke
OCMVerifyAll(self.fakeAPIService);
}
- (void)testGetTokenSuccessSetsRegisteredFlag {
// 1. Stub API service.
NSString *expectedDebugToken = [self.provider currentDebugToken];
GACAppCheckToken *validToken = [[GACAppCheckToken alloc] initWithToken:@"valid_token"
expirationDate:[NSDate date]
receivedAtDate:[NSDate date]];
FBLPromise *resolvedPromise = [FBLPromise pendingPromise];
[resolvedPromise fulfill:validToken];
OCMExpect([self.fakeAPIService appCheckTokenWithDebugToken:expectedDebugToken limitedUse:NO])
.andReturn(resolvedPromise);
[[GULUserDefaults standardUserDefaults]
removeObjectForKey:self.provider.registeredUserDefaultsKey];
// 2. Validate get token.
[self validateGetToken:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
XCTAssertNil(error);
XCTAssertNotNil(token);
}];
// 3. Verify flag is now YES.
XCTAssertTrue(
[[GULUserDefaults standardUserDefaults] boolForKey:self.provider.registeredUserDefaultsKey]);
// 4. Verify fakes.
OCMVerifyAll(self.fakeAPIService);
}
- (void)testGetTokenPermanentFailureClearsRegisteredFlag {
// 1. Stub API service.
NSString *expectedDebugToken = [self.provider currentDebugToken];
NSError *APIError = [NSError errorWithDomain:@"testGetTokenPermanentFailureClearsRegisteredFlag"
code:-1
userInfo:nil];
FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
[rejectedPromise reject:APIError];
OCMExpect([self.fakeAPIService appCheckTokenWithDebugToken:expectedDebugToken limitedUse:NO])
.andReturn(rejectedPromise);
// Pre-populate flag to YES.
[[GULUserDefaults standardUserDefaults] setBool:YES
forKey:self.provider.registeredUserDefaultsKey];
// 2. Validate get token.
[self validateGetToken:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
XCTAssertNotNil(error);
XCTAssertNil(token);
}];
// 3. Verify flag is cleared.
XCTAssertNil([[GULUserDefaults standardUserDefaults]
objectForKey:self.provider.registeredUserDefaultsKey]);
// 4. Verify fakes.
OCMVerifyAll(self.fakeAPIService);
}
- (void)testGetTokenNetworkFailureDoesNotClearRegisteredFlag {
// 1. Stub API service.
NSString *expectedDebugToken = [self.provider currentDebugToken];
NSError *networkError = [NSError errorWithDomain:GACAppCheckErrorDomain
code:GACAppCheckErrorCodeServerUnreachable
userInfo:nil];
FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
[rejectedPromise reject:networkError];
OCMExpect([self.fakeAPIService appCheckTokenWithDebugToken:expectedDebugToken limitedUse:NO])
.andReturn(rejectedPromise);
// Pre-populate flag to YES.
[[GULUserDefaults standardUserDefaults] setBool:YES
forKey:self.provider.registeredUserDefaultsKey];
// 2. Validate get token.
[self validateGetToken:^(GACAppCheckToken *_Nullable token, NSError *_Nullable error) {
XCTAssertNotNil(error);
XCTAssertNil(token);
}];
// 3. Verify flag is still YES.
XCTAssertTrue(
[[GULUserDefaults standardUserDefaults] boolForKey:self.provider.registeredUserDefaultsKey]);
// 4. Verify fakes.
OCMVerifyAll(self.fakeAPIService);
}
#pragma mark - Keys
- (void)testRegisteredUserDefaultsKeyForServiceName_resourceName {
XCTAssertEqualObjects(
[GACAppCheckDebugProvider registeredUserDefaultsKeyForServiceName:@"app1"
resourceName:@"projects/p1/apps/a1"],
@"GACAppCheckDebugTokenRegistered_app1_projects_p1_apps_a1");
XCTAssertEqualObjects(
[GACAppCheckDebugProvider registeredUserDefaultsKeyForServiceName:@"app2"
resourceName:@"projects/p2/apps/a2"],
@"GACAppCheckDebugTokenRegistered_app2_projects_p2_apps_a2");
XCTAssertEqualObjects([GACAppCheckDebugProvider registeredUserDefaultsKeyForServiceName:@""
resourceName:@""],
@"GACAppCheckDebugTokenRegistered_default_default");
XCTAssertEqualObjects([GACAppCheckDebugProvider registeredUserDefaultsKeyForServiceName:nil
resourceName:nil],
@"GACAppCheckDebugTokenRegistered_default_default");
}
#pragma mark - Helpers
- (void)validateGetToken:(GACAppCheckTokenValidationBlock)validationBlock {
+2 -1
View File
@@ -1,5 +1,6 @@
# 11.3.0
- [changed] Added Recaptcha Enterprise attestation provider.
- [changed] Added Recaptcha Enterprise attestation provider. (#94)
- [changed] Only log local debug tokens when not yet registered. (#95)
# 11.2.0
- [changed] To prevent reusing expired artifacts, skip local cache when making