feat(appcheck): implement RecaptchaEnterpriseProvider and tests (#16155)

Co-authored-by: Paul Beusterien <paulbeusterien@google.com>
This commit is contained in:
Nick CookeandPaul Beusterien authored and GitHub committed 2026-06-10 12:08:31 -04:00
1 parent 57e4e51357
commit 06d53a2ae0
36 files changed
+1375 -55

No files matched your search

@@ -21,6 +21,7 @@ on:
env: env:
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
FIREBASE_APP_CHECK_BRANCH: main
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
@@ -44,6 +45,7 @@ jobs:
method: xcodebuild method: xcodebuild
os: ${{ matrix.os }} os: ${{ matrix.os }}
xcode: ${{ matrix.xcode }} xcode: ${{ matrix.xcode }}
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
client-app-spm-source-firestore: client-app-spm-source-firestore:
strategy: strategy:
@@ -63,6 +65,7 @@ jobs:
os: ${{ matrix.os }} os: ${{ matrix.os }}
xcode: ${{ matrix.xcode }} xcode: ${{ matrix.xcode }}
setup_command: echo "FIREBASE_SOURCE_FIRESTORE=1" >> $GITHUB_ENV setup_command: echo "FIREBASE_SOURCE_FIRESTORE=1" >> $GITHUB_ENV
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
client-app-cocoapods: client-app-cocoapods:
strategy: strategy:
@@ -80,3 +83,4 @@ jobs:
os: ${{ matrix.os }} os: ${{ matrix.os }}
xcode: ${{ matrix.xcode }} xcode: ${{ matrix.xcode }}
setup_command: scripts/install_prereqs.sh ClientApp iOS xcodebuild setup_command: scripts/install_prereqs.sh ClientApp iOS xcodebuild
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
+8
View File
@@ -29,6 +29,7 @@ jobs:
spm-package-resolved: spm-package-resolved:
env: env:
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
FIREBASE_APP_CHECK_BRANCH: main
runs-on: macos-26 runs-on: macos-26
outputs: outputs:
cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} cache_key: ${{ steps.generate_cache_key.outputs.cache_key }}
@@ -55,6 +56,9 @@ jobs:
# Don't run on private repo unless it is a PR. # Don't run on private repo unless it is a PR.
if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'
needs: [spm-package-resolved] needs: [spm-package-resolved]
env:
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
FIREBASE_APP_CHECK_BRANCH: main
strategy: strategy:
matrix: matrix:
include: include:
@@ -105,6 +109,8 @@ jobs:
# Don't run on private repo unless it is a PR. # Don't run on private repo unless it is a PR.
if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'
needs: [spm-package-resolved] needs: [spm-package-resolved]
env:
FIREBASE_APP_CHECK_BRANCH: main
strategy: strategy:
matrix: matrix:
include: include:
@@ -144,6 +150,8 @@ jobs:
# Don't run on private repo unless it is a PR. # Don't run on private repo unless it is a PR.
if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'
needs: [spm-package-resolved] needs: [spm-package-resolved]
env:
FIREBASE_APP_CHECK_BRANCH: main
strategy: strategy:
matrix: matrix:
# Full set of Firebase-Package tests only run on iOS. Run subset on other platforms. # Full set of Firebase-Package tests only run on iOS. Run subset on other platforms.
+2
View File
@@ -30,6 +30,7 @@ jobs:
uses: ./.github/workflows/_spm.yml uses: ./.github/workflows/_spm.yml
with: with:
target: ${{ matrix.target }} target: ${{ matrix.target }}
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
catalyst: catalyst:
uses: ./.github/workflows/_catalyst.yml uses: ./.github/workflows/_catalyst.yml
@@ -57,6 +58,7 @@ jobs:
method: spm method: spm
sanitizers: ${{ matrix.diagnostic }} sanitizers: ${{ matrix.diagnostic }}
setup_command: scripts/setup_spm_tests.sh setup_command: scripts/setup_spm_tests.sh
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
app_check-cron-only: app_check-cron-only:
needs: pod_lib_lint needs: pod_lib_lint
+1
View File
@@ -28,6 +28,7 @@ jobs:
uses: ./.github/workflows/_spm.yml uses: ./.github/workflows/_spm.yml
with: with:
target: CoreUnit target: CoreUnit
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
catalyst: catalyst:
uses: ./.github/workflows/_catalyst.yml uses: ./.github/workflows/_catalyst.yml
+6
View File
@@ -540,6 +540,7 @@ jobs:
env: env:
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
FIREBASE_SOURCE_FIRESTORE: 1 FIREBASE_SOURCE_FIRESTORE: 1
FIREBASE_APP_CHECK_BRANCH: main
outputs: outputs:
cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} cache_key: ${{ steps.generate_cache_key.outputs.cache_key }}
steps: steps:
@@ -591,6 +592,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
FIREBASE_SOURCE_FIRESTORE: 1 FIREBASE_SOURCE_FIRESTORE: 1
FIREBASE_APP_CHECK_BRANCH: main
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Xcode - name: Xcode
@@ -614,6 +616,7 @@ jobs:
with: with:
target: FirebaseFirestoreTests target: FirebaseFirestoreTests
platforms: iOS platforms: iOS
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
spm-source-cron: spm-source-cron:
# Don't run on private repo. # Don't run on private repo.
@@ -624,6 +627,7 @@ jobs:
target: [tvOS, macOS, catalyst] target: [tvOS, macOS, catalyst]
env: env:
FIREBASE_SOURCE_FIRESTORE: 1 FIREBASE_SOURCE_FIRESTORE: 1
FIREBASE_APP_CHECK_BRANCH: main
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Xcode - name: Xcode
@@ -648,6 +652,8 @@ jobs:
strategy: strategy:
matrix: matrix:
target: [tvOS, macOS, catalyst] target: [tvOS, macOS, catalyst]
env:
FIREBASE_APP_CHECK_BRANCH: main
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Xcode - name: Xcode
+5 -1
View File
@@ -44,7 +44,7 @@ Pod::Spec.new do |s|
s.osx.weak_framework = 'DeviceCheck' s.osx.weak_framework = 'DeviceCheck'
s.tvos.weak_framework = 'DeviceCheck' s.tvos.weak_framework = 'DeviceCheck'
s.dependency 'AppCheckCore', '~> 11.0' s.dependency 'AppCheckCore', '~> 11.3'
s.dependency 'FirebaseAppCheckInterop', '~> 12.15.0' s.dependency 'FirebaseAppCheckInterop', '~> 12.15.0'
s.dependency 'FirebaseCore', '~> 12.15.0' s.dependency 'FirebaseCore', '~> 12.15.0'
s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/Environment', '~> 8.1'
@@ -93,7 +93,11 @@ Pod::Spec.new do |s|
} }
swift_unit_tests.source_files = [ swift_unit_tests.source_files = [
base_dir + 'Tests/Unit/Swift/**/*.swift', base_dir + 'Tests/Unit/Swift/**/*.swift',
'SharedTestUtilities/ExceptionCatcher.[mh]'
] ]
swift_unit_tests.pod_target_xcconfig = {
'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseAppCheck/Tests/Unit/Swift/FirebaseAppCheck-unit-Bridging-Header.h'
}
swift_unit_tests.dependency 'FirebaseCoreExtension', '~> 12.15.0' swift_unit_tests.dependency 'FirebaseCoreExtension', '~> 12.15.0'
end end
@@ -0,0 +1,182 @@
# E2E Testing with FIRAppCheckTestApp
This document provides information on how to configure and run End-to-End (E2E)
tests for App Check providers using this sample app.
## Configurability
The app's behavior can be configured using environment variables passed during
test execution.
### Environment Variables
Starting with Xcode 13, you can pass environment variables directly to the
test runner by prefixing them with `TEST_RUNNER_`. The prefix is stripped when
it reaches the test process.
- **`TEST_RUNNER_RECAPTCHA_SITE_KEY`**: The reCAPTCHA site key used
by the `AppCheckRecaptchaProvider`.
- **Access in Code**: Read via
`ProcessInfo.processInfo.environment["RECAPTCHA_SITE_KEY"]`.
- **`TEST_RUNNER_APP_CHECK_PROVIDER`**: Specifies which App Check provider
factory to use.
- **Supported Values**: `recaptcha` (default), `debug`.
- **Access in Code**: Read via
`ProcessInfo.processInfo.environment["APP_CHECK_PROVIDER"]`.
### Manual Override
For local debugging and manual testing, you can override the environment
variables by setting `manualProviderOverride` in `AppDelegate.swift`:
```swift
let manualProviderOverride: String? = "debug"
```
## Running Tests
The commands below should be run from the **repository root**.
### Prerequisites
- Ensure you have a local checkout of the `app-check` repository if you are
developing it locally. Set `FIREBASE_APP_CHECK_LOCAL_PATH` to point to it.
### Sample Commands
#### Run tests with reCAPTCHA provider
```bash
export TEST_RUNNER_RECAPTCHA_SITE_KEY="your_site_key_here"
export TEST_RUNNER_APP_CHECK_PROVIDER="recaptcha"
export FIREBASE_APP_CHECK_LOCAL_PATH="/path/to/your/local/app-check"
SIM_ID=$(xcrun simctl list devices available | grep "iPhone" | grep -E -o '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}' | head -n 1)
xcodebuild test \
-workspace FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcworkspace \
-scheme FIRAppCheckTestApp \
-destination "platform=iOS Simulator,id=$SIM_ID"
```
#### Run tests with Debug provider
```bash
export TEST_RUNNER_APP_CHECK_PROVIDER="debug"
export FIREBASE_APP_CHECK_LOCAL_PATH="/path/to/your/local/app-check"
SIM_ID=$(xcrun simctl list devices available | grep "iPhone" | grep -E -o '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}' | head -n 1)
xcodebuild test \
-workspace FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcworkspace \
-scheme FIRAppCheckTestApp \
-destination "platform=iOS Simulator,id=$SIM_ID"
```
*Note: The Debug provider might require you to register the generated debug token in the Firebase Console for the tests to pass if they interact with live services.*
### Running and Testing in Xcode
If you prefer to use the Xcode UI instead of `xcodebuild`, follow these steps
to configure the environment:
#### 1. Resolve Local Dependency
If you are using a local checkout of the `app-check` repository, Xcode must be
launched from the terminal with the `FIREBASE_APP_CHECK_LOCAL_PATH` environment
variable set so that Swift Package Manager can resolve it correctly.
Run the following command from the repository root:
```bash
open --env FIREBASE_APP_CHECK_LOCAL_PATH=/path/to/your/local/app-check FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcworkspace
```
#### 2. Configure Provider and Site Key
You have two options to configure the provider when running or testing in Xcode:
**Option A: Via Manual Override in Code (Easiest for Running the App)**
If you just want to quickly run the app with a specific provider without
changing scheme settings:
1. Open `AppDelegate.swift`.
2. Locate `manualProviderOverride` in `application(_:didFinishLaunchingWithOptions:)`.
3. Set it to your desired provider:
```swift
let manualProviderOverride: String? = "recaptcha"
```
*Note: Remember to revert this change before committing.*
**Option B: Via Xcode Scheme (Recommended for Tests)**
This avoids modifying code and works for both running and testing.
1. In Xcode, go to **Product > Scheme > Edit Scheme...** (or press `⌘<`).
2. Select the **Run** or **Test** action in the left sidebar, depending on
what you are doing.
3. Go to the **Arguments** tab.
4. In the **Environment Variables** section, add:
* `APP_CHECK_PROVIDER`: Set to `recaptcha` or `debug`.
* `RECAPTCHA_SITE_KEY`: Set to your reCAPTCHA site key (required for
`recaptcha`).
### Running and Testing with CocoaPods
If you prefer to use the CocoaPods workflow instead of SPM:
#### 0. Clean Up State (Optional but Recommended)
If you are switching from the SPM workflow or encounter issues, it is
recommended to clean up the CocoaPods state first:
```bash
pod deintegrate FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcodeproj
rm -rf FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcworkspace
rm -f FirebaseAppCheck/Apps/FIRAppCheckTestApp/Podfile.lock
```
#### 1. Install Dependencies
To ensure a clean update and avoid conflicts with local development paths or
stale state, it is recommended to remove the existing `Pods` directory and
`Podfile.lock` before updating.
Run the following command from the repository root:
```bash
rm -rf FirebaseAppCheck/Apps/FIRAppCheckTestApp/Pods
rm -f FirebaseAppCheck/Apps/FIRAppCheckTestApp/Podfile.lock
FIREBASE_APP_CHECK_LOCAL_PATH="/path/to/your/local/app-check" pod update --repo-update --project-directory=FirebaseAppCheck/Apps/FIRAppCheckTestApp/
```
#### 2. Open Workspace
Open the generated CocoaPods workspace instead of the project file:
```bash
open FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcworkspace
```
#### 3. Remove SPM Dependencies (If needed)
By default, the project file is configured for SPM. To avoid duplicate symbol
issues or conflicting resolutions when using CocoaPods:
1. In Xcode, select the project in the file navigator.
2. Select the project file at the top (not a target).
3. Go to the **Package Dependencies** tab.
4. Remove the `firebase-ios-sdk` or `app-check` package references if they
appear there.
5. Also, select the `FIRAppCheckTestApp` target, go to the **General** tab,
and scroll down to **Frameworks, Libraries, and Embedded Content**.
6. Remove any SPM-resolved frameworks from this list.
#### 4. Configure and Run
You can configure the provider and site key either via the Xcode Scheme or by
passing environment variables to `xcodebuild`.
**Via Xcode Scheme:**
Follow the instructions in **[Running and Testing in Xcode](#running-and-testing-in-xcode)**.
**Via `xcodebuild` (Command Line):**
Run the following command from the repository root, replacing the site key with
your own:
```bash
export TEST_RUNNER_RECAPTCHA_SITE_KEY="your_site_key_here"
export TEST_RUNNER_APP_CHECK_PROVIDER="recaptcha"
SIM_ID=$(xcrun simctl list devices available | grep "iPhone" | grep -E -o '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}' | head -n 1)
xcodebuild test \
-workspace FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp.xcworkspace \
-scheme FIRAppCheckTestApp \
-destination "platform=iOS Simulator,id=$SIM_ID"
```
*(Note: See [Running Tests](#running-tests) for how to dynamically find a valid
simulator destination).*
## Project Structure
- **`FIRAppCheckTestAppTests`**: A hosted unit test target containing the test cases. It runs inside the app process to have access to the full app context.
@@ -7,45 +7,93 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
8F1202DD209F881D67D20BA8 /* FIRAppCheckTestAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DBE09F2A53CEC2B3E7600F4 /* FIRAppCheckTestAppTests.swift */; };
9AC7C27C2541C7E500F5DD80 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */; }; 9AC7C27C2541C7E500F5DD80 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */; };
9AC7C27E2541C7E500F5DD80 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */; }; 9AC7C27E2541C7E500F5DD80 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */; };
9AC7C2852541C7E600F5DD80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AC7C2842541C7E600F5DD80 /* Assets.xcassets */; }; 9AC7C2852541C7E600F5DD80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AC7C2842541C7E600F5DD80 /* Assets.xcassets */; };
9AC7C2882541C7E600F5DD80 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9AC7C2862541C7E600F5DD80 /* LaunchScreen.storyboard */; }; 9AC7C2882541C7E600F5DD80 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9AC7C2862541C7E600F5DD80 /* LaunchScreen.storyboard */; };
EAD122EF2DB97E10004D64C9 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = EAD122EE2DB97E10004D64C9 /* GoogleService-Info.plist */; }; CB1E711C5CFEE1D2BFB9069D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5A8784D39A5706A47E89D31 /* Foundation.framework */; };
EAA2C1AB2FAA966B008D663E /* RecaptchaEnterprise in Frameworks */ = {isa = PBXBuildFile; productRef = EAA2C1AA2FAA966B008D663E /* RecaptchaEnterprise */; };
EAA2C1AD2FABCB69008D663E /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = EAA2C1AC2FABCB69008D663E /* FirebaseStorage */; };
EABC1CB82FA9257600C35F73 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = EABC1CB72FA9257600C35F73 /* GoogleService-Info.plist */; };
EAD122F12DB98BD0004D64C9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD122F02DB98BCC004D64C9 /* ContentView.swift */; }; EAD122F12DB98BD0004D64C9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD122F02DB98BCC004D64C9 /* ContentView.swift */; };
EAD122F32DB9920D004D64C9 /* AppCheckTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD122F22DB99206004D64C9 /* AppCheckTestApp.swift */; }; EAD122F32DB9920D004D64C9 /* AppCheckTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD122F22DB99206004D64C9 /* AppCheckTestApp.swift */; };
EAD122F62DB9940E004D64C9 /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = EAD122F52DB9940E004D64C9 /* FirebaseAppCheck */; }; EAD122F62DB9940E004D64C9 /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = EAD122F52DB9940E004D64C9 /* FirebaseAppCheck */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
6394C6DF77B30A8569910AC4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9AC7C2702541C7E500F5DD80 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9AC7C2772541C7E500F5DD80;
remoteInfo = FIRAppCheckTestApp;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
2F8964B09A6746481927ACD1 /* FIRAppCheckTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FIRAppCheckTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FIRAppCheckTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FIRAppCheckTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; }; 9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
9AC7C2842541C7E600F5DD80 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 9AC7C2842541C7E600F5DD80 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9AC7C2872541C7E600F5DD80 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 9AC7C2872541C7E600F5DD80 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
9AC7C2892541C7E600F5DD80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 9AC7C2892541C7E600F5DD80 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
EAD122EE2DB97E10004D64C9 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; 9DBE09F2A53CEC2B3E7600F4 /* FIRAppCheckTestAppTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FIRAppCheckTestAppTests.swift; path = FIRAppCheckTestAppTests/FIRAppCheckTestAppTests.swift; sourceTree = "<group>"; };
A5A8784D39A5706A47E89D31 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
EABC1CB72FA9257600C35F73 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
EAD122F02DB98BCC004D64C9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; EAD122F02DB98BCC004D64C9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
EAD122F22DB99206004D64C9 /* AppCheckTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCheckTestApp.swift; sourceTree = "<group>"; }; EAD122F22DB99206004D64C9 /* AppCheckTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCheckTestApp.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
2D060508510F1138A9FC12DE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CB1E711C5CFEE1D2BFB9069D /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9AC7C2752541C7E500F5DD80 /* Frameworks */ = { 9AC7C2752541C7E500F5DD80 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EAD122F62DB9940E004D64C9 /* FirebaseAppCheck in Frameworks */, EAD122F62DB9940E004D64C9 /* FirebaseAppCheck in Frameworks */,
EAA2C1AB2FAA966B008D663E /* RecaptchaEnterprise in Frameworks */,
EAA2C1AD2FABCB69008D663E /* FirebaseStorage in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
4ABD2FB516E7A91E6E1527EA /* iOS */ = {
isa = PBXGroup;
children = (
A5A8784D39A5706A47E89D31 /* Foundation.framework */,
);
name = iOS;
sourceTree = "<group>";
};
671F07FAFFBA7B1FCBCABDBD /* Frameworks */ = {
isa = PBXGroup;
children = (
4ABD2FB516E7A91E6E1527EA /* iOS */,
);
name = Frameworks;
sourceTree = "<group>";
};
9AC7C26F2541C7E500F5DD80 = { 9AC7C26F2541C7E500F5DD80 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
9AC7C27A2541C7E500F5DD80 /* FIRAppCheckTestApp */, 9AC7C27A2541C7E500F5DD80 /* FIRAppCheckTestApp */,
9AC7C2792541C7E500F5DD80 /* Products */, 9AC7C2792541C7E500F5DD80 /* Products */,
671F07FAFFBA7B1FCBCABDBD /* Frameworks */,
9DBE09F2A53CEC2B3E7600F4 /* FIRAppCheckTestAppTests.swift */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -53,6 +101,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */, 9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */,
2F8964B09A6746481927ACD1 /* FIRAppCheckTestAppTests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -60,7 +109,7 @@
9AC7C27A2541C7E500F5DD80 /* FIRAppCheckTestApp */ = { 9AC7C27A2541C7E500F5DD80 /* FIRAppCheckTestApp */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EAD122EE2DB97E10004D64C9 /* GoogleService-Info.plist */, EABC1CB72FA9257600C35F73 /* GoogleService-Info.plist */,
9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */, 9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */,
9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */, 9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */,
EAD122F02DB98BCC004D64C9 /* ContentView.swift */, EAD122F02DB98BCC004D64C9 /* ContentView.swift */,
@@ -79,6 +128,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 9AC7C2A22541C7E600F5DD80 /* Build configuration list for PBXNativeTarget "FIRAppCheckTestApp" */; buildConfigurationList = 9AC7C2A22541C7E600F5DD80 /* Build configuration list for PBXNativeTarget "FIRAppCheckTestApp" */;
buildPhases = ( buildPhases = (
9AC7C2742541C7E500F5DD80 /* Sources */, 9AC7C2742541C7E500F5DD80 /* Sources */,
9AC7C2752541C7E500F5DD80 /* Frameworks */, 9AC7C2752541C7E500F5DD80 /* Frameworks */,
9AC7C2762541C7E500F5DD80 /* Resources */, 9AC7C2762541C7E500F5DD80 /* Resources */,
@@ -92,6 +142,24 @@
productReference = 9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */; productReference = 9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
DD8474378DDE4504369D6D2A /* FIRAppCheckTestAppTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3829FC5F4FEBE88FE48726F9 /* Build configuration list for PBXNativeTarget "FIRAppCheckTestAppTests" */;
buildPhases = (
DF4B7F4A1910F5A1E4CA3F06 /* Sources */,
2D060508510F1138A9FC12DE /* Frameworks */,
FD0227B5A3BEC9A7E811417E /* Resources */,
);
buildRules = (
);
dependencies = (
06C131FB9DE2B06EDC5A605D /* PBXTargetDependency */,
);
name = FIRAppCheckTestAppTests;
productName = FIRAppCheckTestAppTests;
productReference = 2F8964B09A6746481927ACD1 /* FIRAppCheckTestAppTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
@@ -116,13 +184,15 @@
); );
mainGroup = 9AC7C26F2541C7E500F5DD80; mainGroup = 9AC7C26F2541C7E500F5DD80;
packageReferences = ( packageReferences = (
EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "../../../../firebase-ios-sdk" */, EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "firebase-ios-sdk" */,
EAA2C1A92FAA966B008D663E /* XCRemoteSwiftPackageReference "recaptcha-enterprise-mobile-sdk" */,
); );
productRefGroup = 9AC7C2792541C7E500F5DD80 /* Products */; productRefGroup = 9AC7C2792541C7E500F5DD80 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
9AC7C2772541C7E500F5DD80 /* FIRAppCheckTestApp */, 9AC7C2772541C7E500F5DD80 /* FIRAppCheckTestApp */,
DD8474378DDE4504369D6D2A /* FIRAppCheckTestAppTests */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -133,13 +203,25 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
9AC7C2882541C7E600F5DD80 /* LaunchScreen.storyboard in Resources */, 9AC7C2882541C7E600F5DD80 /* LaunchScreen.storyboard in Resources */,
EAD122EF2DB97E10004D64C9 /* GoogleService-Info.plist in Resources */,
9AC7C2852541C7E600F5DD80 /* Assets.xcassets in Resources */, 9AC7C2852541C7E600F5DD80 /* Assets.xcassets in Resources */,
EABC1CB82FA9257600C35F73 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
FD0227B5A3BEC9A7E811417E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
9AC7C2742541C7E500F5DD80 /* Sources */ = { 9AC7C2742541C7E500F5DD80 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
@@ -152,8 +234,25 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
DF4B7F4A1910F5A1E4CA3F06 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8F1202DD209F881D67D20BA8 /* FIRAppCheckTestAppTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
06C131FB9DE2B06EDC5A605D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = FIRAppCheckTestApp;
target = 9AC7C2772541C7E500F5DD80 /* FIRAppCheckTestApp */;
targetProxy = 6394C6DF77B30A8569910AC4 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
9AC7C2862541C7E600F5DD80 /* LaunchScreen.storyboard */ = { 9AC7C2862541C7E600F5DD80 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@@ -290,6 +389,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = EQHXZ8M8AV; DEVELOPMENT_TEAM = EQHXZ8M8AV;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = EQHXZ8M8AV;
INFOPLIST_FILE = FIRAppCheckTestApp/Info.plist; INFOPLIST_FILE = FIRAppCheckTestApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -298,6 +398,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Firebase App Check Dev"; PROVISIONING_PROFILE_SPECIFIER = "Firebase App Check Dev";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Firebase App Check Dev";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
@@ -311,6 +412,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = EQHXZ8M8AV; DEVELOPMENT_TEAM = EQHXZ8M8AV;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = EQHXZ8M8AV;
INFOPLIST_FILE = FIRAppCheckTestApp/Info.plist; INFOPLIST_FILE = FIRAppCheckTestApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -319,14 +421,71 @@
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev; PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Firebase App Check Dev"; PROVISIONING_PROFILE_SPECIFIER = "Firebase App Check Dev";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Firebase App Check Dev";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
}; };
B539FF780C152B469B6CBAD9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = EQHXZ8M8AV;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev;
PRODUCT_NAME = FIRAppCheckTestAppTests;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Firebase App Check Dev";
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FIRAppCheckTestApp.app/FIRAppCheckTestApp";
WRAPPER_EXTENSION = xctest;
};
name = Debug;
};
F6B3E8BA0B5DC042AD569583 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = EQHXZ8M8AV;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev;
PRODUCT_NAME = FIRAppCheckTestAppTests;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Firebase App Check Dev";
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FIRAppCheckTestApp.app/FIRAppCheckTestApp";
VALIDATE_PRODUCT = YES;
WRAPPER_EXTENSION = xctest;
};
name = Release;
};
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
3829FC5F4FEBE88FE48726F9 /* Build configuration list for PBXNativeTarget "FIRAppCheckTestAppTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F6B3E8BA0B5DC042AD569583 /* Release */,
B539FF780C152B469B6CBAD9 /* Debug */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9AC7C2732541C7E500F5DD80 /* Build configuration list for PBXProject "FIRAppCheckTestApp" */ = { 9AC7C2732541C7E500F5DD80 /* Build configuration list for PBXProject "FIRAppCheckTestApp" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@@ -348,13 +507,34 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */ /* Begin XCLocalSwiftPackageReference section */
EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "../../../../firebase-ios-sdk" */ = { EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "firebase-ios-sdk" */ = {
isa = XCLocalSwiftPackageReference; isa = XCLocalSwiftPackageReference;
relativePath = "../../../../firebase-ios-sdk"; relativePath = "../../../../firebase-ios-sdk";
}; };
/* End XCLocalSwiftPackageReference section */ /* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
EAA2C1A92FAA966B008D663E /* XCRemoteSwiftPackageReference "recaptcha-enterprise-mobile-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/GoogleCloudPlatform/recaptcha-enterprise-mobile-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 18.9.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
EAA2C1AA2FAA966B008D663E /* RecaptchaEnterprise */ = {
isa = XCSwiftPackageProductDependency;
package = EAA2C1A92FAA966B008D663E /* XCRemoteSwiftPackageReference "recaptcha-enterprise-mobile-sdk" */;
productName = RecaptchaEnterprise;
};
EAA2C1AC2FABCB69008D663E /* FirebaseStorage */ = {
isa = XCSwiftPackageProductDependency;
package = EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseStorage;
};
EAD122F52DB9940E004D64C9 /* FirebaseAppCheck */ = { EAD122F52DB9940E004D64C9 /* FirebaseAppCheck */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = FirebaseAppCheck; productName = FirebaseAppCheck;
@@ -27,12 +27,13 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables> <Testables>
<TestableReference <TestableReference
skipped = "NO"> skipped = "NO">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "9AC7C28D2541C7E600F5DD80" BlueprintIdentifier = "DD8474378DDE4504369D6D2A"
BuildableName = "FIRAppCheckTestAppTests.xctest" BuildableName = "FIRAppCheckTestAppTests.xctest"
BlueprintName = "FIRAppCheckTestAppTests" BlueprintName = "FIRAppCheckTestAppTests"
ReferencedContainer = "container:FIRAppCheckTestApp.xcodeproj"> ReferencedContainer = "container:FIRAppCheckTestApp.xcodeproj">
@@ -16,31 +16,76 @@
import UIKit import UIKit
import AppCheckCore
import FirebaseAppCheck import FirebaseAppCheck
import FirebaseCore import FirebaseCore
import FirebaseStorage
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
private(set) static var shared: AppDelegate?
override init() {
super.init()
AppDelegate.shared = self
}
func application(_ application: UIApplication, func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication didFinishLaunchingWithOptions launchOptions: [UIApplication
.LaunchOptionsKey: Any]?) -> Bool { .LaunchOptionsKey: Any]?) -> Bool {
let providerFactory = AppCheckDebugProviderFactory() // Manual override for testing/debugging.
AppCheck.setAppCheckProviderFactory(providerFactory) // Change this to explicitly set a provider, or leave nil to use environment variable.
let manualProviderOverride: String? = nil // e.g., "debug" or "recaptcha"
FirebaseApp.configure() let options = setupAppCheck(overrideProvider: manualProviderOverride)
requestLimitedUseToken() FirebaseApp.configure(options: options)
requestDeviceCheckToken()
requestDebugToken()
if #available(iOS 14.0, *) {
requestAppAttestToken()
}
return true return true
} }
private func setupAppCheck(overrideProvider: String?) -> FirebaseOptions {
// Note: If running via `xcodebuild test`, pass this with the `TEST_RUNNER_` prefix
// (e.g., `TEST_RUNNER_APP_CHECK_PROVIDER="debug"`). Xcode strips the prefix at runtime.
let providerType = overrideProvider ?? ProcessInfo.processInfo
.environment["APP_CHECK_PROVIDER"] ?? "debug"
if overrideProvider == nil && ProcessInfo.processInfo.environment["APP_CHECK_PROVIDER"] == nil {
print("⚠️ Warning: APP_CHECK_PROVIDER environment variable is missing. Defaulting to 'debug'.")
}
print("Info: Using App Check provider: '\(providerType)'")
guard let options = FirebaseOptions.defaultOptions() else {
fatalError(
"Failed to load default Firebase options. Ensure GoogleService-Info.plist is added to the project."
)
}
let providerFactory: AppCheckProviderFactory
switch providerType {
case "recaptcha":
guard let siteKey = ProcessInfo.processInfo.environment["RECAPTCHA_SITE_KEY"],
!siteKey.isEmpty else {
fatalError(
"Error: RECAPTCHA_SITE_KEY environment variable is missing or empty. E2E tests require this key."
)
}
options.recaptchaSiteKey = siteKey
providerFactory = RecaptchaProviderFactory()
case "debug":
providerFactory = AppCheckDebugProviderFactory()
default:
print(
"Warning: Unknown APP_CHECK_PROVIDER '\(providerType)'. Falling back to Debug provider."
)
providerFactory = AppCheckDebugProviderFactory()
}
AppCheck.setAppCheckProviderFactory(providerFactory)
return options
}
// MARK: UISceneSession Lifecycle // MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, func application(_ application: UIApplication,
@@ -70,12 +115,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return return
} }
DeviceCheckProvider(app: firebaseApp)?.getToken { token, error in Task {
if let token { do {
print("DeviceCheck token: \(token.token), expiration date: \(token.expirationDate)") if let provider = DeviceCheckProvider(app: firebaseApp) {
} let token = try await provider.getToken()
print("DeviceCheck token: \(token.token), expiration date: \(token.expirationDate)")
if let error { }
} catch {
print("DeviceCheck error: \((error as NSError).userInfo)") print("DeviceCheck error: \((error as NSError).userInfo)")
} }
} }
@@ -89,12 +135,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let debugProvider = AppCheckDebugProvider(app: firebaseApp) { if let debugProvider = AppCheckDebugProvider(app: firebaseApp) {
print("Debug token: \(debugProvider.currentDebugToken())") print("Debug token: \(debugProvider.currentDebugToken())")
debugProvider.getToken { token, error in Task {
if let token { do {
let token = try await debugProvider.getToken()
print("Debug FAC token: \(token.token), expiration date: \(token.expirationDate)") print("Debug FAC token: \(token.token), expiration date: \(token.expirationDate)")
} } catch {
if let error {
print("Debug error: \(error)") print("Debug error: \(error)")
} }
} }
@@ -103,19 +148,47 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: App Check API // MARK: App Check API
func requestLimitedUseToken() { @discardableResult
AppCheck.appCheck().limitedUseToken { result, error in func fetchAppCheckToken(forcingRefresh: Bool = false) async throws -> AppCheckToken {
if let result { let token = try await AppCheck.appCheck().token(forcingRefresh: forcingRefresh)
print("FAC limited-use token: \(result.token), expiration date: \(result.expirationDate)")
}
if let error { let ttl = token.expirationDate.timeIntervalSinceNow
print("Error: \(String(describing: error))") print("[NON-LIMITED USE] Token: \(token.token)")
} print(" - Expiration date: \(token.expirationDate)")
print(" - TTL: \(Int(ttl)) seconds")
try await readFromStorage()
return token
}
func readFromStorage() async throws {
print("Attempting to read from Cloud Storage...")
let storage = Storage.storage()
let storageRef = storage.reference()
// NOTE: This path corresponds to the security rules configured for the test project.
// The rules allow public read on '/cep/ping'. If these rules change, this test may fail.
let pingRef = storageRef.child("cep/ping")
let data = try await pingRef.data(maxSize: 1 * 1024 * 1024)
// This shouldn't be possible, but we want to know if it ever happens.
guard let string = String(data: data, encoding: .utf8) else {
fatalError(
"Unexpected state: data is not valid UTF-8. This shouldn't happen, but we want to know if it does."
)
} }
print("Storage content: \(string)")
}
func requestLimitedUseToken() async throws -> String {
let result = try await AppCheck.appCheck().limitedUseToken()
print("[LIMITED USE] Token: \(result.token)")
print(" - Expiration date: \(result.expirationDate)")
return result.token
} }
@available(iOS 14.0, *)
func requestAppAttestToken() { func requestAppAttestToken() {
guard let firebaseApp = FirebaseApp.app() else { guard let firebaseApp = FirebaseApp.app() else {
return return
@@ -126,12 +199,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return return
} }
appAttestProvider.getToken { token, error in Task {
if let token { do {
let token = try await appAttestProvider.getToken()
print("App Attest FAC token: \(token.token), expiration date: \(token.expirationDate)") print("App Attest FAC token: \(token.token), expiration date: \(token.expirationDate)")
} } catch {
if let error {
print("App Attest error: \(error)") print("App Attest error: \(error)")
} }
} }
@@ -0,0 +1,52 @@
// 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 AppCheckCore
@testable import FIRAppCheckTestApp
import FirebaseAppCheck
import XCTest
final class FIRAppCheckTestAppTests: XCTestCase {
var appDelegate: AppDelegate!
@MainActor
override func setUp() async throws {
try await super.setUp()
appDelegate = try XCTUnwrap(AppDelegate.shared, "AppDelegate.shared is nil")
}
func testTokenAcquisitionAndStorageAccess() async throws {
let token = try await appDelegate.fetchAppCheckToken()
XCTAssertGreaterThan(token.expirationDate, Date(), "Token should not be expired")
}
func testLimitedUseTokenAcquisition() async throws {
let token = try await appDelegate.requestLimitedUseToken()
XCTAssertFalse(token.isEmpty, "Limited-use token should not be empty")
}
func testCacheWorks() async throws {
let token1 = try await appDelegate.fetchAppCheckToken().token
let token2 = try await appDelegate.fetchAppCheckToken().token
XCTAssertEqual(token1, token2, "Tokens should be identical (cached)")
}
func testForceRefresh() async throws {
let token1 = try await appDelegate.fetchAppCheckToken().token
let token2 = try await appDelegate.fetchAppCheckToken(forcingRefresh: true).token
XCTAssertNotEqual(token1, token2, "Tokens should be different after forced refresh")
}
}
@@ -11,4 +11,17 @@ target 'FIRAppCheckTestApp' do
pod 'FirebaseAppCheck', :path => '../../../' pod 'FirebaseAppCheck', :path => '../../../'
pod 'FirebaseCore', :path => '../../../' pod 'FirebaseCore', :path => '../../../'
# FirebaseStorage is used for testing AppCheck protected access.
pod 'FirebaseStorage', :path => '../../../'
# RecaptchaEnterprise is needed for Firebase App Check reCAPTCHA provider.
pod 'RecaptchaEnterprise'
# Use local AppCheckCore if the environment variable is set
if ENV['FIREBASE_APP_CHECK_LOCAL_PATH']
pod 'AppCheckCore', :path => ENV['FIREBASE_APP_CHECK_LOCAL_PATH']
end
target 'FIRAppCheckTestAppTests' do
inherit! :search_paths
end
end end
+12 -1
View File
@@ -1,11 +1,22 @@
# Unreleased # Unreleased
- [added] Added a reCAPTCHA attestation provider. Using this provider requires the
[reCAPTCHA Enterprise SDK to be installed](https://docs.cloud.google.com/recaptcha/docs/instrument-ios-apps#prepare-environment),
enabling the reCAPTCHA provider on the Firebase App Check console, and
replacing the local `GoogleService-Info.plist` with one redownloaded from
the project settings on the Firebase console.
- [changed] Updated `AppCheckDebugProvider` documentation to recommend the - [changed] Updated `AppCheckDebugProvider` documentation to recommend the
generic `AppCheckDebugToken` environment variable instead of the legacy generic `AppCheckDebugToken` environment variable instead of the legacy
`FIRAAppCheckDebugToken`. Note that `FIRAAppCheckDebugToken` remains `FIRAAppCheckDebugToken`. Note that `FIRAAppCheckDebugToken` remains
supported for backwards compatibility, with `AppCheckDebugToken` taking supported for backwards compatibility, with `AppCheckDebugToken` taking
priority if both are set. (#16230) priority if both are set. (#16230)
- [changed] The default App Check provider when running on a simulator is now - [changed] The default App Check provider when running on a simulator is now
the debug provider; physical devices continue to default to DeviceCheck. (#16190) the [debug provider](https://firebase.google.com/docs/app-check/ios/debug-provider);
physical devices now default to reCAPTCHA if the
[reCAPTCHA Enterprise SDK is
installed](https://docs.cloud.google.com/recaptcha/docs/instrument-ios-apps#prepare-environment),
and fall back to the
[DeviceCheck provider](https://firebase.google.com/docs/app-check/ios/devicecheck-provider)
otherwise. (#16190)
- [changed] Removed redundant debug token warning log. (#16197) - [changed] Removed redundant debug token warning log. (#16197)
- [changed] Log an actionable warning when debug token exchange fails. (#16232) - [changed] Log an actionable warning when debug token exchange fails. (#16232)
@@ -36,6 +36,16 @@ FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDebugToken;
// FIRDeviceCheckProvider.m // FIRDeviceCheckProvider.m
FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions; FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions;
// FIRRecaptchaProvider.m
FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageRecaptchaProviderIncompleteFIROptions;
FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageRecaptchaProviderMissingSiteKey;
FOUNDATION_EXPORT NSString *const
kFIRLoggerAppCheckMessageRecaptchaProviderMissingRecaptchaEnterpriseSDK;
// FIRDefaultProviderFactory.m
FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeRecaptchaFallbackToDeviceCheck;
FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDeviceCheckProviderUnavailable;
void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...); void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...);
GACAppCheckLogLevel FIRGetGACAppCheckLogLevel(void); GACAppCheckLogLevel FIRGetGACAppCheckLogLevel(void);
@@ -36,6 +36,16 @@ NSString *const kFIRLoggerAppCheckMessageCodeDebugToken = @"I-FAA005001";
// FIRDeviceCheckProvider.m // FIRDeviceCheckProvider.m
NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions = @"I-FAA006001"; NSString *const kFIRLoggerAppCheckMessageDeviceCheckProviderIncompleteFIROptions = @"I-FAA006001";
// FIRRecaptchaProvider.m
NSString *const kFIRLoggerAppCheckMessageRecaptchaProviderIncompleteFIROptions = @"I-FAA007001";
NSString *const kFIRLoggerAppCheckMessageRecaptchaProviderMissingSiteKey = @"I-FAA007002";
NSString *const kFIRLoggerAppCheckMessageRecaptchaProviderMissingRecaptchaEnterpriseSDK =
@"I-FAA007003";
// FIRDefaultProviderFactory.m
NSString *const kFIRLoggerAppCheckMessageCodeRecaptchaFallbackToDeviceCheck = @"I-FAA008001";
NSString *const kFIRLoggerAppCheckMessageCodeDeviceCheckProviderUnavailable = @"I-FAA008002";
#pragma mark - Log functions #pragma mark - Log functions
void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...) { void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...) {
@@ -14,9 +14,13 @@
#import "FirebaseAppCheck/Sources/DefaultProviderFactory/FIRDefaultProviderFactory.h" #import "FirebaseAppCheck/Sources/DefaultProviderFactory/FIRDefaultProviderFactory.h"
#import "FirebaseAppCheck/Sources/Core/FIRApp+AppCheck.h"
#import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProviderFactory.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProviderFactory.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProviderFactory.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProviderFactory.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaProviderFactory.h"
#import "FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaProvider+Internal.h"
@implementation FIRDefaultProviderFactory @implementation FIRDefaultProviderFactory
@@ -27,10 +31,32 @@
- (nullable id<FIRAppCheckProvider>)createProviderWithApp:(nonnull FIRApp *)app { - (nullable id<FIRAppCheckProvider>)createProviderWithApp:(nonnull FIRApp *)app {
#if TARGET_OS_SIMULATOR #if TARGET_OS_SIMULATOR
return [[[FIRAppCheckDebugProviderFactory alloc] init] createProviderWithApp:app]; return [[[FIRAppCheckDebugProviderFactory alloc] init] createProviderWithApp:app];
// TODO(ncooke3): Add elif case for future reCAPTCHA provider. #else // !TARGET_OS_SIMULATOR
#else
return [[[FIRDeviceCheckProviderFactory alloc] init] createProviderWithApp:app]; #if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
if (app.options.recaptchaSiteKey.length > 0) {
return [[[FIRRecaptchaProviderFactory alloc] init] createProviderWithApp:app];
} else {
FIRLogWarning(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageCodeRecaptchaFallbackToDeviceCheck,
@"reCAPTCHA Enterprise site key not found in Firebase options for app: %@. "
@"If you want to use reCAPTCHA, please ensure the provider is enabled in the "
@"Firebase Console and redownload your GoogleService-Info.plist. "
@"Default attestation provider is falling back to DeviceCheck. If DeviceCheck is "
@"not configured, App Check enforcement will fail.",
app.name);
}
#endif #endif
if (@available(watchOS 9.0, *)) {
return [[[FIRDeviceCheckProviderFactory alloc] init] createProviderWithApp:app];
} else {
FIRLogWarning(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageCodeDeviceCheckProviderUnavailable,
@"DeviceCheck is not supported on this device/OS version. "
@"App Check enforcement will fail.");
return nil;
}
#endif // TARGET_OS_SIMULATOR
} }
@end @end
@@ -0,0 +1,63 @@
/*
* 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
/// App Check provider that verifies app integrity using
/// [reCAPTCHA Enterprise for iOS](https://cloud.google.com/recaptcha/docs/instrument-ios-apps)
/// API.
NS_SWIFT_NAME(RecaptchaProvider)
API_AVAILABLE(ios(15.0), visionos(1.0))
API_UNAVAILABLE(macos, tvos, watchos, macCatalyst)
@interface FIRRecaptchaProvider : NSObject <FIRAppCheckProvider>
- (instancetype)init NS_UNAVAILABLE;
/// The default initializer.
/// @param app A `FirebaseApp` instance.
/// @return An instance of `RecaptchaProvider` if the provided
/// `FirebaseApp` instance contains all required parameters.
- (nullable instancetype)initWithApp:(FIRApp *)app;
/* Jazzy doesn't generate documentation for protocol-inherited
* methods, so this is copied over from the protocol declaration.
*/
/// Returns a new Firebase App Check token.
/// @param handler The completion handler. Make sure to call the handler with either a token
/// or an error.
- (void)getTokenWithCompletion:
(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler
NS_SWIFT_NAME(getToken(completion:));
/// Returns a new Firebase App Check token.
/// When implementing this method for your custom provider, the token returned should be suitable
/// for consumption in a limited-use scenario. If you do not implement this method, the
/// getTokenWithCompletion will be invoked instead whenever a limited-use token is requested.
/// @param handler The completion handler. Make sure to call the handler with either a token
/// or an error.
- (void)getLimitedUseTokenWithCompletion:
(void (^)(FIRAppCheckToken *_Nullable token, NSError *_Nullable error))handler
NS_SWIFT_NAME(getLimitedUseToken(completion:));
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,31 @@
/*
* 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
/// An implementation of `AppCheckProviderFactory` that creates a new instance of
/// `AppCheckRecaptchaProvider` when requested.
NS_SWIFT_NAME(RecaptchaProviderFactory)
API_AVAILABLE(ios(15.0), visionos(1.0))
API_UNAVAILABLE(macos, tvos, watchos, macCatalyst)
@interface FIRRecaptchaProviderFactory : NSObject <FIRAppCheckProviderFactory>
@end
NS_ASSUME_NONNULL_END
@@ -31,3 +31,7 @@
// App Attest provider. // App Attest provider.
#import "FIRAppAttestProvider.h" #import "FIRAppAttestProvider.h"
#import "FIRAppAttestProviderFactory.h" #import "FIRAppAttestProviderFactory.h"
// Recaptcha provider
#import "FIRRecaptchaProvider.h"
#import "FIRRecaptchaProviderFactory.h"
@@ -0,0 +1,30 @@
/*
* 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/FIRRecaptchaProvider.h"
NS_ASSUME_NONNULL_BEGIN
API_AVAILABLE(ios(15.0), visionos(1.0))
API_UNAVAILABLE(macos, tvos, watchos, macCatalyst)
@interface FIRRecaptchaProvider (Internal)
/// Returns `YES` if the reCAPTCHA Enterprise SDK is linked and available at runtime.
+ (BOOL)isSupported;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,156 @@
/*
* 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/FIRRecaptchaProvider.h"
#import "FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaProvider+Internal.h"
#import <AppCheckCore/AppCheckCore.h>
#if SWIFT_PACKAGE
@import AppCheckRecaptchaProvider;
#elif __has_include(<AppCheckCore/AppCheckCore-Swift.h>)
#import <AppCheckCore/AppCheckCore-Swift.h>
#elif __has_include("AppCheckCore-Swift.h")
// If frameworks are not available, fall back to importing the header as it
// should be findable from a header search path pointing to the build
// directory. See #12611 for more context.
#import "AppCheckCore-Swift.h"
#endif
#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"
@interface FIRRecaptchaProvider ()
@property(nonatomic, readonly) id<GACAppCheckProvider> recaptchaProvider;
- (instancetype)initWithRecaptchaProvider:(id<GACAppCheckProvider>)recaptchaProvider;
@end
@implementation FIRRecaptchaProvider
+ (BOOL)isSupported {
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
return [GACRecaptchaProvider isSupported];
#else
return NO;
#endif
}
- (nullable instancetype)initWithApp:(FIRApp *)app {
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
// 1. Validate options and raise exceptions on invalid configuration
NSString *siteKey = app.options.recaptchaSiteKey;
if (siteKey.length == 0) {
NSString *message = [NSString
stringWithFormat:
@"Cannot instantiate `RecaptchaProvider` for app: %@. "
@"`FirebaseOptions.recaptchaSiteKey` "
@"is missing or empty. "
@"Please ensure you have downloaded the latest `GoogleService-Info.plist` from the "
@"Firebase console or set `recaptchaSiteKey` on `FirebaseOptions` programmatically.",
app.name];
FIRLogError(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageRecaptchaProviderMissingSiteKey, @"%@",
message);
[NSException raise:NSInvalidArgumentException format:@"%@", message];
}
NSArray<NSString *> *missingOptionsFields =
[FIRAppCheckValidator tokenExchangeMissingFieldsInOptions:app.options];
if (missingOptionsFields.count > 0) {
FIRLogError(kFIRLoggerAppCheck, kFIRLoggerAppCheckMessageRecaptchaProviderIncompleteFIROptions,
@"Cannot instantiate `RecaptchaProvider` for app: %@. The following "
@"`FirebaseOptions` fields are "
@"missing: %@. "
@"Please ensure your `GoogleService-Info.plist` is complete or these fields are "
@"set on `FirebaseOptions` programmatically.",
app.name, [missingOptionsFields componentsJoinedByString:@", "]);
return nil;
}
// 2. Validate SDK Linkage
if (![FIRRecaptchaProvider isSupported]) {
NSString *message = [NSString
stringWithFormat:
@"Cannot instantiate `RecaptchaProvider` for app: %@. The reCAPTCHA Enterprise SDK "
@"is "
@"not linked. "
@"Please ensure you have installed the `FirebaseAppCheck` package along with "
@"the underlying reCAPTCHA Enterprise dependency. "
@"See "
@"https://cloud.google.com/recaptcha/docs/instrument-ios-apps#prepare-environment "
@"for details.",
app.name];
FIRLogError(kFIRLoggerAppCheck,
kFIRLoggerAppCheckMessageRecaptchaProviderMissingRecaptchaEnterpriseSDK, @"%@",
message);
[NSException raise:NSInternalInconsistencyException format:@"%@", message];
}
id heartbeatHook = [app.heartbeatLogger requestHook];
GACRecaptchaProvider *recaptchaProvider =
[[GACRecaptchaProvider alloc] initWithSiteKey:siteKey
resourceName:app.resourceName
APIKey:app.options.APIKey
requestHooks:heartbeatHook ? @[ heartbeatHook ] : @[]];
return [self initWithRecaptchaProvider:recaptchaProvider];
#else
return nil;
#endif
}
- (instancetype)initWithRecaptchaProvider:(id<GACAppCheckProvider>)recaptchaProvider {
self = [super init];
if (self) {
_recaptchaProvider = recaptchaProvider;
}
return self;
}
#pragma mark - FIRAppCheckProvider
- (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
NSError *_Nullable error))handler {
[self.recaptchaProvider 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.recaptchaProvider getLimitedUseTokenWithCompletion:^(
GACAppCheckToken *_Nullable internalToken, NSError *_Nullable error) {
if (error) {
handler(nil, error);
return;
}
handler([[FIRAppCheckToken alloc] initWithInternalToken:internalToken], nil);
}];
}
@end
@@ -0,0 +1,31 @@
/*
* 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/FIRRecaptchaProviderFactory.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaProvider.h"
@implementation FIRRecaptchaProviderFactory
- (nullable id<FIRAppCheckProvider>)createProviderWithApp:(nonnull FIRApp *)app {
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
return [[FIRRecaptchaProvider alloc] initWithApp:app];
#else
return nil;
#endif
}
@end
@@ -18,34 +18,118 @@
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckAvailability.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckDebugProvider.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProvider.h" #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProvider.h"
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaProvider.h"
#import <OCMock/OCMock.h>
#import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#import "FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaProvider+Internal.h"
FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
@interface FIRDefaultProviderFactoryTests : XCTestCase @interface FIRDefaultProviderFactoryTests : XCTestCase
@end @end
@implementation FIRDefaultProviderFactoryTests @implementation FIRDefaultProviderFactoryTests
- (void)testCreateProviderWithApp { - (FIRApp *)mockAppWithRecaptchaSiteKey:(nullable NSString *)siteKey {
FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"]; FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"];
options.APIKey = @"api_key"; options.APIKey = @"api_key";
options.projectID = @"project_id"; options.projectID = @"project_id";
if (siteKey) {
options.recaptchaSiteKey = siteKey;
}
FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options]; FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options];
// The following disables automatic token refresh, which could interfere with tests.
app.dataCollectionDefaultEnabled = NO; app.dataCollectionDefaultEnabled = NO;
return app;
}
- (void)testCreateProvider_Simulator {
#if TARGET_OS_SIMULATOR
FIRApp *app = [self mockAppWithRecaptchaSiteKey:nil];
FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init]; FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init];
id<FIRAppCheckProvider> provider = [factory createProviderWithApp:app]; id<FIRAppCheckProvider> provider = [factory createProviderWithApp:app];
XCTAssertNotNil(provider); XCTAssertNotNil(provider);
XCTAssert([provider isKindOfClass:[FIRAppCheckDebugProvider class]]);
#endif
}
- (void)testCreateProvider_Device_RecaptchaLinked {
#if ((TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION) && !TARGET_OS_SIMULATOR
FIRApp *app = [self mockAppWithRecaptchaSiteKey:@"site_key"];
id recaptchaMock = OCMClassMock([FIRRecaptchaProvider class]);
OCMStub([recaptchaMock isSupported]).andReturn(YES);
FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init];
id<FIRAppCheckProvider> provider = [factory createProviderWithApp:app];
XCTAssertNotNil(provider);
XCTAssert([provider isKindOfClass:[FIRRecaptchaProvider class]]);
[recaptchaMock stopMocking];
#endif
}
- (void)testCreateProvider_Device_RecaptchaNotLinked {
#if !TARGET_OS_SIMULATOR
FIRApp *app = [self mockAppWithRecaptchaSiteKey:nil];
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
id recaptchaMock = OCMClassMock([FIRRecaptchaProvider class]);
OCMStub([recaptchaMock isSupported]).andReturn(NO);
#endif
FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init];
id<FIRAppCheckProvider> provider = [factory createProviderWithApp:app];
XCTAssertNotNil(provider);
XCTAssert([provider isKindOfClass:[FIRDeviceCheckProvider class]]);
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
[recaptchaMock stopMocking];
#endif
#endif
}
- (void)testCreateProvider_Device_RecaptchaNotLinked_WithSiteKey_Throws {
#if ((TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION) && !TARGET_OS_SIMULATOR
FIRApp *app = [self mockAppWithRecaptchaSiteKey:@"site_key"];
id recaptchaMock = OCMClassMock([FIRRecaptchaProvider class]);
OCMStub([recaptchaMock isSupported]).andReturn(NO);
FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init];
XCTAssertThrows([factory createProviderWithApp:app]);
[recaptchaMock stopMocking];
#endif
}
- (void)testCreateProviderWithApp_PublicAPI {
// Verifies that the public API doesn't crash and returns a provider.
FIRApp *app = [self mockAppWithRecaptchaSiteKey:nil];
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
id recaptchaMock = OCMClassMock([FIRRecaptchaProvider class]);
OCMStub([recaptchaMock isSupported]).andReturn(NO);
#endif
FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init];
id<FIRAppCheckProvider> provider = [factory createProviderWithApp:app];
XCTAssertNotNil(provider);
#if TARGET_OS_SIMULATOR #if TARGET_OS_SIMULATOR
XCTAssert([provider isKindOfClass:[FIRAppCheckDebugProvider class]]); XCTAssert([provider isKindOfClass:[FIRAppCheckDebugProvider class]]);
#else #else
XCTAssert([provider isKindOfClass:[FIRDeviceCheckProvider class]]); XCTAssert([provider isKindOfClass:[FIRDeviceCheckProvider class]]);
#endif #endif
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
[recaptchaMock stopMocking];
#endif
} }
@end @end
@@ -0,0 +1,15 @@
// 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 "SharedTestUtilities/ExceptionCatcher.h"
@@ -0,0 +1,36 @@
// 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.
#if os(iOS) && !targetEnvironment(macCatalyst) || os(visionOS)
import AppCheckCore
import FirebaseAppCheck
/// Internal Objective-C interface for test helper methods.
@objc protocol RecaptchaProviderTesting {
@objc(initWithRecaptchaProvider:)
func initWithRecaptchaProvider(_ recaptchaProvider: AppCheckCoreProvider) -> RecaptchaProvider
}
@objc extension RecaptchaProvider {
/// Safe, compile-time checked test helper that bypasses production validation checks.
class func testInstance(recaptchaProvider: AppCheckCoreProvider) -> RecaptchaProvider {
let providerClass = RecaptchaProvider.self as AnyObject
let allocated = providerClass.perform(NSSelectorFromString("alloc")).takeUnretainedValue()
let uninitialized = unsafeBitCast(allocated, to: RecaptchaProviderTesting.self)
return uninitialized.initWithRecaptchaProvider(recaptchaProvider)
}
}
#endif
@@ -0,0 +1,216 @@
// 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.
#if os(iOS) && !targetEnvironment(macCatalyst) || os(visionOS)
import AppCheckCore
import FirebaseAppCheck
import FirebaseCore
#if SWIFT_PACKAGE
import SharedTestUtilities
#endif
import XCTest
class FakeInternalProvider: NSObject, AppCheckCoreProvider {
var stubbedToken: AppCheckCoreToken?
var stubbedError: Error?
@objc(getTokenWithCompletion:)
func getToken(completion handler: @escaping (AppCheckCoreToken?, Error?) -> Void) {
handler(stubbedToken, stubbedError)
}
@objc(getLimitedUseTokenWithCompletion:)
func getLimitedUseToken(completion handler: @escaping (AppCheckCoreToken?, Error?) -> Void) {
handler(stubbedToken, stubbedError)
}
}
final class RecaptchaProviderTests: XCTestCase {
var provider: RecaptchaProvider!
var fakeInternalProvider: FakeInternalProvider!
override func setUp() {
super.setUp()
fakeInternalProvider = FakeInternalProvider()
provider = RecaptchaProvider.testInstance(recaptchaProvider: fakeInternalProvider)
}
override func tearDown() {
provider = nil
fakeInternalProvider = nil
super.tearDown()
}
func testInitWithIncompleteApp() {
let options = FirebaseOptions(googleAppID: "1:123456789:ios:abc123", gcmSenderID: "sender_id")
options.projectID = "project_id"
options.recaptchaSiteKey = "test_site_key"
let appName = "testInitWithIncompleteApp1"
let missingAPIKeyApp: FirebaseApp
if let existingApp = FirebaseApp.app(name: appName) {
missingAPIKeyApp = existingApp
} else {
FirebaseApp.configure(name: appName, options: options)
missingAPIKeyApp = FirebaseApp.app(name: appName)!
}
missingAPIKeyApp.isDataCollectionDefaultEnabled = false
XCTAssertNil(RecaptchaProvider(app: missingAPIKeyApp))
options.projectID = nil
options.apiKey = "api_key"
options.recaptchaSiteKey = "test_site_key"
let appName2 = "testInitWithIncompleteApp2"
let missingProjectIDApp: FirebaseApp
if let existingApp = FirebaseApp.app(name: appName2) {
missingProjectIDApp = existingApp
} else {
FirebaseApp.configure(name: appName2, options: options)
missingProjectIDApp = FirebaseApp.app(name: appName2)!
}
missingProjectIDApp.isDataCollectionDefaultEnabled = false
XCTAssertNil(RecaptchaProvider(app: missingProjectIDApp))
}
func testInitWithMissingSiteKey() {
let options = FirebaseOptions(googleAppID: "1:123456789:ios:abc123", gcmSenderID: "sender_id")
options.apiKey = "api_key"
options.projectID = "project_id"
// options.recaptchaSiteKey is nil
let appName = "testInitWithMissingSiteKey"
let app: FirebaseApp
if let existingApp = FirebaseApp.app(name: appName) {
app = existingApp
} else {
FirebaseApp.configure(name: appName, options: options)
app = FirebaseApp.app(name: appName)!
}
app.isDataCollectionDefaultEnabled = false
XCTAssertThrowsError(try ExceptionCatcher.catchException {
_ = RecaptchaProvider(app: app)
}) { error in
let nsError = error as NSError
XCTAssertEqual(nsError.domain, NSExceptionName.invalidArgumentException.rawValue)
XCTAssertEqual(nsError.code, -114)
XCTAssertTrue((nsError.userInfo["ExceptionReason"] as? String)?
.contains("recaptchaSiteKey") ?? false)
}
}
func testInitWithMissingSDKThrows() {
let options = FirebaseOptions(googleAppID: "1:123456789:ios:abc123", gcmSenderID: "sender_id")
options.apiKey = "api_key"
options.projectID = "project_id"
options.recaptchaSiteKey = "test_site_key"
let appName = "testInitWithMissingSDKThrows"
let app: FirebaseApp
if let existingApp = FirebaseApp.app(name: appName) {
app = existingApp
} else {
FirebaseApp.configure(name: appName, options: options)
app = FirebaseApp.app(name: appName)!
}
app.isDataCollectionDefaultEnabled = false
XCTAssertThrowsError(try ExceptionCatcher.catchException {
_ = RecaptchaProvider(app: app)
}) { error in
let nsError = error as NSError
XCTAssertEqual(nsError.domain, NSExceptionName.internalInconsistencyException.rawValue)
XCTAssertEqual(nsError.code, -114)
XCTAssertTrue((nsError.userInfo["ExceptionReason"] as? String)?
.contains("reCAPTCHA Enterprise SDK is not linked") ?? false)
}
}
func testGetTokenSuccess() {
let date = Date()
let validInternalToken = AppCheckCoreToken(
token: "valid_token",
expirationDate: date,
receivedAt: date
)
fakeInternalProvider.stubbedToken = validInternalToken
let expectation = self.expectation(description: "getToken")
provider.getToken { token, error in
XCTAssertEqual(token?.token, validInternalToken.token)
XCTAssertEqual(token?.expirationDate, validInternalToken.expirationDate)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func testGetTokenAPIError() {
let expectedError = NSError(domain: "testGetTokenAPIError", code: -1, userInfo: nil)
fakeInternalProvider.stubbedError = expectedError
let expectation = self.expectation(description: "getTokenError")
provider.getToken { token, error in
XCTAssertNil(token)
XCTAssertEqual(error as NSError?, expectedError)
expectation.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func testGetLimitedUseTokenSuccess() {
let date = Date()
let validInternalToken = AppCheckCoreToken(
token: "TEST_ValidToken",
expirationDate: date,
receivedAt: date
)
fakeInternalProvider.stubbedToken = validInternalToken
let expectation = self.expectation(description: "getLimitedUseToken")
provider.getLimitedUseToken { token, error in
XCTAssertEqual(token?.token, validInternalToken.token)
XCTAssertEqual(token?.expirationDate, validInternalToken.expirationDate)
XCTAssertNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
func testGetLimitedUseTokenProviderError() {
let expectedError = NSError(domain: "TEST_LimitedUseToken_Error", code: -1, userInfo: nil)
fakeInternalProvider.stubbedError = expectedError
let expectation = self.expectation(description: "getLimitedUseTokenError")
provider.getLimitedUseToken { token, error in
XCTAssertNil(token)
XCTAssertEqual(error as NSError?, expectedError)
expectation.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
}
}
#endif
+10
View File
@@ -29,6 +29,7 @@ NSString *const kFIRStorageBucket = @"STORAGE_BUCKET";
NSString *const kFIRBundleID = @"BUNDLE_ID"; NSString *const kFIRBundleID = @"BUNDLE_ID";
// The key to locate the project identifier in the plist file. // The key to locate the project identifier in the plist file.
NSString *const kFIRProjectID = @"PROJECT_ID"; NSString *const kFIRProjectID = @"PROJECT_ID";
NSString *const kFIRRecaptchaSiteKey = @"RECAPTCHA_SITE_KEY";
NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED"; NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED";
NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED"; NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED";
@@ -306,6 +307,15 @@ static dispatch_once_t sDefaultOptionsDictionaryOnceToken;
_optionsDictionary[kFIRStorageBucket] = [storageBucket copy]; _optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
} }
- (NSString *)recaptchaSiteKey {
return self.optionsDictionary[kFIRRecaptchaSiteKey];
}
- (void)setRecaptchaSiteKey:(NSString *)recaptchaSiteKey {
[self checkEditingLocked];
_optionsDictionary[kFIRRecaptchaSiteKey] = [recaptchaSiteKey copy];
}
- (NSString *)bundleID { - (NSString *)bundleID {
return self.optionsDictionary[kFIRBundleID]; return self.optionsDictionary[kFIRBundleID];
} }
@@ -82,6 +82,11 @@ NS_SWIFT_NAME(FirebaseOptions)
*/ */
@property(nonatomic, copy, nullable) NSString *appGroupID; @property(nonatomic, copy, nullable) NSString *appGroupID;
/**
* The reCAPTCHA site key used by App Check.
*/
@property(nonatomic, copy, nullable) NSString *recaptchaSiteKey;
/** /**
* Initializes a customized instance of FirebaseOptions from the file at the given plist file path. * Initializes a customized instance of FirebaseOptions from the file at the given plist file path.
* This will read the file synchronously from disk. * This will read the file synchronously from disk.
+13
View File
@@ -154,6 +154,19 @@ extern NSString *const kFIRLibraryVersionID;
XCTAssertFalse(options.usingOptionsFromDefaultPlist); XCTAssertFalse(options.usingOptionsFromDefaultPlist);
} }
- (void)testRecaptchaSiteKey {
NSString *siteKey = @"placeholder_site_key";
NSDictionary *optionsDictionary = @{@"RECAPTCHA_SITE_KEY" : siteKey};
FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
XCTAssertEqualObjects(options.recaptchaSiteKey, siteKey);
}
- (void)testSetRecaptchaSiteKey {
FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"];
options.recaptchaSiteKey = @"manual_site_key";
XCTAssertEqualObjects(options.recaptchaSiteKey, @"manual_site_key");
}
- (void)assertOptionsMatchDefaults:(FIROptions *)options andProjectID:(BOOL)matchProjectID { - (void)assertOptionsMatchDefaults:(FIROptions *)options andProjectID:(BOOL)matchProjectID {
XCTAssertEqualObjects(options.googleAppID, kGoogleAppID); XCTAssertEqualObjects(options.googleAppID, kGoogleAppID);
XCTAssertEqualObjects(options.APIKey, kAPIKey); XCTAssertEqualObjects(options.APIKey, kAPIKey);
+22 -2
View File
@@ -172,8 +172,7 @@ let package = Package(
url: "https://github.com/google/interop-ios-for-google-sdks.git", url: "https://github.com/google/interop-ios-for-google-sdks.git",
"101.0.0" ..< "102.0.0" "101.0.0" ..< "102.0.0"
), ),
.package(url: "https://github.com/google/app-check.git", appCheckDependency(),
"11.0.1" ..< "12.0.0"),
], ],
targets: [ targets: [
.target( .target(
@@ -945,6 +944,7 @@ let package = Package(
.target( .target(
name: "SharedTestUtilities", name: "SharedTestUtilities",
dependencies: ["FirebaseCore", dependencies: ["FirebaseCore",
"FirebaseCoreExtension",
"FirebaseAppCheckInterop", "FirebaseAppCheckInterop",
"FirebaseAuthInterop", "FirebaseAuthInterop",
"FirebaseMessagingInterop", "FirebaseMessagingInterop",
@@ -1268,6 +1268,7 @@ let package = Package(
"FirebaseAppCheckInterop", "FirebaseAppCheckInterop",
"FirebaseCore", "FirebaseCore",
.product(name: "AppCheckCore", package: "app-check"), .product(name: "AppCheckCore", package: "app-check"),
.product(name: "AppCheckRecaptchaProvider", package: "app-check"),
.product(name: "GULEnvironment", package: "GoogleUtilities"), .product(name: "GULEnvironment", package: "GoogleUtilities"),
.product(name: "GULUserDefaults", package: "GoogleUtilities"), .product(name: "GULUserDefaults", package: "GoogleUtilities"),
], ],
@@ -1316,8 +1317,13 @@ let package = Package(
dependencies: [ dependencies: [
"FirebaseAppCheck", "FirebaseAppCheck",
"FirebaseCoreExtension", "FirebaseCoreExtension",
"SharedTestUtilities",
.product(name: "AppCheckCore", package: "app-check"),
], ],
path: "FirebaseAppCheck/Tests/Unit/Swift", path: "FirebaseAppCheck/Tests/Unit/Swift",
cSettings: [
.headerSearchPath("../../../"),
],
swiftSettings: [ swiftSettings: [
.swiftLanguageMode(SwiftLanguageMode.v5), .swiftLanguageMode(SwiftLanguageMode.v5),
] ]
@@ -1675,3 +1681,17 @@ func isFoundationModelsSupportedPlatformSwiftSetting() -> SwiftSetting {
.when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS]) .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.3.0" ..< "12.0.0")
}
+4
View File
@@ -16,7 +16,11 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#if SWIFT_PACKAGE
#import <FirebaseAuthInterop/FIRAuthInterop.h>
#else
#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h"
#endif
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
+4
View File
@@ -16,7 +16,11 @@
#import "SharedTestUtilities/FIRAuthInteropFake.h" #import "SharedTestUtilities/FIRAuthInteropFake.h"
#if SWIFT_PACKAGE
#import <FirebaseAuthInterop/FIRAuthInterop.h>
#else
#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h"
#endif
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -16,7 +16,11 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#if SWIFT_PACKAGE
#import <FirebaseCoreInternal.h>
#else
#import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#endif
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -16,7 +16,11 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#if SWIFT_PACKAGE
#import <FIRMessagingInterop.h>
#else
#import "FirebaseMessaging/Interop/FIRMessagingInterop.h" #import "FirebaseMessaging/Interop/FIRMessagingInterop.h"
#endif
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@@ -22,7 +22,11 @@
#import <SafariServices/SafariServices.h> #import <SafariServices/SafariServices.h>
#endif #endif
#if SWIFT_PACKAGE
#import <FirebaseCoreInternal.h>
#else
#import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#endif
NSString *const kGoogleAppIDPlistKey = @"GOOGLE_APP_ID"; NSString *const kGoogleAppIDPlistKey = @"GOOGLE_APP_ID";
// Dummy plist GOOGLE_APP_ID // Dummy plist GOOGLE_APP_ID
+6 -2
View File
@@ -135,8 +135,12 @@ private func checkFile(_ file: String, logger: ErrorLogger, inRepo repoURL: URL,
let importFile = line.components(separatedBy: " ")[1] let importFile = line.components(separatedBy: " ")[1]
if inSwiftPackageElse { if inSwiftPackageElse {
if importFile.first != "<" { if importFile.first != "<" {
logger // SharedTestUtilities files are included directly in test targets and
.importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum) // use repo-relative imports.
if !file.contains("SharedTestUtilities/") {
logger
.importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum)
}
} }
continue continue
} }