mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
feat(appcheck): implement RecaptchaEnterpriseProvider and tests (#16155)
Co-authored-by: Paul Beusterien <paulbeusterien@google.com>
This commit is contained in:
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"}'
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
+186
-6
@@ -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;
|
||||||
|
|||||||
+2
-1
@@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
@@ -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
|
||||||
@@ -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
|
||||||
+86
-2
@@ -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
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user