Skip to content

Commit 918d77d

Browse files
authored
[Functions] Pass placeholder app check tokens on error (#14467)
1 parent 37b6456 commit 918d77d

File tree

3 files changed

+77
-22
lines changed

3 files changed

+77
-22
lines changed

FirebaseFunctions/Sources/Internal/FunctionsContext.swift

+7-12
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ struct FunctionsContextProvider {
5656
private func getAppCheckToken(options: HTTPSCallableOptions?) async -> String? {
5757
guard
5858
options?.requireLimitedUseAppCheckTokens != true,
59-
let tokenResult = await appCheck?.getToken(forcingRefresh: false),
60-
tokenResult.error == nil
59+
let tokenResult = await appCheck?.getToken(forcingRefresh: false)
6160
else { return nil }
61+
// The placeholder token should be used in the case of App Check error.
6262
return tokenResult.token
6363
}
6464

@@ -77,8 +77,7 @@ struct FunctionsContextProvider {
7777
}
7878

7979
limitedUseTokenClosure { tokenResult in
80-
// Make sure there’s no error and the token is valid:
81-
guard tokenResult.error == nil else { return continuation.resume(returning: nil) }
80+
// The placeholder token should be used in the case of App Check error.
8281
continuation.resume(returning: tokenResult.token)
8382
}
8483
}
@@ -111,21 +110,17 @@ struct FunctionsContextProvider {
111110
// If it’s not implemented, we still need to leave the dispatch group.
112111
if let limitedUseTokenClosure = appCheck.getLimitedUseToken {
113112
limitedUseTokenClosure { tokenResult in
114-
// Send only valid token to functions.
115-
if tokenResult.error == nil {
116-
limitedUseAppCheckToken = tokenResult.token
117-
}
113+
// In the case of an error, the token will be the placeholder token.
114+
limitedUseAppCheckToken = tokenResult.token
118115
dispatchGroup.leave()
119116
}
120117
} else {
121118
dispatchGroup.leave()
122119
}
123120
} else {
124121
appCheck.getToken(forcingRefresh: false) { tokenResult in
125-
// Send only valid token to functions.
126-
if tokenResult.error == nil {
127-
appCheckToken = tokenResult.token
128-
}
122+
// In the case of an error, the token will be the placeholder token.
123+
appCheckToken = tokenResult.token
129124
dispatchGroup.leave()
130125
}
131126
}

FirebaseFunctions/Tests/Unit/ContextProviderTests.swift

+62-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ class ContextProviderTests: XCTestCase {
3232
code: -1,
3333
userInfo: nil
3434
))
35+
let appCheckLimitedUseTokenError = FIRAppCheckTokenResultFake(token: "limited use token",
36+
error: NSError(
37+
domain: "testAppCheckError",
38+
code: -1,
39+
userInfo: nil
40+
))
3541
let appCheckTokenSuccess = FIRAppCheckTokenResultFake(token: "valid_token", error: nil)
3642
let messagingFake = FIRMessagingInteropFake()
3743

@@ -143,8 +149,20 @@ class ContextProviderTests: XCTestCase {
143149

144150
XCTAssertNil(context.authToken)
145151
XCTAssertNil(context.fcmToken)
146-
// Don't expect any token in the case of App Check error.
147-
XCTAssertNil(context.appCheckToken)
152+
// Expect placeholder token in the case of App Check error.
153+
XCTAssertEqual(context.appCheckToken, appCheckFake.tokenResult.token)
154+
}
155+
156+
func testAsyncContextWithAppCheckOnlyError_LimitedUseToken() async throws {
157+
appCheckFake.limitedUseTokenResult = appCheckLimitedUseTokenError
158+
let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake)
159+
160+
let context = try await provider.context(options: .init(requireLimitedUseAppCheckTokens: true))
161+
162+
XCTAssertNil(context.authToken)
163+
XCTAssertNil(context.fcmToken)
164+
// Expect placeholder token in the case of App Check error.
165+
XCTAssertEqual(context.limitedUseAppCheckToken, appCheckFake.limitedUseTokenResult.token)
148166
}
149167

