Skip to content

Commit 02b1777

Browse files
authored
Add App Check support to desktop Functions (#1225)
* Add App Check to desktop Functions * File formatting * Update Podfile
1 parent d9a5a1b commit 02b1777

File tree

9 files changed

+178
-4
lines changed

9 files changed

+178
-4
lines changed

app_check/integration_test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ endif()
234234
# Add the Firebase libraries to the target using the function from the SDK.
235235
add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL)
236236
# Note that firebase_app needs to be last in the list.
237-
set(firebase_libs firebase_app_check firebase_database firebase_storage firebase_auth firebase_app)
237+
set(firebase_libs firebase_app_check firebase_database firebase_storage firebase_functions firebase_auth firebase_app)
238238
set(gtest_libs gtest gmock)
239239
target_link_libraries(${integration_test_target_name} ${firebase_libs}
240240
${gtest_libs} ${ADDITIONAL_LIBS})

app_check/integration_test/Podfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ target 'integration_test' do
77
pod 'Firebase/AppCheck', '10.5.0'
88
pod 'Firebase/Database', '10.5.0'
99
pod 'Firebase/Auth', '10.5.0'
10+
pod 'Firebase/Storage', '10.5.0'
11+
pod 'Firebase/Functions', '10.5.0'
1012
end
1113

1214
target 'integration_test_tvos' do
1315
platform :tvos, '12.0'
1416
pod 'Firebase/AppCheck', '10.5.0'
1517
pod 'Firebase/Database', '10.5.0'
1618
pod 'Firebase/Auth', '10.5.0'
19+
pod 'Firebase/Storage', '10.5.0'
20+
pod 'Firebase/Functions', '10.5.0'
1721
end
1822

1923
post_install do |installer|

