Skip to content

Commit

Permalink
Add Firebase Auth MFA info to user record
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardmp committed Dec 5, 2022
1 parent d24289a commit 612a9bb
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 1 deletion.
71 changes: 71 additions & 0 deletions src/main/java/com/google/firebase/auth/MultiFactorInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2022 Google Inc.
*
* 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.
*/

package com.google.firebase.auth;

import com.google.firebase.auth.internal.GetAccountInfoResponse;
import com.google.firebase.internal.Nullable;

/**
* Interface representing the common properties of a user-enrolled second factor.
*/
public abstract class MultiFactorInfo {

/**
* The ID of the enrolled second factor. This ID is unique to the user.
*/
private final String uid;

/**
* The optional display name of the enrolled second factor.
*/
private final String displayName;

/**
* The type identifier of the second factor. For SMS second factors, this is `phone`.
*/
private final String factorId;

/**
* The optional date the second factor was enrolled, formatted as a UTC string.
*/
private final String enrollmentTime;

MultiFactorInfo(GetAccountInfoResponse.MultiFactorInfo response) {
this.uid = response.getMfaEnrollmentId();
this.displayName = response.getDisplayName();
this.factorId = response.getFactorId();
this.enrollmentTime = response.getEnrollmentTime();
}

public String getUid() {
return uid;
}

@Nullable
public String getDisplayName() {
return displayName;
}

public String getFactorId() {
return factorId;
}

@Nullable
public String getEnrollmentTime() {
return enrollmentTime;
}
}
33 changes: 33 additions & 0 deletions src/main/java/com/google/firebase/auth/MultiFactorSettings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2022 Google Inc.
*
* 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.
*/

package com.google.firebase.auth;

