Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions .github/workflows/_spm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/sdk.appcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions FirebaseAppCheck/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [added] Added reCAPTCHA Enterprise provider.

# 10.27.0
- [fixed] [CocoaPods] missing symbol error for FIRGetLoggerLevel. (#12899)

Expand Down
4 changes: 4 additions & 0 deletions FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
4 changes: 4 additions & 0 deletions FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.m
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>
#import "FIRAppCheckProviderFactory.h"

NS_ASSUME_NONNULL_BEGIN

NS_SWIFT_NAME(AppCheckRecaptchaEnterpriseProviderFactory)
@interface FIRAppCheckRecaptchaEnterpriseProviderFactory : NSObject <FIRAppCheckProviderFactory>

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithSiteKey:(NSString *)siteKey;

@end
NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 <Foundation/Foundation.h>
#import "FIRAppCheckAvailability.h"
#import "FIRAppCheckProvider.h"

@class FIRApp;

NS_ASSUME_NONNULL_BEGIN

NS_SWIFT_NAME(RecaptchaEnterpriseProvider)
@interface FIRRecaptchaEnterpriseProvider : NSObject <FIRAppCheckProvider>

- (instancetype)init NS_UNAVAILABLE;
- (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey;

@end
NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@

// App Attest provider.
#import "FIRAppAttestProvider.h"

// Recaptcha Enterprise provider
#import "FIRAppCheckRecaptchaEnterpriseProviderFactory.h"
#import "FIRRecaptchaEnterpriseProvider.h"
Original file line number Diff line number Diff line change
@@ -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 "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h"

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

@interface FIRAppCheckRecaptchaEnterpriseProviderFactory ()

@property(nonatomic, readonly) NSString *siteKey;

@end

@implementation FIRAppCheckRecaptchaEnterpriseProviderFactory

- (instancetype)initWithSiteKey:(NSString *)siteKey {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Similar to the provider initialization, it is good practice to ensure the siteKey provided to the factory is not empty.

Suggested change
- (instancetype)initWithSiteKey:(NSString *)siteKey {
- (instancetype)initWithSiteKey:(NSString *)siteKey {
NSParameterAssert(siteKey.length > 0);
self = [super init];

NSParameterAssert(siteKey.length > 0);
self = [super init];

if (self) {
_siteKey = [siteKey copy];
}
return self;
}

- (nullable id<FIRAppCheckProvider>)createProviderWithApp:(nonnull FIRApp *)app {
return [[FIRRecaptchaEnterpriseProvider alloc] initWithApp:app siteKey:self.siteKey];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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/FIRRecaptchaEnterpriseProvider.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h"

#import <AppCheckCore/AppCheckCore.h>

@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"

@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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

It is recommended to validate that siteKey is not an empty string before proceeding with initialization. While NS_ASSUME_NONNULL handles nullability at compile-time, a runtime check or assertion for an empty string can prevent issues with the underlying GACRecaptchaEnterpriseProvider.

- (nullable instancetype)initWithApp:(FIRApp *)app siteKey:(NSString *)siteKey {
  if (siteKey.length == 0) {
    return nil;
  }

if (siteKey.length == 0) {
return nil;
}
NSArray<NSString *> *missingOptionsFields =
[FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options];
Comment on lines +54 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

There is an unnecessary blank line and inconsistent indentation here. It's better to keep the assignment clean and follow standard continuation indentation.

Suggested change
NSArray<NSString *> *missingOptionsFields =
[FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options];
NSArray<NSString *> *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;
}

id heartbeatHook = [app.heartbeatLogger requestHook];
GACRecaptchaEnterpriseProvider *recaptchaEnterpriseProvider =
[[GACRecaptchaEnterpriseProvider alloc]
initWithSiteKey:siteKey
resourceName:app.resourceName
APIKey:app.options.APIKey
requestHooks:heartbeatHook ? @[ heartbeatHook ] : @[]];

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);
Comment thread
ncooke3 marked this conversation as resolved.
}];
}

- (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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 <XCTest/XCTest.h>

#import <FirebaseAppCheck/FIRAppCheckRecaptchaEnterpriseProviderFactory.h>
#import <FirebaseAppCheck/FIRRecaptchaEnterpriseProvider.h>

#import "FirebaseCore/Extension/FirebaseCoreInternal.h"

@interface FIRAppCheckRecaptchaEnterpriseProviderFactoryTests : XCTestCase
@end

@implementation FIRAppCheckRecaptchaEnterpriseProviderFactoryTests

- (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";
FIRAppCheckRecaptchaEnterpriseProviderFactory *factory =
[[FIRAppCheckRecaptchaEnterpriseProviderFactory alloc] initWithSiteKey:siteKey];

FIRRecaptchaEnterpriseProvider *createdProvider = [factory createProviderWithApp:app];

XCTAssert([createdProvider isKindOfClass:[FIRRecaptchaEnterpriseProvider class]]);
}

@end
Loading
Loading