Skip to content

Commit 612a9bb

Browse files
committed
Add Firebase Auth MFA info to user record
1 parent d24289a commit 612a9bb

File tree

6 files changed

+280
-1
lines changed

6 files changed

+280
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2022 Google Inc.
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+
* http://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+
package com.google.firebase.auth;
18+
19+
import com.google.firebase.auth.internal.GetAccountInfoResponse;
20+
import com.google.firebase.internal.Nullable;
21+
22+
/**
23+
* Interface representing the common properties of a user-enrolled second factor.
24+
*/
25+
public abstract class MultiFactorInfo {
26+
27+
/**
28+
* The ID of the enrolled second factor. This ID is unique to the user.
29+
*/
30+
private final String uid;
31+
32+
/**
33+
* The optional display name of the enrolled second factor.
34+
*/
35+
private final String displayName;
36+
37+
/**
38+
* The type identifier of the second factor. For SMS second factors, this is `phone`.
39+
*/
40+
private final String factorId;
41+
42+
/**
43+
* The optional date the second factor was enrolled, formatted as a UTC string.
44+
*/
45+
private final String enrollmentTime;
46+
47+
MultiFactorInfo(GetAccountInfoResponse.MultiFactorInfo response) {
48+
this.uid = response.getMfaEnrollmentId();
49+
this.displayName = response.getDisplayName();
50+
this.factorId = response.getFactorId();
51+
this.enrollmentTime = response.getEnrollmentTime();
52+
}
53+
54+
public String getUid() {
55+
return uid;
56+
}
57+
58+
@Nullable
59+
public String getDisplayName() {
60+
return displayName;
61+
}
62+
63+
public String getFactorId() {
64+
return factorId;
65+
}
66+
67+
@Nullable
68+
public String getEnrollmentTime() {
69+
return enrollmentTime;
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2022 Google Inc.
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+
* http://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+
package com.google.firebase.auth;
18+
19+
/**
20+
* The multi-factor related user settings.
21+
*/
22+
public class MultiFactorSettings {
23+
24+
private final PhoneMultiFactorInfo[] enrolledFactors;
25+
26+
public MultiFactorSettings(PhoneMultiFactorInfo[] enrolledFactors) {
27+
this.enrolledFactors = enrolledFactors;
28+
}
29+
30+
public PhoneMultiFactorInfo[] getEnrolledFactors() {
31+
return enrolledFactors;
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2022 Google Inc.
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+
* http://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+
package com.google.firebase.auth;
18+
19+
import com.google.firebase.auth.internal.GetAccountInfoResponse;
20+
21+
/**
22+
* Interface representing the common properties of a user-enrolled second factor.
23+
*/
24+
public class PhoneMultiFactorInfo extends MultiFactorInfo {
25+
26+
/**
27+
* The phone number associated with a phone second factor.
28+
*/
29+
private final String phoneNumber;
30+
31+
private final String unobfuscatedPhoneNumber;
32+
33+
public PhoneMultiFactorInfo(GetAccountInfoResponse.MultiFactorInfo response) {
34+
super(response);
35+
36+
this.phoneNumber = response.getPhoneInfo();
37+
this.unobfuscatedPhoneNumber = response.getUnobfuscatedPhoneInfo();
38+
}
39+
40+
public String getPhoneNumber() {
41+
return phoneNumber;
42+
}
43+
44+
public String getUnobfuscatedPhoneNumber() {
45+
return unobfuscatedPhoneNumber;
46+
}
47+
}

src/main/java/com/google/firebase/auth/UserRecord.java

+20
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class UserRecord implements UserInfo {
6161
private final long tokensValidAfterTimestamp;
6262
private final UserMetadata userMetadata;
6363
private final Map<String, Object> customClaims;
64+
private final MultiFactorSettings multiFactor;
6465

6566
UserRecord(User response, JsonFactory jsonFactory) {
6667
checkNotNull(response, "response must not be null");
@@ -82,6 +83,18 @@ public class UserRecord implements UserInfo {
8283
this.providers[i] = new ProviderUserInfo(response.getProviders()[i]);
8384
}
8485
}
86+
87+
if (response.getMfaInfo() == null || response.getMfaInfo().length == 0) {
88+
this.multiFactor = new MultiFactorSettings(new PhoneMultiFactorInfo[0]);
89+
} else {
90+
int mfaInfoLength = response.getMfaInfo().length;
91+
PhoneMultiFactorInfo[] multiFactorInfos = new PhoneMultiFactorInfo[mfaInfoLength];
92+
for (int i = 0; i < multiFactorInfos.length; i++) {
93+
multiFactorInfos[i] = new PhoneMultiFactorInfo(response.getMfaInfo()[i]);
94+
}
95+
this.multiFactor = new MultiFactorSettings(multiFactorInfos);
96+
}
97+
8598
this.tokensValidAfterTimestamp = response.getValidSince() * 1000;
8699

87100
String lastRefreshAtRfc3339 = response.getLastRefreshAt();
@@ -240,6 +253,13 @@ public Map<String,Object> getCustomClaims() {
240253
return customClaims;
241254
}
242255

256+
/**
257+
* The multi-factor related properties for the current user, if available.
258+
*/
259+
public MultiFactorSettings getMultiFactor() {
260+
return multiFactor;
261+
}
262+
243263
/**
244264
* Returns a new {@link UpdateRequest}, which can be used to update the attributes
245265
* of this user.

src/main/java/com/google/firebase/auth/internal/GetAccountInfoResponse.java

+75
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.firebase.auth.internal;
1818

1919
import com.google.api.client.util.Key;
20+
2021
import java.util.List;
2122

2223
/**
@@ -85,6 +86,9 @@ public static class User {
8586
@Key("customAttributes")
8687
private String customClaims;
8788

89+
@Key("mfaInfo")
90+
private MultiFactorInfo[] mfaInfo;
91+
8892
public String getUid() {
8993
return uid;
9094
}
@@ -140,6 +144,10 @@ public long getValidSince() {
140144
public String getCustomClaims() {
141145
return customClaims;
142146
}
147+
148+
public MultiFactorInfo[] getMfaInfo() {
149+
return mfaInfo;
150+
}
143151
}
144152

145153
/**
@@ -189,4 +197,71 @@ public String getProviderId() {
189197
return providerId;
190198
}
191199
}
200+
201+
/**
202+
* JSON data binding for multi factor info data.
203+
*/
204+
public static final class MultiFactorInfo {
205+
/**
206+
* The ID of the enrolled second factor. This ID is unique to the user.
207+
*/
208+
@Key("mfaEnrollmentId")
209+
private String mfaEnrollmentId;
210+
211+
/**
212+
* The optional display name of the enrolled second factor.
213+
*/
214+
@Key("displayName")
215+
private String displayName;
216+
217+
/**
218+
* The type identifier of the second factor. For SMS second factors, this is `phone`.
219+
*/
220+
@Key("factorId")
221+
private String factorId;
222+
223+
/**
224+
* The optional date the second factor was enrolled, formatted as a UTC string.
225+
*/
226+
@Key("enrollmentTime")
227+
private String enrollmentTime;
228+
229+
/**
230+
* Normally this will show the phone number associated with this enrollment.
231+
* In some situations, such as after a first factor sign in,
232+
* it will only show the obfuscated version of the associated phone number.
233+
*/
234+
@Key("phoneInfo")
235+
private String phoneInfo;
236+
237+
/**
238+
* Unobfuscated phoneInfo.
239+
*/
240+
@Key("unobfuscatedPhoneInfo")
241+
private String unobfuscatedPhoneInfo;
242+
243+
public String getMfaEnrollmentId() {
244+
return mfaEnrollmentId;
245+
}
246+
247+
public String getDisplayName() {
248+
return displayName;
249+
}
250+
251+
public String getFactorId() {
252+
return factorId;
253+
}
254+
255+
public String getEnrollmentTime() {
256+
return enrollmentTime;
257+
}
258+
259+
public String getPhoneInfo() {
260+
return phoneInfo;
261+
}
262+
263+
public String getUnobfuscatedPhoneInfo() {
264+
return unobfuscatedPhoneInfo;
265+
}
266+
}
192267
}

src/test/java/com/google/firebase/auth/UserRecordTest.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.Assert.assertEquals;
44
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertNotNull;
56
import static org.junit.Assert.assertNull;
67
import static org.junit.Assert.fail;
78

@@ -17,6 +18,7 @@
1718
import java.io.IOException;
1819
import java.io.InputStream;
1920
import java.nio.charset.Charset;
21+
2022
import org.junit.Test;
2123

2224
public class UserRecordTest {
@@ -92,7 +94,7 @@ public void testAllProviderInfo() throws IOException {
9294
.put("photoUrl", "http://photo.url")
9395
.put("providerId", "providerId")
9496
.build()
95-
)
97+
)
9698
);
9799
String json = JSON_FACTORY.toString(resp);
98100
UserRecord userRecord = parseUser(json);
@@ -108,6 +110,37 @@ public void testAllProviderInfo() throws IOException {
108110
}
109111
}
110112

113+
@Test
114+
public void testPhoneMultiFactors() throws IOException {
115+
ImmutableMap<String, Object> resp = ImmutableMap.<String, Object>of(
116+
"localId", "user",
117+
"mfaInfo", ImmutableList.of(
118+
ImmutableMap.builder()
119+
.put("mfaEnrollmentId", "53HG4HG45HG8G04GJ40J4G3J")
120+
.put("displayName", "Display Name")
121+
.put("factorId", "phone")
122+
.put("enrollmentTime", "Fri, 22 Sep 2017 01:49:58 GMT")
123+
.put("phoneInfo", "+16505551234")
124+
.build()
125+
)
126+
);
127+
String json = JSON_FACTORY.toString(resp);
128+
UserRecord userRecord = parseUser(json);
129+
assertEquals("user", userRecord.getUid());
130+
131+
assertNotNull(userRecord.getMultiFactor());
132+
PhoneMultiFactorInfo[] enrolledFactors = userRecord.getMultiFactor().getEnrolledFactors();
133+
assertEquals(1, enrolledFactors.length);
134+
for (PhoneMultiFactorInfo multiFactorInfo : enrolledFactors) {
135+
assertEquals("53HG4HG45HG8G04GJ40J4G3J", multiFactorInfo.getUid());
136+
assertEquals("Display Name", multiFactorInfo.getDisplayName());
137+
assertEquals("phone", multiFactorInfo.getFactorId());
138+
assertEquals("Fri, 22 Sep 2017 01:49:58 GMT", multiFactorInfo.getEnrollmentTime());
139+
assertEquals("+16505551234", multiFactorInfo.getPhoneNumber());
140+
assertNull(multiFactorInfo.getUnobfuscatedPhoneNumber());
141+
}
142+
}
143+
111144
@Test
112145
public void testUserMetadata() throws IOException {
113146
ImmutableMap<String, Object> resp = ImmutableMap.<String, Object>of(

0 commit comments

Comments
 (0)