Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Firebase Auth MFA info to user record #745

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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