app_check/integration_test/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ android {
6464
"-DFIREBASE_INCLUDE_APP_CHECK=ON",
6565
"-DFIREBASE_INCLUDE_AUTH=ON",
6666
"-DFIREBASE_INCLUDE_DATABASE=ON",
67-
"-DFIREBASE_INCLUDE_STORAGE=ON"
67+
"-DFIREBASE_INCLUDE_STORAGE=ON",
68+
"-DFIREBASE_INCLUDE_FUNCTIONS=ON"
6869
}
6970
}
7071
externalNativeBuild.cmake {
@@ -85,6 +86,7 @@ firebaseCpp.dependencies {
8586
auth
8687
database
8788
storage
89+
functions
8890
}
8991

9092
apply plugin: 'com.google.gms.google-services'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Cloud functions used by the integration test.
2+
Instructions to deploy:
3+
1. From the innermost functions directory run `npm install`
4+
2. From this directory, run `firebase deploy --only functions`
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"functions": {
3+
}
4+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const functions = require('firebase-functions');
18+
const admin = require('firebase-admin');
19+
admin.initializeApp(functions.config().firebase);
20+
21+
// Adds two numbers to each other.
22+
exports.addNumbers = functions
23+
.runWith({
24+
enforceAppCheck: true // Requests without valid App Check tokens will be rejected.
25+
})
26+
.https.onCall((data, context) => {
27+
// context.app will be undefined if the request doesn't include an
28+
// App Check token. (If the request includes an invalid App Check
29+
// token, the request will be rejected with HTTP error 401.)
30+
if (context.app == undefined) {
31+
throw new functions.https.HttpsError(
32+
'failed-precondition',
33+
'The function must be called from an App Check verified app.')
34+
}
35+
36+
// Numbers passed from the client.
37+
const firstNumber = data.firstNumber;
38+
const secondNumber = data.secondNumber;
39+
40+
// Checking that attributes are present and are numbers.
41+
if (!Number.isFinite(firstNumber) || !Number.isFinite(secondNumber)) {
42+
// Throwing an HttpsError so that the client gets the error details.
43+
throw new functions.https.HttpsError('invalid-argument', 'The function ' +
44+
'must be called with two arguments "firstNumber" and "secondNumber" ' +
45+
'which must both be numbers.');
46+
}
47+
48+
// returning result.
49+
return {
50+
firstNumber: firstNumber,
51+
secondNumber: secondNumber,
52+
operator: '+',
53+
operationResult: firstNumber + secondNumber,
54+
};
55+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "functions",
3+
"description": "Cloud Functions for Firebase",
4+
"engines": {
5+
"node": "16"
6+
},
7+
"main": "index.js",
8+
"dependencies": {
9+
"firebase-admin": "^11.5.0",
10+
"firebase-functions": "^4.2.1"
11+
},
12+
"private": true
13+
}

app_check/integration_test/src/integration_test.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "firebase/app_check/play_integrity_provider.h"
3636
#include "firebase/auth.h"
3737
#include "firebase/database.h"
38+
#include "firebase/functions.h"
3839
#include "firebase/internal/platform.h"
3940
#include "firebase/storage.h"
4041
#include "firebase/util.h"
@@ -115,6 +116,11 @@ class FirebaseAppCheckTest : public FirebaseTest {
115116
// Initialize everything needed for Storage tests.
116117
void InitializeAppAuthStorage();
117118

119+
// Initialize Firebase Functions.
120+
void InitializeFunctions();
121+
// Shut down Firebase Functions.
122+
void TerminateFunctions();
123+
118124
firebase::database::DatabaseReference CreateWorkingPath(
119125
bool suppress_cleanup = false);
120126

@@ -126,6 +132,8 @@ class FirebaseAppCheckTest : public FirebaseTest {
126132
std::vector<firebase::database::DatabaseReference> cleanup_paths_;
127133

128134
firebase::storage::Storage* storage_;
135+
136+
firebase::functions::Functions* functions_;
129137
};
130138

131139
// Listens for token changed notifications
@@ -202,6 +210,7 @@ FirebaseAppCheckTest::FirebaseAppCheckTest()
202210
auth_(nullptr),
203211
database_(nullptr),
204212
storage_(nullptr),
213+
functions_(nullptr),
205214
cleanup_paths_() {
206215
FindFirebaseConfig(FIREBASE_CONFIG_STRING);
207216
}
@@ -215,6 +224,7 @@ void FirebaseAppCheckTest::TearDown() {
215224
// Teardown all the products
216225
TerminateDatabase();
217226
TerminateStorage();
227+
TerminateFunctions();
218228
TerminateAuth();
219229
TerminateAppCheck();
220230
TerminateApp();
@@ -349,6 +359,37 @@ void FirebaseAppCheckTest::InitializeAppAuthStorage() {
349359
InitializeStorage();
350360
}
351361

362+
void FirebaseAppCheckTest::InitializeFunctions() {
363+
LogDebug("Initializing Firebase Functions.");
364+
365+
::firebase::ModuleInitializer initializer;
366+
initializer.Initialize(
367+
app_, &functions_, [](::firebase::App* app, void* target) {
368+
LogDebug("Attempting to initialize Firebase Functions.");
369+
::firebase::InitResult result;
370+
*reinterpret_cast<firebase::functions::Functions**>(target) =
371+
firebase::functions::Functions::GetInstance(app, &result);
372+
return result;
373+
});
374+
375+
WaitForCompletion(initializer.InitializeLastResult(), "InitializeFunctions");
376+
377+
ASSERT_EQ(initializer.InitializeLastResult().error(), 0)
378+
<< initializer.InitializeLastResult().error_message();
379+
380+
LogDebug("Successfully initialized Firebase Functions.");
381+
}
382+
383+
void FirebaseAppCheckTest::TerminateFunctions() {
384+
if (functions_) {
385+
LogDebug("Shutdown the Functions library.");
386+
delete functions_;
387+
functions_ = nullptr;
388+
}
389+
390+
ProcessEvents(100);
391+
}
392+
352393
void FirebaseAppCheckTest::SignIn() {
353394
if (auth_->current_user() != nullptr) {
354395
// Already signed in.
@@ -745,4 +786,39 @@ TEST_F(FirebaseAppCheckTest, TestStorageReadFileUnauthenticated) {
745786
}
746787
#endif // !FIREBASE_PLATFORM_ANDROID
747788

789+
TEST_F(FirebaseAppCheckTest, TestFunctionsSuccess) {
790+
InitializeAppCheckWithDebug();
791+
InitializeApp();
792+
InitializeFunctions();
793+
firebase::functions::HttpsCallableReference ref;
794+
ref = functions_->GetHttpsCallable("addNumbers");
795+
firebase::Variant data(firebase::Variant::EmptyMap());
796+
data.map()["firstNumber"] = 5;
797+
data.map()["secondNumber"] = 7;
798+
firebase::Future<firebase::functions::HttpsCallableResult> future;
799+
future = ref.Call(data);
800+
WaitForCompletion(future, "CallFunction addnumbers",
801+
firebase::functions::kErrorNone);
802+
firebase::Variant result = future.result()->data();
803+
EXPECT_TRUE(result.is_map());
804+
if (result.is_map()) {
805+
EXPECT_EQ(result.map()["operationResult"], 12);
806+
}
807+
}
808+
809+
TEST_F(FirebaseAppCheckTest, TestFunctionsFailure) {
810+
// Don't set up AppCheck
811+
InitializeApp();
812+
InitializeFunctions();
813+
firebase::functions::HttpsCallableReference ref;
814+
ref = functions_->GetHttpsCallable("addNumbers");
815+
firebase::Variant data(firebase::Variant::EmptyMap());
816+
data.map()["firstNumber"] = 6;
817+
data.map()["secondNumber"] = 8;
818+
firebase::Future<firebase::functions::HttpsCallableResult> future;
819+
future = ref.Call(data);
820+
WaitForCompletion(future, "CallFunction addnumbers",
821+
firebase::functions::kErrorUnauthenticated);
822+
}
823+
748824
} // namespace firebase_testapp_automated

functions/src/desktop/callable_reference_desktop.cc

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,24 @@ Future<HttpsCallableResult> HttpsCallableReferenceInternal::Call(
331331
request_.set_future_handle(handle);
332332
request_.set_response(&response_);
333333

334-
// Start the request.
335-
transport_.Perform(&request_, &response_, nullptr);
334+
// Check for App Check token function
335+
Future<std::string> app_check_future;
336+
bool succeeded = functions_->app()->function_registry()->CallFunction(
337+
::firebase::internal::FnAppCheckGetTokenAsync, functions_->app(), nullptr,
338+
&app_check_future);
339+
if (succeeded && app_check_future.status() != kFutureStatusInvalid) {
340+
// Perform the transform request on a completion
341+
app_check_future.OnCompletion([&](const Future<std::string>& future_token) {
342+
if (future_token.result()) {
343+
request_.add_header("X-Firebase-AppCheck",
344+
future_token.result()->c_str());
345+
}
346+
transport_.Perform(&request_, &response_, nullptr);
347+
});
348+
} else {
349+
// Start the request.
350+
transport_.Perform(&request_, &response_, nullptr);
351+
}
336352

337353
return CallLastResult();
338354
}

0 commit comments

Comments
 (0)