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:
|
||||
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
@@ -44,6 +45,7 @@ jobs:
|
||||
method: xcodebuild
|
||||
os: ${{ matrix.os }}
|
||||
xcode: ${{ matrix.xcode }}
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
|
||||
client-app-spm-source-firestore:
|
||||
strategy:
|
||||
@@ -63,6 +65,7 @@ jobs:
|
||||
os: ${{ matrix.os }}
|
||||
xcode: ${{ matrix.xcode }}
|
||||
setup_command: echo "FIREBASE_SOURCE_FIRESTORE=1" >> $GITHUB_ENV
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
|
||||
client-app-cocoapods:
|
||||
strategy:
|
||||
@@ -80,3 +83,4 @@ jobs:
|
||||
os: ${{ matrix.os }}
|
||||
xcode: ${{ matrix.xcode }}
|
||||
setup_command: scripts/install_prereqs.sh ClientApp iOS xcodebuild
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
@@ -29,6 +29,7 @@ jobs:
|
||||
spm-package-resolved:
|
||||
env:
|
||||
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
runs-on: macos-26
|
||||
outputs:
|
||||
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.
|
||||
if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request'
|
||||
needs: [spm-package-resolved]
|
||||
env:
|
||||
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -105,6 +109,8 @@ jobs:
|
||||
# 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'
|
||||
needs: [spm-package-resolved]
|
||||
env:
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -144,6 +150,8 @@ jobs:
|
||||
# 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'
|
||||
needs: [spm-package-resolved]
|
||||
env:
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
strategy:
|
||||
matrix:
|
||||
# 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
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
|
||||
catalyst:
|
||||
uses: ./.github/workflows/_catalyst.yml
|
||||
@@ -57,6 +58,7 @@ jobs:
|
||||
method: spm
|
||||
sanitizers: ${{ matrix.diagnostic }}
|
||||
setup_command: scripts/setup_spm_tests.sh
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
|
||||
app_check-cron-only:
|
||||
needs: pod_lib_lint
|
||||
|
||||
@@ -28,6 +28,7 @@ jobs:
|
||||
uses: ./.github/workflows/_spm.yml
|
||||
with:
|
||||
target: CoreUnit
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
|
||||
catalyst:
|
||||
uses: ./.github/workflows/_catalyst.yml
|
||||
|
||||
@@ -540,6 +540,7 @@ jobs:
|
||||
env:
|
||||
FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
|
||||
FIREBASE_SOURCE_FIRESTORE: 1
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
outputs:
|
||||
cache_key: ${{ steps.generate_cache_key.outputs.cache_key }}
|
||||
steps:
|
||||
@@ -591,6 +592,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
FIREBASE_SOURCE_FIRESTORE: 1
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Xcode
|
||||
@@ -614,6 +616,7 @@ jobs:
|
||||
with:
|
||||
target: FirebaseFirestoreTests
|
||||
platforms: iOS
|
||||
env_vars: '{"FIREBASE_APP_CHECK_BRANCH": "main"}'
|
||||
|
||||
spm-source-cron:
|
||||
# Don't run on private repo.
|
||||
@@ -624,6 +627,7 @@ jobs:
|
||||
target: [tvOS, macOS, catalyst]
|
||||
env:
|
||||
FIREBASE_SOURCE_FIRESTORE: 1
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Xcode
|
||||
@@ -648,6 +652,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
target: [tvOS, macOS, catalyst]
|
||||
env:
|
||||
FIREBASE_APP_CHECK_BRANCH: main
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
- name: Xcode
|
||||
|
||||
@@ -44,7 +44,7 @@ Pod::Spec.new do |s|
|
||||
s.osx.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 'FirebaseCore', '~> 12.15.0'
|
||||
s.dependency 'GoogleUtilities/Environment', '~> 8.1'
|
||||
@@ -93,7 +93,11 @@ Pod::Spec.new do |s|
|
||||
}
|
||||
swift_unit_tests.source_files = [
|
||||
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'
|
||||
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 = {
|
||||
|
||||
/* 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 */; };
|
||||
9AC7C27E2541C7E500F5DD80 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */; };
|
||||
9AC7C2852541C7E600F5DD80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AC7C2842541C7E600F5DD80 /* Assets.xcassets */; };
|
||||
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 */; };
|
||||
EAD122F32DB9920D004D64C9 /* AppCheckTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD122F22DB99206004D64C9 /* AppCheckTestApp.swift */; };
|
||||
EAD122F62DB9940E004D64C9 /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = EAD122F52DB9940E004D64C9 /* FirebaseAppCheck */; };
|
||||
/* 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 */
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
EAD122F22DB99206004D64C9 /* AppCheckTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCheckTestApp.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
2D060508510F1138A9FC12DE /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CB1E711C5CFEE1D2BFB9069D /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
9AC7C2752541C7E500F5DD80 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EAD122F62DB9940E004D64C9 /* FirebaseAppCheck in Frameworks */,
|
||||
EAA2C1AB2FAA966B008D663E /* RecaptchaEnterprise in Frameworks */,
|
||||
EAA2C1AD2FABCB69008D663E /* FirebaseStorage in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase 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 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9AC7C27A2541C7E500F5DD80 /* FIRAppCheckTestApp */,
|
||||
9AC7C2792541C7E500F5DD80 /* Products */,
|
||||
671F07FAFFBA7B1FCBCABDBD /* Frameworks */,
|
||||
9DBE09F2A53CEC2B3E7600F4 /* FIRAppCheckTestAppTests.swift */,
|
||||
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -53,6 +101,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */,
|
||||
2F8964B09A6746481927ACD1 /* FIRAppCheckTestAppTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -60,7 +109,7 @@
|
||||
9AC7C27A2541C7E500F5DD80 /* FIRAppCheckTestApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAD122EE2DB97E10004D64C9 /* GoogleService-Info.plist */,
|
||||
EABC1CB72FA9257600C35F73 /* GoogleService-Info.plist */,
|
||||
9AC7C27B2541C7E500F5DD80 /* AppDelegate.swift */,
|
||||
9AC7C27D2541C7E500F5DD80 /* SceneDelegate.swift */,
|
||||
EAD122F02DB98BCC004D64C9 /* ContentView.swift */,
|
||||
@@ -79,6 +128,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 9AC7C2A22541C7E600F5DD80 /* Build configuration list for PBXNativeTarget "FIRAppCheckTestApp" */;
|
||||
buildPhases = (
|
||||
|
||||
9AC7C2742541C7E500F5DD80 /* Sources */,
|
||||
9AC7C2752541C7E500F5DD80 /* Frameworks */,
|
||||
9AC7C2762541C7E500F5DD80 /* Resources */,
|
||||
@@ -92,6 +142,24 @@
|
||||
productReference = 9AC7C2782541C7E500F5DD80 /* FIRAppCheckTestApp.app */;
|
||||
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 */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -116,13 +184,15 @@
|
||||
);
|
||||
mainGroup = 9AC7C26F2541C7E500F5DD80;
|
||||
packageReferences = (
|
||||
EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "../../../../firebase-ios-sdk" */,
|
||||
EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "firebase-ios-sdk" */,
|
||||
EAA2C1A92FAA966B008D663E /* XCRemoteSwiftPackageReference "recaptcha-enterprise-mobile-sdk" */,
|
||||
);
|
||||
productRefGroup = 9AC7C2792541C7E500F5DD80 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
9AC7C2772541C7E500F5DD80 /* FIRAppCheckTestApp */,
|
||||
DD8474378DDE4504369D6D2A /* FIRAppCheckTestAppTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -133,13 +203,25 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9AC7C2882541C7E600F5DD80 /* LaunchScreen.storyboard in Resources */,
|
||||
EAD122EF2DB97E10004D64C9 /* GoogleService-Info.plist in Resources */,
|
||||
9AC7C2852541C7E600F5DD80 /* Assets.xcassets in Resources */,
|
||||
EABC1CB82FA9257600C35F73 /* GoogleService-Info.plist in Resources */,
|
||||
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FD0227B5A3BEC9A7E811417E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
9AC7C2742541C7E500F5DD80 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@@ -152,8 +234,25 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DF4B7F4A1910F5A1E4CA3F06 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8F1202DD209F881D67D20BA8 /* FIRAppCheckTestAppTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
06C131FB9DE2B06EDC5A605D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = FIRAppCheckTestApp;
|
||||
target = 9AC7C2772541C7E500F5DD80 /* FIRAppCheckTestApp */;
|
||||
targetProxy = 6394C6DF77B30A8569910AC4 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
9AC7C2862541C7E600F5DD80 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -290,6 +389,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = EQHXZ8M8AV;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = EQHXZ8M8AV;
|
||||
INFOPLIST_FILE = FIRAppCheckTestApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -298,6 +398,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Firebase App Check Dev";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Firebase App Check Dev";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -311,6 +412,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEVELOPMENT_TEAM = EQHXZ8M8AV;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = EQHXZ8M8AV;
|
||||
INFOPLIST_FILE = FIRAppCheckTestApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -319,14 +421,71 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.appcheck.testapp.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "Firebase App Check Dev";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Firebase App Check Dev";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
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 */
|
||||
|
||||
/* 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" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -348,13 +507,34 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "../../../../firebase-ios-sdk" */ = {
|
||||
EAD122F42DB9940E004D64C9 /* XCLocalSwiftPackageReference "firebase-ios-sdk" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = "../../../../firebase-ios-sdk";
|
||||
};
|
||||
/* 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 */
|
||||
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 */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FirebaseAppCheck;
|
||||
|
||||
+2
-1
@@ -27,12 +27,13 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9AC7C28D2541C7E600F5DD80"
|
||||
BlueprintIdentifier = "DD8474378DDE4504369D6D2A"
|
||||
BuildableName = "FIRAppCheckTestAppTests.xctest"
|
||||
BlueprintName = "FIRAppCheckTestAppTests"
|
||||
ReferencedContainer = "container:FIRAppCheckTestApp.xcodeproj">
|
||||
|
||||
@@ -16,31 +16,76 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
import AppCheckCore
|
||||
import FirebaseAppCheck
|
||||
import FirebaseCore
|
||||
import FirebaseStorage
|
||||
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
private(set) static var shared: AppDelegate?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
AppDelegate.shared = self
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication
|
||||
.LaunchOptionsKey: Any]?) -> Bool {
|
||||
let providerFactory = AppCheckDebugProviderFactory()
|
||||
AppCheck.setAppCheckProviderFactory(providerFactory)
|
||||
// Manual override for testing/debugging.
|
||||
// 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()
|
||||
|
||||
requestDeviceCheckToken()
|
||||
|
||||
requestDebugToken()
|
||||
|
||||
if #available(iOS 14.0, *) {
|
||||
requestAppAttestToken()
|
||||
}
|
||||
FirebaseApp.configure(options: options)
|
||||
|
||||
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
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
@@ -70,12 +115,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
DeviceCheckProvider(app: firebaseApp)?.getToken { token, error in
|
||||
if let token {
|
||||
print("DeviceCheck token: \(token.token), expiration date: \(token.expirationDate)")
|
||||
}
|
||||
|
||||
if let error {
|
||||
Task {
|
||||
do {
|
||||
if let provider = DeviceCheckProvider(app: firebaseApp) {
|
||||
let token = try await provider.getToken()
|
||||
print("DeviceCheck token: \(token.token), expiration date: \(token.expirationDate)")
|
||||
}
|
||||
} catch {
|
||||
print("DeviceCheck error: \((error as NSError).userInfo)")
|
||||
}
|
||||
}
|
||||
@@ -89,12 +135,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
if let debugProvider = AppCheckDebugProvider(app: firebaseApp) {
|
||||
print("Debug token: \(debugProvider.currentDebugToken())")
|
||||
|
||||
debugProvider.getToken { token, error in
|
||||
if let token {
|
||||
Task {
|
||||
do {
|
||||
let token = try await debugProvider.getToken()
|
||||
print("Debug FAC token: \(token.token), expiration date: \(token.expirationDate)")
|
||||
}
|
||||
|
||||
if let error {
|
||||
} catch {
|
||||
print("Debug error: \(error)")
|
||||
}
|
||||
}
|
||||
@@ -103,19 +148,47 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
// MARK: App Check API
|
||||
|
||||
func requestLimitedUseToken() {
|
||||
AppCheck.appCheck().limitedUseToken { result, error in
|
||||
if let result {
|
||||
print("FAC limited-use token: \(result.token), expiration date: \(result.expirationDate)")
|
||||
}
|
||||
@discardableResult
|
||||
func fetchAppCheckToken(forcingRefresh: Bool = false) async throws -> AppCheckToken {
|
||||
let token = try await AppCheck.appCheck().token(forcingRefresh: forcingRefresh)
|
||||
|
||||
if let error {
|
||||
print("Error: \(String(describing: error))")
|
||||
}
|
||||
let ttl = token.expirationDate.timeIntervalSinceNow
|
||||
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() {
|
||||
guard let firebaseApp = FirebaseApp.app() else {
|
||||
return
|
||||
@@ -126,12 +199,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
appAttestProvider.getToken { token, error in
|
||||
if let token {
|
||||
Task {
|
||||
do {
|
||||
let token = try await appAttestProvider.getToken()
|
||||
print("App Attest FAC token: \(token.token), expiration date: \(token.expirationDate)")
|
||||
}
|
||||
|
||||
if let error {
|
||||
} catch {
|
||||
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 '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
|
||||
@@ -1,11 +1,22 @@
|
||||
# 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
|
||||
generic `AppCheckDebugToken` environment variable instead of the legacy
|
||||
`FIRAAppCheckDebugToken`. Note that `FIRAAppCheckDebugToken` remains
|
||||
supported for backwards compatibility, with `AppCheckDebugToken` taking
|
||||
priority if both are set. (#16230)
|
||||
- [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] Log an actionable warning when debug token exchange fails. (#16232)
|
||||
|
||||
|
||||
@@ -36,6 +36,16 @@ FOUNDATION_EXPORT NSString *const kFIRLoggerAppCheckMessageCodeDebugToken;
|
||||
// FIRDeviceCheckProvider.m
|
||||
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, ...);
|
||||
|
||||
GACAppCheckLogLevel FIRGetGACAppCheckLogLevel(void);
|
||||
@@ -36,6 +36,16 @@ NSString *const kFIRLoggerAppCheckMessageCodeDebugToken = @"I-FAA005001";
|
||||
// FIRDeviceCheckProvider.m
|
||||
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
|
||||
|
||||
void FIRAppCheckDebugLog(NSString *messageCode, NSString *message, ...) {
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
|
||||
#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/FIRAppCheckDebugProviderFactory.h"
|
||||
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRDeviceCheckProviderFactory.h"
|
||||
#import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRRecaptchaProviderFactory.h"
|
||||
#import "FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaProvider+Internal.h"
|
||||
|
||||
@implementation FIRDefaultProviderFactory
|
||||
|
||||
@@ -27,10 +31,32 @@
|
||||
- (nullable id<FIRAppCheckProvider>)createProviderWithApp:(nonnull FIRApp *)app {
|
||||
#if TARGET_OS_SIMULATOR
|
||||
return [[[FIRAppCheckDebugProviderFactory alloc] init] createProviderWithApp:app];
|
||||
// TODO(ncooke3): Add elif case for future reCAPTCHA provider.
|
||||
#else
|
||||
return [[[FIRDeviceCheckProviderFactory alloc] init] createProviderWithApp:app];
|
||||
#else // !TARGET_OS_SIMULATOR
|
||||
|
||||
#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
|
||||
|
||||
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
|
||||
@@ -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.
|
||||
#import "FIRAppAttestProvider.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/FIRAppCheckDebugProvider.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 "FirebaseAppCheck/Sources/RecaptchaProvider/FIRRecaptchaProvider+Internal.h"
|
||||
|
||||
FIR_DEVICE_CHECK_PROVIDER_AVAILABILITY
|
||||
@interface FIRDefaultProviderFactoryTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation FIRDefaultProviderFactoryTests
|
||||
|
||||
- (void)testCreateProviderWithApp {
|
||||
- (FIRApp *)mockAppWithRecaptchaSiteKey:(nullable NSString *)siteKey {
|
||||
FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"];
|
||||
options.APIKey = @"api_key";
|
||||
options.projectID = @"project_id";
|
||||
if (siteKey) {
|
||||
options.recaptchaSiteKey = siteKey;
|
||||
}
|
||||
FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options];
|
||||
// The following disables automatic token refresh, which could interfere with tests.
|
||||
app.dataCollectionDefaultEnabled = NO;
|
||||
return app;
|
||||
}
|
||||
|
||||
- (void)testCreateProvider_Simulator {
|
||||
#if TARGET_OS_SIMULATOR
|
||||
FIRApp *app = [self mockAppWithRecaptchaSiteKey:nil];
|
||||
FIRDefaultProviderFactory *factory = [[FIRDefaultProviderFactory alloc] init];
|
||||
|
||||
id<FIRAppCheckProvider> provider = [factory createProviderWithApp:app];
|
||||
|
||||
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
|
||||
XCTAssert([provider isKindOfClass:[FIRAppCheckDebugProvider class]]);
|
||||
#else
|
||||
XCTAssert([provider isKindOfClass:[FIRDeviceCheckProvider class]]);
|
||||
#endif
|
||||
|
||||
#if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_VISION
|
||||
[recaptchaMock stopMocking];
|
||||
#endif
|
||||
}
|
||||
|
||||
@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";
|
||||
// The key to locate the project identifier in the plist file.
|
||||
NSString *const kFIRProjectID = @"PROJECT_ID";
|
||||
NSString *const kFIRRecaptchaSiteKey = @"RECAPTCHA_SITE_KEY";
|
||||
|
||||
NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED";
|
||||
NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED";
|
||||
@@ -306,6 +307,15 @@ static dispatch_once_t sDefaultOptionsDictionaryOnceToken;
|
||||
_optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
|
||||
}
|
||||
|
||||
- (NSString *)recaptchaSiteKey {
|
||||
return self.optionsDictionary[kFIRRecaptchaSiteKey];
|
||||
}
|
||||
|
||||
- (void)setRecaptchaSiteKey:(NSString *)recaptchaSiteKey {
|
||||
[self checkEditingLocked];
|
||||
_optionsDictionary[kFIRRecaptchaSiteKey] = [recaptchaSiteKey copy];
|
||||
}
|
||||
|
||||
- (NSString *)bundleID {
|
||||
return self.optionsDictionary[kFIRBundleID];
|
||||
}
|
||||
|
||||
@@ -82,6 +82,11 @@ NS_SWIFT_NAME(FirebaseOptions)
|
||||
*/
|
||||
@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.
|
||||
* This will read the file synchronously from disk.
|
||||
|
||||
@@ -154,6 +154,19 @@ extern NSString *const kFIRLibraryVersionID;
|
||||
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 {
|
||||
XCTAssertEqualObjects(options.googleAppID, kGoogleAppID);
|
||||
XCTAssertEqualObjects(options.APIKey, kAPIKey);
|
||||
|
||||
+22
-2
@@ -172,8 +172,7 @@ let package = Package(
|
||||
url: "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||
"101.0.0" ..< "102.0.0"
|
||||
),
|
||||
.package(url: "https://github.com/google/app-check.git",
|
||||
"11.0.1" ..< "12.0.0"),
|
||||
appCheckDependency(),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@@ -945,6 +944,7 @@ let package = Package(
|
||||
.target(
|
||||
name: "SharedTestUtilities",
|
||||
dependencies: ["FirebaseCore",
|
||||
"FirebaseCoreExtension",
|
||||
"FirebaseAppCheckInterop",
|
||||
"FirebaseAuthInterop",
|
||||
"FirebaseMessagingInterop",
|
||||
@@ -1268,6 +1268,7 @@ let package = Package(
|
||||
"FirebaseAppCheckInterop",
|
||||
"FirebaseCore",
|
||||
.product(name: "AppCheckCore", package: "app-check"),
|
||||
.product(name: "AppCheckRecaptchaProvider", package: "app-check"),
|
||||
.product(name: "GULEnvironment", package: "GoogleUtilities"),
|
||||
.product(name: "GULUserDefaults", package: "GoogleUtilities"),
|
||||
],
|
||||
@@ -1316,8 +1317,13 @@ let package = Package(
|
||||
dependencies: [
|
||||
"FirebaseAppCheck",
|
||||
"FirebaseCoreExtension",
|
||||
"SharedTestUtilities",
|
||||
.product(name: "AppCheckCore", package: "app-check"),
|
||||
],
|
||||
path: "FirebaseAppCheck/Tests/Unit/Swift",
|
||||
cSettings: [
|
||||
.headerSearchPath("../../../"),
|
||||
],
|
||||
swiftSettings: [
|
||||
.swiftLanguageMode(SwiftLanguageMode.v5),
|
||||
]
|
||||
@@ -1675,3 +1681,17 @@ func isFoundationModelsSupportedPlatformSwiftSetting() -> SwiftSetting {
|
||||
.when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])
|
||||
)
|
||||
}
|
||||
|
||||
func appCheckDependency() -> Package.Dependency {
|
||||
let appCheckURL = "https://github.com/google/app-check.git"
|
||||
|
||||
if let localPath = Context.environment["FIREBASE_APP_CHECK_LOCAL_PATH"] {
|
||||
return .package(path: localPath)
|
||||
}
|
||||
|
||||
if let branch = Context.environment["FIREBASE_APP_CHECK_BRANCH"] {
|
||||
return .package(url: appCheckURL, branch: branch)
|
||||
}
|
||||
|
||||
return .package(url: appCheckURL, "11.3.0" ..< "12.0.0")
|
||||
}
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
#import <FirebaseAuthInterop/FIRAuthInterop.h>
|
||||
#else
|
||||
#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
#import "SharedTestUtilities/FIRAuthInteropFake.h"
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
#import <FirebaseAuthInterop/FIRAuthInterop.h>
|
||||
#else
|
||||
#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
#import <FirebaseCoreInternal.h>
|
||||
#else
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
#import <FIRMessagingInterop.h>
|
||||
#else
|
||||
#import "FirebaseMessaging/Interop/FIRMessagingInterop.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
#import <SafariServices/SafariServices.h>
|
||||
#endif
|
||||
|
||||
#if SWIFT_PACKAGE
|
||||
#import <FirebaseCoreInternal.h>
|
||||
#else
|
||||
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
||||
#endif
|
||||
|
||||
NSString *const kGoogleAppIDPlistKey = @"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]
|
||||
if inSwiftPackageElse {
|
||||
if importFile.first != "<" {
|
||||
logger
|
||||
.importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum)
|
||||
// SharedTestUtilities files are included directly in test targets and
|
||||
// use repo-relative imports.
|
||||
if !file.contains("SharedTestUtilities/") {
|
||||
logger
|
||||
.importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user