150168
func testContextWithAppCheckOnlyError() {
@@ -156,8 +174,24 @@ class ContextProviderTests: XCTestCase {
156174
XCTAssertNil(error)
157175
XCTAssertNil(context.authToken)
158176
XCTAssertNil(context.fcmToken)
159-
// Don't expect any token in the case of App Check error.
160-
XCTAssertNil(context.appCheckToken)
177+
// Expect placeholder token in the case of App Check error.
178+
XCTAssertEqual(context.appCheckToken, self.appCheckFake.tokenResult.token)
179+
expectation.fulfill()
180+
}
181+
waitForExpectations(timeout: 0.1)
182+
}
183+
184+
func testContextWithAppCheckOnlyError_LimitedUseToken() {
185+
appCheckFake.limitedUseTokenResult = appCheckLimitedUseTokenError
186+
let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake)
187+
let expectation = expectation(description: "Verify bad app check token")
188+
provider.getContext(options: .init(requireLimitedUseAppCheckTokens: true)) { context, error in
189+
XCTAssertNotNil(context)
190+
XCTAssertNil(error)
191+
XCTAssertNil(context.authToken)
192+
XCTAssertNil(context.fcmToken)
193+
// Expect placeholder token in the case of App Check error.
194+
XCTAssertEqual(context.limitedUseAppCheckToken, self.appCheckFake.limitedUseTokenResult.token)
161195
expectation.fulfill()
162196
}
163197
waitForExpectations(timeout: 0.1)
@@ -264,8 +298,30 @@ class ContextProviderTests: XCTestCase {
264298
XCTAssertEqual(error as NSError?, authError)
265299
XCTAssertNil(context.authToken)
266300
XCTAssertEqual(context.fcmToken, self.messagingFake.fcmToken)
267-
// Don't expect any token in the case of App Check error.
268-
XCTAssertNil(context.appCheckToken)
301+
// Expect placeholder token in the case of App Check error.
302+
XCTAssertEqual(context.appCheckToken, self.appCheckFake.tokenResult.token)
303+
expectation.fulfill()
304+
}
305+
waitForExpectations(timeout: 0.1)
306+
}
307+
308+
func testAllContextsAuthAndAppCheckError_LimitedUseToken() {
309+
appCheckFake.limitedUseTokenResult = appCheckLimitedUseTokenError
310+
let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil)
311+
let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError)
312+
let provider = FunctionsContextProvider(
313+
auth: auth,
314+
messaging: messagingFake,
315+
appCheck: appCheckFake
316+
)
317+
let expectation = expectation(description: "All contexts with errors")
318+
provider.getContext(options: .init(requireLimitedUseAppCheckTokens: true)) { context, error in
319+
XCTAssertNotNil(context)
320+
XCTAssertEqual(error as NSError?, authError)
321+
XCTAssertNil(context.authToken)
322+
XCTAssertEqual(context.fcmToken, self.messagingFake.fcmToken)
323+
// Expect placeholder token in the case of App Check error.
324+
XCTAssertEqual(context.limitedUseAppCheckToken, self.appCheckFake.limitedUseTokenResult.token)
269325
expectation.fulfill()
270326
}
271327
waitForExpectations(timeout: 0.1)

FirebaseFunctions/Tests/Unit/FunctionsTests.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -215,18 +215,22 @@ class FunctionsTests: XCTestCase {
215215
waitForExpectations(timeout: 1.5)
216216
}
217217

218-
func testCallFunctionWhenLimitedUseAppCheckTokenCannotBeGeneratedThenCallWithoutToken() {
218+
func testCallFunctionWhenLimitedUseAppCheckTokenCannotBeGeneratedThenCallWithPlaceholderToken() {
219219
// Given
220220
appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
221-
token: "dummy token",
221+
token: "limited use token",
222222
error: NSError(domain: #function, code: -1)
223223
)
224224

225225
let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
226226
fetcherService.testBlock = { fetcherToTest, testResponse in
227227
// Assert that header does not contain an AppCheck token.
228-
fetcherToTest.request?.allHTTPHeaderFields?.forEach { key, _ in
229-
XCTAssertNotEqual(key, "X-Firebase-AppCheck")
228+
do {
229+
let appCheckHeader = try XCTUnwrap(fetcherToTest.request?
230+
.allHTTPHeaderFields?["X-Firebase-AppCheck"])
231+
XCTAssertEqual(appCheckHeader, self.appCheckFake.limitedUseTokenResult.token)
232+
} catch {
233+
XCTFail("Unexpected failure: \(error)")
230234
}
231235

232236
testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)

0 commit comments

Comments
 (0)