From 4be9ef3def1a721ad695d6a2806847527a0819d0 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 15:03:48 -0400 Subject: [PATCH 01/12] feat(AppCheck): implement FIRRecaptchaEnterpriseProvider and tests --- .../Sources/Core/FIRAppCheckLogger.h | 4 + .../Sources/Core/FIRAppCheckLogger.m | 4 + .../FIRRecaptchaEnterpriseProvider.h | 16 ++ .../FIRRecaptchaEnterpriseProviderFactory.h | 13 ++ .../FIRRecaptchaEnterpriseProvider.m | 102 +++++++++++ .../FIRRecaptchaEnterpriseProviderFactory.m | 46 +++++ ...RRecaptchaEnterpriseProviderFactoryTests.m | 44 +++++ .../FIRRecaptchaEnterpriseProviderTests.m | 161 ++++++++++++++++++ Package.swift | 3 +- 9 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h create mode 100644 FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h create mode 100644 FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m create mode 100644 FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m create mode 100644 FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m create mode 100644 FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h index 3f2fce59da5..9f479b1c7f7 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h @@ -35,6 +35,10 @@ FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDebugToken; // FIRDeviceCheckProvider.m FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions; +// FIRRecaptchaEnterpriseProvider.m +FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions; + + void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...); GACAppCheckLogLevel FIRGetGACAppCheckLogLevel(void); diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m index 3c001b22326..e683bdca606 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m @@ -35,6 +35,10 @@ // FIRDeviceCheckProvider.m NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions = @"I-FAA006001"; +// FIRRecaptchaEnterpriseProvider.m +NSString *const kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions = @"I-FAA007001"; + + #pragma mark - Log functions void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...) { diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h new file mode 100644 index 00000000000..c3bada74d6b --- /dev/null +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h @@ -0,0 +1,16 @@ +#import +#import "FIRAppCheckAvailability.h" +#import "FIRAppCheckProvider.h" + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(RecaptchaEnterpriseProvider) +@interface FIRRecaptchaEnterpriseProvider : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey; + +@end +NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h new file mode 100644 index 00000000000..e2ac9a3ac6f --- /dev/null +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h @@ -0,0 +1,13 @@ +#import +#import "FIRAppCheckProviderFactory.h" + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(RecaptchaEnterpriseProviderFactory) +@interface FIRRecaptchaEnterpriseProviderFactory : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithSiteKey:(NSString *)siteKey; + +@end +NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m new file mode 100644 index 00000000000..6b51a6f9922 --- /dev/null +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m @@ -0,0 +1,102 @@ +/* + * 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/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h" + +#import + +@import RecaptchaEnterpriseProvider; + +#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h" +#import "FirebaseAppCheck/Sources/Core/FIRHeartbeatLogger+AppCheck.h" + +#import "FirebaseCore/Extension/FirebaseCoreInternal.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRRecaptchaEnterpriseProvider () + +@property(nonatomic, readonly) GACRecaptchaEnterpriseProvider *recaptchaEnterpriseProvider; + +@end + +@implementation FIRRecaptchaEnterpriseProvider + +- (instancetype)initWithRecaptchaEnterpriseProvider:(GACRecaptchaEnterpriseProvider *)recaptchaEnterpriseProvider { + self = [super init]; + if (self) { + _recaptchaEnterpriseProvider = recaptchaEnterpriseProvider; + } + return self; +} + +- (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey { + NSArray *missingOptionsFields = + [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options]; + if (missingOptionsFields.count > 0) { + FIRLogError(kFIRLoggerAppCheck, + kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions, + @"Cannot instantiate `FIRRecaptchaEnterpriseProvider` for app: %@. The following " + @"`FirebaseOptions` fields are missing: %@", + app.name, [missingOptionsFields componentsJoinedByString:@", "]); + return nil; + } + + GACRecaptchaEnterpriseProvider *recaptchaEnterpriseProvider = + [[GACRecaptchaEnterpriseProvider alloc] initWithSiteKey:siteKey + resourceName:app.resourceName + APIKey:app.options.APIKey + requestHooks:@[ [app.heartbeatLogger requestHook] ]]; + + return [self initWithRecaptchaEnterpriseProvider:recaptchaEnterpriseProvider]; +} + +#pragma mark - FIRAppCheckProvider + +- (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, + NSError *_Nullable error))handler { + [self.recaptchaEnterpriseProvider getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; +} + +- (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, + NSError *_Nullable))handler { + [self.recaptchaEnterpriseProvider + getLimitedUseTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m new file mode 100644 index 00000000000..3dfcf15e195 --- /dev/null +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m @@ -0,0 +1,46 @@ +/* + * 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/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h" + +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRRecaptchaEnterpriseProviderFactory () + +@property(nonatomic, readonly) NSString *siteKey; + +@end + +@implementation FIRRecaptchaEnterpriseProviderFactory + +- (instancetype)initWithSiteKey:(NSString *)siteKey { + self = [super init]; + if (self) { + _siteKey = [siteKey copy]; + } + return self; +} + +- (nullable id)createProviderWithApp:(nonnull FIRApp *)app { + return [[FIRRecaptchaEnterpriseProvider alloc] initWithApp:app siteKey:self.siteKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m new file mode 100644 index 00000000000..591aca40af1 --- /dev/null +++ b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m @@ -0,0 +1,44 @@ +/* + * 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 + +#import +#import + +#import "FirebaseCore/Extension/FirebaseCoreInternal.h" + +@interface FIRRecaptchaEnterpriseProviderFactoryTests : XCTestCase +@end + +@implementation FIRRecaptchaEnterpriseProviderFactoryTests + +- (void)testCreateProviderWithApp { + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; + options.APIKey = @"api_key"; + options.projectID = @"project_id"; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testCreateProviderWithApp" options:options]; + app.dataCollectionDefaultEnabled = NO; + + NSString *siteKey = @"test_site_key"; + FIRRecaptchaEnterpriseProviderFactory *factory = [[FIRRecaptchaEnterpriseProviderFactory alloc] initWithSiteKey:siteKey]; + + FIRRecaptchaEnterpriseProvider *createdProvider = [factory createProviderWithApp:app]; + + XCTAssert([createdProvider isKindOfClass:[FIRRecaptchaEnterpriseProvider class]]); +} + +@end diff --git a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m new file mode 100644 index 00000000000..f92cd844a9c --- /dev/null +++ b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m @@ -0,0 +1,161 @@ +/* + * 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 +#import + +#import +@import RecaptchaEnterpriseProvider; + +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" +#import + +#import "FirebaseCore/Extension/FirebaseCoreInternal.h" + +static NSString *const kAppName = @"test_app_name"; +static NSString *const kAppID = @"test_app_id"; +static NSString *const kAPIKey = @"test_api_key"; +static NSString *const kProjectID = @"test_project_id"; +static NSString *const kProjectNumber = @"123456789"; +static NSString *const kSiteKey = @"test_site_key"; + +@interface FIRRecaptchaEnterpriseProvider (Tests) + +- (instancetype)initWithRecaptchaEnterpriseProvider:(GACRecaptchaEnterpriseProvider *)recaptchaEnterpriseProvider; + +@end + +@interface FIRRecaptchaEnterpriseProviderTests : XCTestCase + +@property(nonatomic, copy) NSString *resourceName; +@property(nonatomic) id recaptchaEnterpriseProviderMock; +@property(nonatomic) FIRRecaptchaEnterpriseProvider *provider; + +@end + +@implementation FIRRecaptchaEnterpriseProviderTests + +- (void)setUp { + [super setUp]; + + self.resourceName = [NSString stringWithFormat:@"projects/%@/apps/%@", kProjectID, kAppID]; + self.recaptchaEnterpriseProviderMock = OCMStrictClassMock([GACRecaptchaEnterpriseProvider class]); + self.provider = + [[FIRRecaptchaEnterpriseProvider alloc] initWithRecaptchaEnterpriseProvider:self.recaptchaEnterpriseProviderMock]; +} + +- (void)tearDown { + self.provider = nil; + [self.recaptchaEnterpriseProviderMock stopMocking]; + self.recaptchaEnterpriseProviderMock = nil; + [super tearDown]; +} + +- (void)testInitWithValidApp { + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.APIKey = kAPIKey; + options.projectID = kProjectID; + FIRApp *app = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + app.dataCollectionDefaultEnabled = NO; + + XCTAssertNotNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:app siteKey:kSiteKey]); +} + +- (void)testInitWithIncompleteApp { + FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kAppID GCMSenderID:kProjectNumber]; + options.projectID = kProjectID; + FIRApp *missingAPIKeyApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + missingAPIKeyApp.dataCollectionDefaultEnabled = NO; + + XCTAssertNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:missingAPIKeyApp siteKey:kSiteKey]); + + options.projectID = nil; + options.APIKey = kAPIKey; + FIRApp *missingProjectIDApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; + missingProjectIDApp.dataCollectionDefaultEnabled = NO; + XCTAssertNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:missingProjectIDApp siteKey:kSiteKey]); +} + +- (void)testGetTokenSuccess { + GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"valid_token" + expirationDate:[NSDate date] + receivedAtDate:[NSDate date]]; + OCMExpect([self.recaptchaEnterpriseProviderMock + getTokenWithCompletion:([OCMArg + invokeBlockWithArgs:validInternalToken, [NSNull null], nil])]); + + [self.provider + getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { + XCTAssertEqualObjects(token.token, validInternalToken.token); + XCTAssertEqualObjects(token.expirationDate, validInternalToken.expirationDate); + XCTAssertEqualObjects(token.receivedAtDate, validInternalToken.receivedAtDate); + XCTAssertNil(error); + }]; + + OCMVerifyAll(self.recaptchaEnterpriseProviderMock); +} + +- (void)testGetTokenAPIError { + NSError *expectedError = [NSError errorWithDomain:@"testGetTokenAPIError" code:-1 userInfo:nil]; + OCMExpect([self.recaptchaEnterpriseProviderMock + getTokenWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], expectedError, nil])]); + + [self.provider + getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) { + XCTAssertNil(token); + XCTAssertEqualObjects(error, expectedError); + }]; + + OCMVerifyAll(self.recaptchaEnterpriseProviderMock); +} + +- (void)testGetLimitedUseTokenSuccess { + GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"TEST_ValidToken" + expirationDate:[NSDate date] + receivedAtDate:[NSDate date]]; + OCMExpect([self.recaptchaEnterpriseProviderMock + getLimitedUseTokenWithCompletion:([OCMArg invokeBlockWithArgs:validInternalToken, + [NSNull null], nil])]); + + [self.provider getLimitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, + NSError *_Nullable error) { + XCTAssertEqualObjects(token.token, validInternalToken.token); + XCTAssertEqualObjects(token.expirationDate, validInternalToken.expirationDate); + XCTAssertEqualObjects(token.receivedAtDate, validInternalToken.receivedAtDate); + XCTAssertNil(error); + }]; + + OCMVerifyAll(self.recaptchaEnterpriseProviderMock); +} + +- (void)testGetLimitedUseTokenProviderError { + NSError *expectedError = [NSError errorWithDomain:@"TEST_LimitedUseToken_Error" + code:-1 + userInfo:nil]; + OCMExpect([self.recaptchaEnterpriseProviderMock + getLimitedUseTokenWithCompletion:([OCMArg invokeBlockWithArgs:[NSNull null], expectedError, + nil])]); + + [self.provider getLimitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, + NSError *_Nullable error) { + XCTAssertNil(token); + XCTAssertIdentical(error, expectedError); + }]; + + OCMVerifyAll(self.recaptchaEnterpriseProviderMock); +} + +@end diff --git a/Package.swift b/Package.swift index de53606344b..a82e36e4a26 100644 --- a/Package.swift +++ b/Package.swift @@ -172,8 +172,7 @@ let package = Package( url: "https://github.com/google/interop-ios-for-google-sdks.git", "101.0.0" ..< "102.0.0" ), - .package(url: "https://github.com/google/app-check.git", - "11.0.1" ..< "12.0.0"), + .package(path: "/Users/nickcooke/Developer/app-check"), ], targets: [ .target( From 6b4f4c4140d8e7049a20b3c3518971a5710b41a5 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 15:40:53 -0400 Subject: [PATCH 02/12] style, changelog, nits --- FirebaseAppCheck/CHANGELOG.md | 3 ++ .../Sources/Core/FIRAppCheckLogger.h | 4 +- .../Sources/Core/FIRAppCheckLogger.m | 4 +- .../FIRRecaptchaEnterpriseProvider.h | 16 ++++++++ .../FIRRecaptchaEnterpriseProviderFactory.h | 16 ++++++++ .../FIRRecaptchaEnterpriseProvider.m | 37 +++++++++---------- .../FIRRecaptchaEnterpriseProviderFactory.m | 4 -- Package.swift | 2 +- 8 files changed, 58 insertions(+), 28 deletions(-) diff --git a/FirebaseAppCheck/CHANGELOG.md b/FirebaseAppCheck/CHANGELOG.md index 7cd810bfbc5..66d2eeeb4d7 100644 --- a/FirebaseAppCheck/CHANGELOG.md +++ b/FirebaseAppCheck/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [added] Added `FIRRecaptchaEnterpriseProvider` wrapping reCAPTCHA Enterprise for iOS. + # 10.27.0 - [fixed] [CocoaPods] missing symbol error for FIRGetLoggerLevel. (#12899) diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h index 9f479b1c7f7..3394fd3fc69 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h @@ -36,8 +36,8 @@ FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDebugToken; FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions; // FIRRecaptchaEnterpriseProvider.m -FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions; - +FOUNDATION_EXPORT NSString *const + kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions; void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...); diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m index e683bdca606..61efd520478 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m @@ -36,8 +36,8 @@ NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions = @"I-FAA006001"; // FIRRecaptchaEnterpriseProvider.m -NSString *const kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions = @"I-FAA007001"; - +NSString *const kFIRLoggerAppCheckMessageRecaptchaEnterpriseProviderIncompleteFIROptions = + @"I-FAA007001"; #pragma mark - Log functions diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h index c3bada74d6b..cd416838ef0 100644 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h @@ -1,3 +1,19 @@ +/* + * 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 #import "FIRAppCheckAvailability.h" #import "FIRAppCheckProvider.h" diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h index e2ac9a3ac6f..5ee0a1ecdd5 100644 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h @@ -1,3 +1,19 @@ +/* + * 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 #import "FIRAppCheckProviderFactory.h" diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m index 6b51a6f9922..bc4bf1d8d7c 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m @@ -14,8 +14,8 @@ * limitations under the License. */ -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h" +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" #import @@ -29,8 +29,6 @@ #import "FirebaseCore/Extension/FirebaseCoreInternal.h" -NS_ASSUME_NONNULL_BEGIN - @interface FIRRecaptchaEnterpriseProvider () @property(nonatomic, readonly) GACRecaptchaEnterpriseProvider *recaptchaEnterpriseProvider; @@ -39,7 +37,8 @@ @interface FIRRecaptchaEnterpriseProvider () @implementation FIRRecaptchaEnterpriseProvider -- (instancetype)initWithRecaptchaEnterpriseProvider:(GACRecaptchaEnterpriseProvider *)recaptchaEnterpriseProvider { +- (instancetype)initWithRecaptchaEnterpriseProvider: + (GACRecaptchaEnterpriseProvider *)recaptchaEnterpriseProvider { self = [super init]; if (self) { _recaptchaEnterpriseProvider = recaptchaEnterpriseProvider; @@ -60,10 +59,11 @@ - (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey { } GACRecaptchaEnterpriseProvider *recaptchaEnterpriseProvider = - [[GACRecaptchaEnterpriseProvider alloc] initWithSiteKey:siteKey - resourceName:app.resourceName - APIKey:app.options.APIKey - requestHooks:@[ [app.heartbeatLogger requestHook] ]]; + [[GACRecaptchaEnterpriseProvider alloc] + initWithSiteKey:siteKey + resourceName:app.resourceName + APIKey:app.options.APIKey + requestHooks:@[ [app.heartbeatLogger requestHook] ]]; return [self initWithRecaptchaEnterpriseProvider:recaptchaEnterpriseProvider]; } @@ -72,15 +72,16 @@ - (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey { - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler { - [self.recaptchaEnterpriseProvider getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, - NSError *_Nullable error) { - if (error) { - handler(nil, error); - return; - } - - handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); - }]; + [self.recaptchaEnterpriseProvider + getTokenWithCompletion:^(GACAppCheckToken *_Nullable internalToken, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + return; + } + + handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil); + }]; } - (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, @@ -98,5 +99,3 @@ - (void)getLimitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, } @end - -NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m index 3dfcf15e195..84579d30f4a 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m @@ -19,8 +19,6 @@ #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h" -NS_ASSUME_NONNULL_BEGIN - @interface FIRRecaptchaEnterpriseProviderFactory () @property(nonatomic, readonly) NSString *siteKey; @@ -42,5 +40,3 @@ - (instancetype)initWithSiteKey:(NSString *)siteKey { } @end - -NS_ASSUME_NONNULL_END diff --git a/Package.swift b/Package.swift index a82e36e4a26..dec61ee92a6 100644 --- a/Package.swift +++ b/Package.swift @@ -172,7 +172,7 @@ let package = Package( url: "https://github.com/google/interop-ios-for-google-sdks.git", "101.0.0" ..< "102.0.0" ), - .package(path: "/Users/nickcooke/Developer/app-check"), + .package(url: "https://github.com/google/app-check.git", branch: "nc/target-split"), ], targets: [ .target( From cbc93334c62977c7a8e567cd8668d3db150e57c8 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 15:41:53 -0400 Subject: [PATCH 03/12] changelog update --- FirebaseAppCheck/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAppCheck/CHANGELOG.md b/FirebaseAppCheck/CHANGELOG.md index 66d2eeeb4d7..57b0e1bbb45 100644 --- a/FirebaseAppCheck/CHANGELOG.md +++ b/FirebaseAppCheck/CHANGELOG.md @@ -1,5 +1,5 @@ # Unreleased -- [added] Added `FIRRecaptchaEnterpriseProvider` wrapping reCAPTCHA Enterprise for iOS. +- [added] Added reCAPTCHA Enterprise provider. # 10.27.0 - [fixed] [CocoaPods] missing symbol error for FIRGetLoggerLevel. (#12899) From 455549f0cadcf73872bde9c31dee18bb2c088c24 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 16:40:08 -0400 Subject: [PATCH 04/12] refactor(AppCheck): align factory name with spec and fix review comments --- ...pCheckRecaptchaEnterpriseProviderFactory.h | 29 +++++++++++++++++++ ...CheckRecaptchaEnterpriseProviderFactory.m} | 8 ++--- .../FIRRecaptchaEnterpriseProvider.m | 4 ++- ...RRecaptchaEnterpriseProviderFactoryTests.m | 9 +++--- .../FIRRecaptchaEnterpriseProviderTests.m | 25 ++++++++-------- Package.swift | 16 +++++++++- 6 files changed, 69 insertions(+), 22 deletions(-) create mode 100644 FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h rename FirebaseAppCheck/Sources/RecaptchaProvider/{FIRRecaptchaEnterpriseProviderFactory.m => FIRAppCheckRecaptchaEnterpriseProviderFactory.m} (78%) diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h new file mode 100644 index 00000000000..49f65af6457 --- /dev/null +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h @@ -0,0 +1,29 @@ +/* + * 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 +#import "FIRAppCheckProviderFactory.h" + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(AppCheckRecaptchaEnterpriseProviderFactory) +@interface FIRAppCheckRecaptchaEnterpriseProviderFactory : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (nullable instancetype)initWithSiteKey:(NSString *)siteKey; + +@end +NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m similarity index 78% rename from FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m rename to FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m index 84579d30f4a..38419fcc006 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactory.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m @@ -14,20 +14,20 @@ * limitations under the License. */ -#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h" +#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h" -@interface FIRRecaptchaEnterpriseProviderFactory () +@interface FIRAppCheckRecaptchaEnterpriseProviderFactory () @property(nonatomic, readonly) NSString *siteKey; @end -@implementation FIRRecaptchaEnterpriseProviderFactory +@implementation FIRAppCheckRecaptchaEnterpriseProviderFactory -- (instancetype)initWithSiteKey:(NSString *)siteKey { +- (nullable instancetype)initWithSiteKey:(NSString *)siteKey { self = [super init]; if (self) { _siteKey = [siteKey copy]; diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m index bc4bf1d8d7c..1f06a25c976 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m @@ -22,6 +22,7 @@ @import RecaptchaEnterpriseProvider; #import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h" + #import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseAppCheck/Sources/Core/FIRAppCheckValidator.h" @@ -58,12 +59,13 @@ - (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey { return nil; } + id heartbeatHook = [app.heartbeatLogger requestHook]; GACRecaptchaEnterpriseProvider *recaptchaEnterpriseProvider = [[GACRecaptchaEnterpriseProvider alloc] initWithSiteKey:siteKey resourceName:app.resourceName APIKey:app.options.APIKey - requestHooks:@[ [app.heartbeatLogger requestHook] ]]; + requestHooks:heartbeatHook ? @[ heartbeatHook ] : @[]]; return [self initWithRecaptchaEnterpriseProvider:recaptchaEnterpriseProvider]; } diff --git a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m index 591aca40af1..b8a79e41f8d 100644 --- a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m +++ b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderFactoryTests.m @@ -16,15 +16,15 @@ #import +#import #import -#import #import "FirebaseCore/Extension/FirebaseCoreInternal.h" -@interface FIRRecaptchaEnterpriseProviderFactoryTests : XCTestCase +@interface FIRAppCheckRecaptchaEnterpriseProviderFactoryTests : XCTestCase @end -@implementation FIRRecaptchaEnterpriseProviderFactoryTests +@implementation FIRAppCheckRecaptchaEnterpriseProviderFactoryTests - (void)testCreateProviderWithApp { FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; @@ -34,7 +34,8 @@ - (void)testCreateProviderWithApp { app.dataCollectionDefaultEnabled = NO; NSString *siteKey = @"test_site_key"; - FIRRecaptchaEnterpriseProviderFactory *factory = [[FIRRecaptchaEnterpriseProviderFactory alloc] initWithSiteKey:siteKey]; + FIRAppCheckRecaptchaEnterpriseProviderFactory *factory = + [[FIRAppCheckRecaptchaEnterpriseProviderFactory alloc] initWithSiteKey:siteKey]; FIRRecaptchaEnterpriseProvider *createdProvider = [factory createProviderWithApp:app]; diff --git a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m index f92cd844a9c..bab69187d4a 100644 --- a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m @@ -14,14 +14,14 @@ * limitations under the License. */ -#import #import +#import #import @import RecaptchaEnterpriseProvider; -#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import +#import "FirebaseAppCheck/Sources/Core/FIRAppCheckToken+Internal.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" @@ -34,13 +34,13 @@ @interface FIRRecaptchaEnterpriseProvider (Tests) -- (instancetype)initWithRecaptchaEnterpriseProvider:(GACRecaptchaEnterpriseProvider *)recaptchaEnterpriseProvider; +- (instancetype)initWithRecaptchaEnterpriseProvider: + (GACRecaptchaEnterpriseProvider *)recaptchaEnterpriseProvider; @end @interface FIRRecaptchaEnterpriseProviderTests : XCTestCase -@property(nonatomic, copy) NSString *resourceName; @property(nonatomic) id recaptchaEnterpriseProviderMock; @property(nonatomic) FIRRecaptchaEnterpriseProvider *provider; @@ -51,10 +51,9 @@ @implementation FIRRecaptchaEnterpriseProviderTests - (void)setUp { [super setUp]; - self.resourceName = [NSString stringWithFormat:@"projects/%@/apps/%@", kProjectID, kAppID]; self.recaptchaEnterpriseProviderMock = OCMStrictClassMock([GACRecaptchaEnterpriseProvider class]); - self.provider = - [[FIRRecaptchaEnterpriseProvider alloc] initWithRecaptchaEnterpriseProvider:self.recaptchaEnterpriseProviderMock]; + self.provider = [[FIRRecaptchaEnterpriseProvider alloc] + initWithRecaptchaEnterpriseProvider:self.recaptchaEnterpriseProviderMock]; } - (void)tearDown { @@ -80,13 +79,15 @@ - (void)testInitWithIncompleteApp { FIRApp *missingAPIKeyApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; missingAPIKeyApp.dataCollectionDefaultEnabled = NO; - XCTAssertNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:missingAPIKeyApp siteKey:kSiteKey]); + XCTAssertNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:missingAPIKeyApp + siteKey:kSiteKey]); options.projectID = nil; options.APIKey = kAPIKey; FIRApp *missingProjectIDApp = [[FIRApp alloc] initInstanceWithName:kAppName options:options]; missingProjectIDApp.dataCollectionDefaultEnabled = NO; - XCTAssertNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:missingProjectIDApp siteKey:kSiteKey]); + XCTAssertNil([[FIRRecaptchaEnterpriseProvider alloc] initWithApp:missingProjectIDApp + siteKey:kSiteKey]); } - (void)testGetTokenSuccess { @@ -131,7 +132,7 @@ - (void)testGetLimitedUseTokenSuccess { [NSNull null], nil])]); [self.provider getLimitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, - NSError *_Nullable error) { + NSError *_Nullable error) { XCTAssertEqualObjects(token.token, validInternalToken.token); XCTAssertEqualObjects(token.expirationDate, validInternalToken.expirationDate); XCTAssertEqualObjects(token.receivedAtDate, validInternalToken.receivedAtDate); @@ -150,9 +151,9 @@ - (void)testGetLimitedUseTokenProviderError { nil])]); [self.provider getLimitedUseTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, - NSError *_Nullable error) { + NSError *_Nullable error) { XCTAssertNil(token); - XCTAssertIdentical(error, expectedError); + XCTAssertEqualObjects(error, expectedError); }]; OCMVerifyAll(self.recaptchaEnterpriseProviderMock); diff --git a/Package.swift b/Package.swift index dec61ee92a6..e02d983b9bd 100644 --- a/Package.swift +++ b/Package.swift @@ -172,7 +172,7 @@ let package = Package( url: "https://github.com/google/interop-ios-for-google-sdks.git", "101.0.0" ..< "102.0.0" ), - .package(url: "https://github.com/google/app-check.git", branch: "nc/target-split"), + appCheckDependency(), ], targets: [ .target( @@ -1666,3 +1666,17 @@ func isFoundationModelsSupportedPlatformSwiftSetting() -> SwiftSetting { .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS]) ) } + +func appCheckDependency() -> Package.Dependency { + let appCheckURL = "https://github.com/google/app-check.git" + + if let localPath = Context.environment["FIREBASE_APP_CHECK_LOCAL_PATH"] { + return .package(path: localPath) + } + + if let branch = Context.environment["FIREBASE_APP_CHECK_BRANCH"] { + return .package(url: appCheckURL, branch: branch) + } + + return .package(url: appCheckURL, "11.0.1" ..< "12.0.0") +} From 9d5f030228cd2a853e23d29cc48b06714cc673ec Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 16:40:29 -0400 Subject: [PATCH 05/12] delete moved --- .../FIRRecaptchaEnterpriseProviderFactory.h | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h deleted file mode 100644 index 5ee0a1ecdd5..00000000000 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaEnterpriseProviderFactory.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 -#import "FIRAppCheckProviderFactory.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(RecaptchaEnterpriseProviderFactory) -@interface FIRRecaptchaEnterpriseProviderFactory : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithSiteKey:(NSString *)siteKey; - -@end -NS_ASSUME_NONNULL_END From 2df1dd1471e3e069f8c924445e92ae43fc4147c6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 16:58:24 -0400 Subject: [PATCH 06/12] test(AppCheck): use consistent date in Recaptcha tests --- .../FIRRecaptchaEnterpriseProviderTests.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m index bab69187d4a..ba53801e7b2 100644 --- a/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m +++ b/FirebaseAppCheck/Tests/Unit/RecaptchaProvider/FIRRecaptchaEnterpriseProviderTests.m @@ -91,9 +91,10 @@ - (void)testInitWithIncompleteApp { } - (void)testGetTokenSuccess { + NSDate *date = [NSDate date]; GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"valid_token" - expirationDate:[NSDate date] - receivedAtDate:[NSDate date]]; + expirationDate:date + receivedAtDate:date]; OCMExpect([self.recaptchaEnterpriseProviderMock getTokenWithCompletion:([OCMArg invokeBlockWithArgs:validInternalToken, [NSNull null], nil])]); @@ -124,9 +125,10 @@ - (void)testGetTokenAPIError { } - (void)testGetLimitedUseTokenSuccess { + NSDate *date = [NSDate date]; GACAppCheckToken *validInternalToken = [[GACAppCheckToken alloc] initWithToken:@"TEST_ValidToken" - expirationDate:[NSDate date] - receivedAtDate:[NSDate date]]; + expirationDate:date + receivedAtDate:date]; OCMExpect([self.recaptchaEnterpriseProviderMock getLimitedUseTokenWithCompletion:([OCMArg invokeBlockWithArgs:validInternalToken, [NSNull null], nil])]); From fe303d830091a0394c77e0604118d8b55ddf239a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 17:18:02 -0400 Subject: [PATCH 07/12] init fixes and dep fixes. --- .../FIRAppCheckRecaptchaEnterpriseProviderFactory.h | 2 +- .../FIRAppCheckRecaptchaEnterpriseProviderFactory.m | 3 ++- Package.swift | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h index 49f65af6457..fba4981ab31 100644 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h @@ -23,7 +23,7 @@ NS_SWIFT_NAME(AppCheckRecaptchaEnterpriseProviderFactory) @interface FIRAppCheckRecaptchaEnterpriseProviderFactory : NSObject - (instancetype)init NS_UNAVAILABLE; -- (nullable instancetype)initWithSiteKey:(NSString *)siteKey; +- (instancetype)initWithSiteKey:(NSString *)siteKey; @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m index 38419fcc006..fba422f1e37 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m @@ -27,8 +27,9 @@ @interface FIRAppCheckRecaptchaEnterpriseProviderFactory () @implementation FIRAppCheckRecaptchaEnterpriseProviderFactory -- (nullable instancetype)initWithSiteKey:(NSString *)siteKey { +- (instancetype)initWithSiteKey:(NSString *)siteKey { self = [super init]; + if (self) { _siteKey = [siteKey copy]; } diff --git a/Package.swift b/Package.swift index e02d983b9bd..5e592d83338 100644 --- a/Package.swift +++ b/Package.swift @@ -1262,7 +1262,9 @@ let package = Package( "FirebaseAppCheckInterop", "FirebaseCore", .product(name: "AppCheckCore", package: "app-check"), + .product(name: "RecaptchaEnterpriseProvider", package: "app-check"), .product(name: "GULEnvironment", package: "GoogleUtilities"), + .product(name: "GULUserDefaults", package: "GoogleUtilities"), ], path: "FirebaseAppCheck/Sources", From 39c4d649bea4afe1a79d5a4950b58029c7b68d66 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 17:23:03 -0400 Subject: [PATCH 08/12] feat(ci): ddd env_vars input to _spm.yml workflow --- .github/workflows/_spm.yml | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/.github/workflows/_spm.yml b/.github/workflows/_spm.yml index d4c8bf96c43..01f84ff11b2 100644 --- a/.github/workflows/_spm.yml +++ b/.github/workflows/_spm.yml @@ -58,6 +58,14 @@ on: required: false default: false + # Custom environment variables to inject into the jobs. + # Expected to be a JSON-formatted string. + # Example: '{"FIREBASE_APP_CHECK_BRANCH": "nc/target-split"}' + env_vars: + type: string + required: false + default: "{}" + outputs: cache_key: description: "The cache key for the Swift package resolution." @@ -72,6 +80,24 @@ jobs: cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - name: Set Custom Environment Variables + run: | + python3 -c ' + import os, json + try: + env_vars = json.loads(os.environ.get("CUSTOM_ENV_VARS", "{}")) + if not isinstance(env_vars, dict): + raise ValueError("env_vars must be a JSON object") + with open(os.environ["GITHUB_ENV"], "a") as f: + for k, v in env_vars.items(): + f.write(f"{k}={v}\n") + except json.JSONDecodeError: + print("Warning: env_vars is not valid JSON. Skipping.") + except Exception as e: + print(f"Error setting env vars: {e}") + ' + env: + CUSTOM_ENV_VARS: ${{ inputs.env_vars }} - name: Xcode run: sudo xcode-select -s /Applications/Xcode_26.4.app/Contents/Developer - name: Generate Swift Package.resolved @@ -110,6 +136,24 @@ jobs: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false + - name: Set Custom Environment Variables + run: | + python3 -c ' + import os, json + try: + env_vars = json.loads(os.environ.get("CUSTOM_ENV_VARS", "{}")) + if not isinstance(env_vars, dict): + raise ValueError("env_vars must be a JSON object") + with open(os.environ["GITHUB_ENV"], "a") as f: + for k, v in env_vars.items(): + f.write(f"{k}={v}\n") + except json.JSONDecodeError: + print("Warning: env_vars is not valid JSON. Skipping.") + except Exception as e: + print(f"Error setting env vars: {e}") + ' + env: + CUSTOM_ENV_VARS: ${{ inputs.env_vars }} - uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: .build From e3f36aaf54210c216a5dab1ac476504e5e35f1b1 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 17:38:43 -0400 Subject: [PATCH 09/12] ci(AppCheck): use env_vars input in sdk.appcheck.yml for remote branch --- .github/workflows/sdk.appcheck.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/sdk.appcheck.yml b/.github/workflows/sdk.appcheck.yml index c47ff92a2c0..f2ac1424a40 100644 --- a/.github/workflows/sdk.appcheck.yml +++ b/.github/workflows/sdk.appcheck.yml @@ -30,6 +30,8 @@ jobs: uses: ./.github/workflows/_spm.yml with: target: ${{ matrix.target }} + env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "nc/target-split"}' + catalyst: uses: ./.github/workflows/_catalyst.yml From e8c8250a9a4fe3cef5e5fa33b9d78dfbc690aa9b Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 17:44:24 -0400 Subject: [PATCH 10/12] fix(AppCheck): add siteKey validation and cleanup Package.swift --- .../FIRAppCheckRecaptchaEnterpriseProviderFactory.m | 1 + .../RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m | 4 ++++ Package.swift | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m index fba422f1e37..918e0c9bb02 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRAppCheckRecaptchaEnterpriseProviderFactory.m @@ -28,6 +28,7 @@ @interface FIRAppCheckRecaptchaEnterpriseProviderFactory () @implementation FIRAppCheckRecaptchaEnterpriseProviderFactory - (instancetype)initWithSiteKey:(NSString *)siteKey { + NSParameterAssert(siteKey.length > 0); self = [super init]; if (self) { diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m index 1f06a25c976..3ba1a758937 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m @@ -48,7 +48,11 @@ - (instancetype)initWithRecaptchaEnterpriseProvider: } - (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey { + if (siteKey.length == 0) { + return nil; + } NSArray *missingOptionsFields = + [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options]; if (missingOptionsFields.count > 0) { FIRLogError(kFIRLoggerAppCheck, diff --git a/Package.swift b/Package.swift index 5e592d83338..adf463f32a0 100644 --- a/Package.swift +++ b/Package.swift @@ -1264,8 +1264,8 @@ let package = Package( .product(name: "AppCheckCore", package: "app-check"), .product(name: "RecaptchaEnterpriseProvider", package: "app-check"), .product(name: "GULEnvironment", package: "GoogleUtilities"), - .product(name: "GULUserDefaults", package: "GoogleUtilities"), + ], path: "FirebaseAppCheck/Sources", publicHeadersPath: "Public", From 7834a055c879ccf6d5c1916780cb64ff3a100570 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 1 May 2026 17:48:53 -0400 Subject: [PATCH 11/12] style(AppCheck): fix blank lines and indentation --- .../Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m | 1 - Package.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m index 3ba1a758937..d5fb456089b 100644 --- a/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m +++ b/FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaEnterpriseProvider.m @@ -52,7 +52,6 @@ - (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey { return nil; } NSArray *missingOptionsFields = - [FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options]; if (missingOptionsFields.count > 0) { FIRLogError(kFIRLoggerAppCheck, diff --git a/Package.swift b/Package.swift index adf463f32a0..4921ce28cf3 100644 --- a/Package.swift +++ b/Package.swift @@ -1265,8 +1265,8 @@ let package = Package( .product(name: "RecaptchaEnterpriseProvider", package: "app-check"), .product(name: "GULEnvironment", package: "GoogleUtilities"), .product(name: "GULUserDefaults", package: "GoogleUtilities"), - ], + path: "FirebaseAppCheck/Sources", publicHeadersPath: "Public", cSettings: [ From 868fa0bcc12d6c201c5d9d79c6cc9f825579e280 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 4 May 2026 11:46:20 -0400 Subject: [PATCH 12/12] fix(AppCheck): add new headers to umbrella header --- .../Sources/Public/FirebaseAppCheck/FirebaseAppCheck.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FirebaseAppCheck.h b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FirebaseAppCheck.h index 807336e29b4..bc7869f6798 100644 --- a/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FirebaseAppCheck.h +++ b/FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FirebaseAppCheck.h @@ -30,3 +30,7 @@ // App Attest provider. #import "FIRAppAttestProvider.h" + +// Recaptcha Enterprise provider +#import "FIRAppCheckRecaptchaEnterpriseProviderFactory.h" +#import "FIRRecaptchaEnterpriseProvider.h"