Skip to content

Commit f36c832

Browse files
authored
Recaptcha Unit Tests (#14151)
1 parent c396f47 commit f36c832

File tree

4 files changed

+228
-0
lines changed

4 files changed

+228
-0
lines changed

FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,86 @@
208208
)
209209
}
210210

211+
/// @fn testVerifyPhoneNumberWithRceAudit
212+
/// @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise in
213+
/// audit mode
214+
func testVerifyPhoneNumberWithRceAuditSuccess() async throws {
215+
initApp(#function)
216+
let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
217+
let provider = PhoneAuthProvider.provider(auth: auth)
218+
let mockVerifier = FakeAuthRecaptchaVerifier(captchaResponse: kCaptchaResponse)
219+
AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
220+
rpcIssuer.rceMode = "AUDIT"
221+
let requestExpectation = expectation(description: "verifyRequester")
222+
rpcIssuer?.verifyRequester = { request in
223+
XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
224+
XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse)
225+
XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
226+
XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
227+
requestExpectation.fulfill()
228+
do {
229+
try self.rpcIssuer?
230+
.respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
231+
} catch {
232+
XCTFail("Failure sending response: \(error)")
233+
}
234+
}
235+
do {
236+
let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
237+
toPhoneNumber: kTestPhoneNumber,
238+
retryOnInvalidAppCredential: false,
239+
uiDelegate: nil,
240+
recaptchaVerifier: mockVerifier
241+
)
242+
XCTAssertEqual(result, kTestVerificationID)
243+
} catch {
244+
XCTFail("Unexpected error")
245+
}
246+
await fulfillment(of: [requestExpectation], timeout: 5.0)
247+
}
248+
249+
/// @fn testVerifyPhoneNumberWithRceAuditInvalidRecaptcha
250+
/// @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise in
251+
/// audit mode
252+
func testVerifyPhoneNumberWithRceAuditInvalidRecaptcha() async throws {
253+
initApp(#function)
254+
let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
255+
let provider = PhoneAuthProvider.provider(auth: auth)
256+
let mockVerifier = FakeAuthRecaptchaVerifier()
257+
AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
258+
rpcIssuer.rceMode = "AUDIT"
259+
let requestExpectation = expectation(description: "verifyRequester")
260+
rpcIssuer?.verifyRequester = { request in
261+
XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
262+
XCTAssertEqual(request.captchaResponse, "NO_RECAPTCHA")
263+
XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
264+
XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
265+
requestExpectation.fulfill()
266+
do {
267+
try self.rpcIssuer?
268+
.respond(
269+
serverErrorMessage: "INVALID_RECAPTCHA_TOKEN",
270+
error: AuthErrorUtils.invalidRecaptchaTokenError() as NSError
271+
)
272+
} catch {
273+
XCTFail("Failure sending response: \(error)")
274+
}
275+
}
276+
do {
277+
_ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
278+
toPhoneNumber: kTestPhoneNumber,
279+
retryOnInvalidAppCredential: false,
280+
uiDelegate: nil,
281+
recaptchaVerifier: mockVerifier
282+
)
283+
} catch {
284+
let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError
285+
let rootError = underlyingError?.userInfo[NSUnderlyingErrorKey] as? NSError
286+
XCTAssertEqual(rootError?.code, AuthErrorCode.invalidRecaptchaToken.code.rawValue)
287+
}
288+
await fulfillment(of: [requestExpectation], timeout: 5.0)
289+
}
290+
211291
/** @fn testVerifyPhoneNumberInTestMode
212292
@brief Tests a successful invocation of @c verifyPhoneNumber:completion: when app verification
213293
is disabled.

FirebaseAuth/Tests/Unit/RPCBaseTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class RPCBaseTests: XCTestCase {
4242
let kCreationDateTimeIntervalInSeconds = 1_505_858_500.0
4343
let kLastSignInDateTimeIntervalInSeconds = 1_505_858_583.0
4444
let kTestPhoneNumber = "415-555-1234"
45+
let kIdToken = "FAKE_ID_TOKEN"
4546
static let kOAuthSessionID = "sessionID"
4647
static let kOAuthRequestURI = "requestURI"
4748
let kGoogleIDToken = "GOOGLE_ID_TOKEN"

FirebaseAuth/Tests/Unit/StartMFAEnrollmentRequestTests.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ import XCTest
2323
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
2424
class StartMFAEnrollmentRequestTests: RPCBaseTests {
2525
let kAPIKey = "APIKey"
26+
let kIDToken = "idToken"
27+
let kTOTPEnrollmentInfo = "totpEnrollmentInfo"
28+
let kPhoneEnrollmentInfo = "enrollmentInfo"
29+
let kPhoneNumber = "phoneNumber"
30+
let kReCAPTCHAToken = "recaptchaToken"
31+
let kCaptchaResponse = "captchaResponse"
32+
let kRecaptchaVersion = "recaptchaVersion"
2633

2734
/**
2835
@fn testTOTPStartMFAEnrollmentRequest
@@ -58,4 +65,55 @@ class StartMFAEnrollmentRequestTests: RPCBaseTests {
5865
XCTAssertEqual(totpInfo, [:])
5966
XCTAssertNil(requestDictionary[kPhoneEnrollmentInfo])
6067
}
68+
69+
/**
70+
@fn testPhoneStartMFAEnrollmentRequest
71+
@brief Tests the Start MFA Enrollment using SMS request.
72+
*/
73+
func testPhoneStartMFAEnrollmentInjectRecaptchaFields() async throws {
74+
// created a base startMFAEnrollment Request
75+
let testPhoneNumber = "1234567890"
76+
let testRecaptchaToken = "RECAPTCHA_FAKE_TOKEN"
77+
78+
let requestConfiguration = AuthRequestConfiguration(apiKey: kAPIKey, appID: "appID")
79+
let smsEnrollmentInfo = AuthProtoStartMFAPhoneRequestInfo(
80+
phoneNumber: testPhoneNumber,
81+
codeIdentity: CodeIdentity.recaptcha(testRecaptchaToken)
82+
)
83+
let request = StartMFAEnrollmentRequest(idToken: kIDToken,
84+
enrollmentInfo: smsEnrollmentInfo,
85+
requestConfiguration: requestConfiguration)
86+
87+
// inject reCAPTCHA response
88+
let testRecaptchaResponse = "RECAPTCHA_FAKE_RESPONSE"
89+
let testRecaptchaVersion = "RECAPTCHA_FAKE_ENTERPRISE"
90+
request.injectRecaptchaFields(
91+
recaptchaResponse: testRecaptchaResponse,
92+
recaptchaVersion: testRecaptchaVersion
93+
)
94+
95+
let expectedURL =
96+
"https://identitytoolkit.googleapis.com/v2/accounts/mfaEnrollment:start?key=\(kAPIKey)"
97+
98+
do {
99+
try await checkRequest(
100+
request: request,
101+
expected: expectedURL,
102+
key: kIDToken,
103+
value: kIDToken
104+
)
105+
} catch {
106+
// Ignore error from missing users array in fake JSON return.
107+
return
108+
}
109+
110+
let requestDictionary = try XCTUnwrap(rpcIssuer.decodedRequest as? [String: AnyHashable])
111+
let smsInfo = try XCTUnwrap(requestDictionary["phoneEnrollmentInfo"] as? [String: String])
112+
XCTAssertEqual(smsInfo[kPhoneNumber], testPhoneNumber)
113+
XCTAssertEqual(smsInfo[kReCAPTCHAToken], testRecaptchaToken)
114+
XCTAssertEqual(smsInfo[kRecaptchaVersion], kRecaptchaVersion)
115+
XCTAssertEqual(smsInfo[kCaptchaResponse], testRecaptchaResponse)
116+
117+
XCTAssertNil(requestDictionary[kTOTPEnrollmentInfo])
118+
}
61119
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
import XCTest
17+
18+
@testable import FirebaseAuth
19+
20+
/** @class StartMFASignInRequestTests
21+
@brief Tests for @c StartMFASignInRequest
22+
*/
23+
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
24+
class StartMFASignInRequestTests: RPCBaseTests {
25+
let kAPIKey = "APIKey"
26+
let kMfaEnrollmentId = "mfaEnrollmentId"
27+
let kTOTPEnrollmentInfo = "totpEnrollmentInfo"
28+
let kPhoneEnrollmentInfo = "enrollmentInfo"
29+
let kPhoneNumber = "phoneNumber"
30+
let kReCAPTCHAToken = "recaptchaToken"
31+
let kCaptchaResponse = "captchaResponse"
32+
let kRecaptchaVersion = "recaptchaVersion"
33+
34+
/**
35+
@fn testPhoneStartMFASignInRequest
36+
@brief Tests the Start MFA Sign In using SMS request.
37+
*/
38+
func testPhoneStartMFASignInRequest() async throws {
39+
let testPendingCredential = "FAKE_PENDING_CREDENTIAL"
40+
let testEnrollmentID = "FAKE_ENROLLMENT_ID"
41+
let testPhoneNumber = "1234567890"
42+
let testRecaptchaToken = "RECAPTCHA_FAKE_TOKEN"
43+
44+
let requestConfiguration = AuthRequestConfiguration(apiKey: kAPIKey, appID: "appID")
45+
let smsSignInInfo = AuthProtoStartMFAPhoneRequestInfo(
46+
phoneNumber: testPhoneNumber,
47+
codeIdentity: CodeIdentity.recaptcha(testRecaptchaToken)
48+
)
49+
50+
let request = StartMFASignInRequest(
51+
MFAPendingCredential: testPendingCredential,
52+
MFAEnrollmentID: testEnrollmentID,
53+
signInInfo: smsSignInInfo,
54+
requestConfiguration: requestConfiguration
55+
)
56+
57+
let expectedURL =
58+
"https://identitytoolkit.googleapis.com/v2/accounts/mfaSignIn:start?key=\(kAPIKey)"
59+
60+
// inject reCAPTCHA response
61+
let testRecaptchaResponse = "RECAPTCHA_FAKE_RESPONSE"
62+
let testRecaptchaVersion = "RECAPTCHA_FAKE_ENTERPRISE"
63+
request.injectRecaptchaFields(
64+
recaptchaResponse: testRecaptchaResponse,
65+
recaptchaVersion: testRecaptchaVersion
66+
)
67+
68+
do {
69+
try await checkRequest(
70+
request: request,
71+
expected: expectedURL,
72+
key: kMfaEnrollmentId,
73+
value: testEnrollmentID
74+
)
75+
} catch {
76+
// Ignore error from missing users array in fake JSON return.
77+
return
78+
}
79+
80+
let requestDictionary = try XCTUnwrap(rpcIssuer.decodedRequest as? [String: AnyHashable])
81+
let smsInfo = try XCTUnwrap(requestDictionary["phoneEnrollmentInfo"] as? [String: String])
82+
XCTAssertEqual(smsInfo[kPhoneNumber], testPhoneNumber)
83+
XCTAssertEqual(smsInfo[kReCAPTCHAToken], testRecaptchaToken)
84+
XCTAssertEqual(smsInfo[kRecaptchaVersion], kRecaptchaVersion)
85+
XCTAssertEqual(smsInfo[kCaptchaResponse], testRecaptchaResponse)
86+
87+
XCTAssertNil(requestDictionary[kTOTPEnrollmentInfo])
88+
}
89+
}

0 commit comments

Comments
 (0)