/**
* The multi-factor related user settings.
*/
public class MultiFactorSettings {

private final PhoneMultiFactorInfo[] enrolledFactors;

public MultiFactorSettings(PhoneMultiFactorInfo[] enrolledFactors) {
this.enrolledFactors = enrolledFactors;
}

public PhoneMultiFactorInfo[] getEnrolledFactors() {
return enrolledFactors;
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/google/firebase/auth/PhoneMultiFactorInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2022 Google Inc.
*
* 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.
*/

package com.google.firebase.auth;

import com.google.firebase.auth.internal.GetAccountInfoResponse;

/**
* Interface representing the common properties of a user-enrolled second factor.
*/
public class PhoneMultiFactorInfo extends MultiFactorInfo {

/**
* The phone number associated with a phone second factor.
*/
private final String phoneNumber;

private final String unobfuscatedPhoneNumber;

public PhoneMultiFactorInfo(GetAccountInfoResponse.MultiFactorInfo response) {
super(response);

this.phoneNumber = response.getPhoneInfo();
this.unobfuscatedPhoneNumber = response.getUnobfuscatedPhoneInfo();
}

public String getPhoneNumber() {
return phoneNumber;
}

public String getUnobfuscatedPhoneNumber() {
return unobfuscatedPhoneNumber;
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/google/firebase/auth/UserRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class UserRecord implements UserInfo {
private final long tokensValidAfterTimestamp;
private final UserMetadata userMetadata;
private final Map<String, Object> customClaims;
private final MultiFactorSettings multiFactor;

UserRecord(User response, JsonFactory jsonFactory) {
checkNotNull(response, "response must not be null");
Expand All @@ -82,6 +83,18 @@ public class UserRecord implements UserInfo {
this.providers[i] = new ProviderUserInfo(response.getProviders()[i]);
}
}

if (response.getMfaInfo() == null || response.getMfaInfo().length == 0) {
this.multiFactor = new MultiFactorSettings(new PhoneMultiFactorInfo[0]);
} else {
int mfaInfoLength = response.getMfaInfo().length;
PhoneMultiFactorInfo[] multiFactorInfos = new PhoneMultiFactorInfo[mfaInfoLength];
for (int i = 0; i < multiFactorInfos.length; i++) {
multiFactorInfos[i] = new PhoneMultiFactorInfo(response.getMfaInfo()[i]);
}
this.multiFactor = new MultiFactorSettings(multiFactorInfos);
}

this.tokensValidAfterTimestamp = response.getValidSince() * 1000;

String lastRefreshAtRfc3339 = response.getLastRefreshAt();
Expand Down Expand Up @@ -240,6 +253,13 @@ public Map<String,Object> getCustomClaims() {
return customClaims;
}

/**
* The multi-factor related properties for the current user, if available.
*/
public MultiFactorSettings getMultiFactor() {
return multiFactor;
}

/**
* Returns a new {@link UpdateRequest}, which can be used to update the attributes
* of this user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.firebase.auth.internal;

import com.google.api.client.util.Key;

import java.util.List;

/**
Expand Down Expand Up @@ -85,6 +86,9 @@ public static class User {
@Key("customAttributes")
private String customClaims;

@Key("mfaInfo")
private MultiFactorInfo[] mfaInfo;

public String getUid() {
return uid;
}
Expand Down Expand Up @@ -140,6 +144,10 @@ public long getValidSince() {
public String getCustomClaims() {
return customClaims;
}

public MultiFactorInfo[] getMfaInfo() {
return mfaInfo;
}
}

/**
Expand Down Expand Up @@ -189,4 +197,71 @@ public String getProviderId() {
return providerId;
}
}

/**
* JSON data binding for multi factor info data.
*/
public static final class MultiFactorInfo {
/**
* The ID of the enrolled second factor. This ID is unique to the user.
*/
@Key("mfaEnrollmentId")
private String mfaEnrollmentId;

/**
* The optional display name of the enrolled second factor.
*/
@Key("displayName")
private String displayName;

/**
* The type identifier of the second factor. For SMS second factors, this is `phone`.
*/
@Key("factorId")
private String factorId;

/**
* The optional date the second factor was enrolled, formatted as a UTC string.
*/
@Key("enrollmentTime")
private String enrollmentTime;

/**
* Normally this will show the phone number associated with this enrollment.
* In some situations, such as after a first factor sign in,
* it will only show the obfuscated version of the associated phone number.
*/
@Key("phoneInfo")
private String phoneInfo;

/**
* Unobfuscated phoneInfo.
*/
@Key("unobfuscatedPhoneInfo")
private String unobfuscatedPhoneInfo;

public String getMfaEnrollmentId() {
return mfaEnrollmentId;
}

public String getDisplayName() {
return displayName;
}

public String getFactorId() {
return factorId;
}

public String getEnrollmentTime() {
return enrollmentTime;
}

public String getPhoneInfo() {
return phoneInfo;
}

public String getUnobfuscatedPhoneInfo() {
return unobfuscatedPhoneInfo;
}
}
}
35 changes: 34 additions & 1 deletion src/test/java/com/google/firebase/auth/UserRecordTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

Expand All @@ -17,6 +18,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

import org.junit.Test;

public class UserRecordTest {
Expand Down Expand Up @@ -92,7 +94,7 @@ public void testAllProviderInfo() throws IOException {
.put("photoUrl", "http://photo.url")
.put("providerId", "providerId")
.build()
)
)
);
String json = JSON_FACTORY.toString(resp);
UserRecord userRecord = parseUser(json);
Expand All @@ -108,6 +110,37 @@ public void testAllProviderInfo() throws IOException {
}
}

@Test
public void testPhoneMultiFactors() throws IOException {
ImmutableMap<String, Object> resp = ImmutableMap.<String, Object>of(
"localId", "user",
"mfaInfo", ImmutableList.of(
ImmutableMap.builder()
.put("mfaEnrollmentId", "53HG4HG45HG8G04GJ40J4G3J")
.put("displayName", "Display Name")
.put("factorId", "phone")
.put("enrollmentTime", "Fri, 22 Sep 2017 01:49:58 GMT")
.put("phoneInfo", "+16505551234")
.build()
)
);
String json = JSON_FACTORY.toString(resp);
UserRecord userRecord = parseUser(json);
assertEquals("user", userRecord.getUid());

assertNotNull(userRecord.getMultiFactor());
PhoneMultiFactorInfo[] enrolledFactors = userRecord.getMultiFactor().getEnrolledFactors();
assertEquals(1, enrolledFactors.length);
for (PhoneMultiFactorInfo multiFactorInfo : enrolledFactors) {
assertEquals("53HG4HG45HG8G04GJ40J4G3J", multiFactorInfo.getUid());
assertEquals("Display Name", multiFactorInfo.getDisplayName());
assertEquals("phone", multiFactorInfo.getFactorId());
assertEquals("Fri, 22 Sep 2017 01:49:58 GMT", multiFactorInfo.getEnrollmentTime());
assertEquals("+16505551234", multiFactorInfo.getPhoneNumber());
assertNull(multiFactorInfo.getUnobfuscatedPhoneNumber());
}
}

@Test
public void testUserMetadata() throws IOException {
ImmutableMap<String, Object> resp = ImmutableMap.<String, Object>of(
Expand Down

0 comments on commit 612a9bb

Please sign in to comment.