Skip to content

Commit

Permalink
Merge dev into master
Browse files Browse the repository at this point in the history
  • Loading branch information
google-oss-bot committed Apr 14, 2021
2 parents 05378ef + 24bca17 commit 4121c50
Show file tree
Hide file tree
Showing 9 changed files with 472 additions and 39 deletions.
2 changes: 1 addition & 1 deletion auth/tenant_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func TestTenantListUsers(t *testing.T) {
want := []*ExportedUserRecord{
{UserRecord: testUser, PasswordHash: "passwordhash1", PasswordSalt: "salt1"},
{UserRecord: testUser, PasswordHash: "passwordhash2", PasswordSalt: "salt2"},
{UserRecord: testUser, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
{UserRecord: testUserWithoutMFA, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
}

testIterator := func(iter *UserIterator, token string, req string) {
Expand Down
87 changes: 70 additions & 17 deletions auth/user_mgt.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ type UserInfo struct {
UID string `json:"rawId,omitempty"`
}

// multiFactorInfoResponse describes the `mfaInfo` of the user record API response
type multiFactorInfoResponse struct {
MFAEnrollmentID string `json:"mfaEnrollmentId,omitempty"`
DisplayName string `json:"displayName,omitempty"`
PhoneInfo string `json:"phoneInfo,omitempty"`
EnrolledAt string `json:"enrolledAt,omitempty"`
}

// MultiFactorInfo describes a user enrolled second phone factor.
type MultiFactorInfo struct {
UID string
DisplayName string
EnrollmentTimestamp int64
FactorID string
PhoneNumber string
}

// MultiFactorSettings describes the multi-factor related user settings.
type MultiFactorSettings struct {
EnrolledFactors []*MultiFactorInfo
}

// UserMetadata contains additional metadata associated with a user account.
// Timestamps are in milliseconds since epoch.
type UserMetadata struct {
Expand All @@ -77,6 +99,7 @@ type UserRecord struct {
TokensValidAfterMillis int64 // milliseconds since epoch.
UserMetadata *UserMetadata
TenantID string
MultiFactor *MultiFactorSettings
}

// UserToCreate is the parameter struct for the CreateUser function.
Expand Down Expand Up @@ -892,23 +915,24 @@ func (c *baseClient) GetUsers(
}

type userQueryResponse struct {
UID string `json:"localId,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PhotoURL string `json:"photoUrl,omitempty"`
CreationTimestamp int64 `json:"createdAt,string,omitempty"`
LastLogInTimestamp int64 `json:"lastLoginAt,string,omitempty"`
LastRefreshAt string `json:"lastRefreshAt,omitempty"`
ProviderID string `json:"providerId,omitempty"`
CustomAttributes string `json:"customAttributes,omitempty"`
Disabled bool `json:"disabled,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
ProviderUserInfo []*UserInfo `json:"providerUserInfo,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
PasswordSalt string `json:"salt,omitempty"`
TenantID string `json:"tenantId,omitempty"`
ValidSinceSeconds int64 `json:"validSince,string,omitempty"`
UID string `json:"localId,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PhotoURL string `json:"photoUrl,omitempty"`
CreationTimestamp int64 `json:"createdAt,string,omitempty"`
LastLogInTimestamp int64 `json:"lastLoginAt,string,omitempty"`
LastRefreshAt string `json:"lastRefreshAt,omitempty"`
ProviderID string `json:"providerId,omitempty"`
CustomAttributes string `json:"customAttributes,omitempty"`
Disabled bool `json:"disabled,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
ProviderUserInfo []*UserInfo `json:"providerUserInfo,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
PasswordSalt string `json:"salt,omitempty"`
TenantID string `json:"tenantId,omitempty"`
ValidSinceSeconds int64 `json:"validSince,string,omitempty"`
MFAInfo []*multiFactorInfoResponse `json:"mfaInfo,omitempty"`
}

func (r *userQueryResponse) makeUserRecord() (*UserRecord, error) {
Expand Down Expand Up @@ -948,6 +972,32 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
lastRefreshTimestamp = t.Unix() * 1000
}

// Map the MFA info to a slice of enrolled factors. Currently there is only
// support for PhoneMultiFactorInfo.
var enrolledFactors []*MultiFactorInfo
for _, factor := range r.MFAInfo {
var enrollmentTimestamp int64
if factor.EnrolledAt != "" {
t, err := time.Parse(time.RFC3339, factor.EnrolledAt)
if err != nil {
return nil, err
}
enrollmentTimestamp = t.Unix() * 1000
}

if factor.PhoneInfo == "" {
return nil, fmt.Errorf("unsupported multi-factor auth response: %#v", factor)
}

enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
UID: factor.MFAEnrollmentID,
DisplayName: factor.DisplayName,
EnrollmentTimestamp: enrollmentTimestamp,
FactorID: "phone",
PhoneNumber: factor.PhoneInfo,
})
}

return &ExportedUserRecord{
UserRecord: &UserRecord{
UserInfo: &UserInfo{
Expand All @@ -969,6 +1019,9 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
CreationTimestamp: r.CreationTimestamp,
LastRefreshTimestamp: lastRefreshTimestamp,
},
MultiFactor: &MultiFactorSettings{
EnrolledFactors: enrolledFactors,
},
},
PasswordHash: hash,
PasswordSalt: r.PasswordSalt,
Expand Down
74 changes: 71 additions & 3 deletions auth/user_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ var testUser = &UserRecord{
PhotoURL: "http://www.example.com/testuser/photo.png",
ProviderID: defaultProviderID,
},
Disabled: false,

Disabled: false,
EmailVerified: true,
ProviderUserInfo: []*UserInfo{
{
Expand All @@ -67,6 +66,51 @@ var testUser = &UserRecord{
},
CustomClaims: map[string]interface{}{"admin": true, "package": "gold"},
TenantID: "testTenant",
MultiFactor: &MultiFactorSettings{
EnrolledFactors: []*MultiFactorInfo{
{
UID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
FactorID: "phone",
EnrollmentTimestamp: 1614776780000,
PhoneNumber: "+1234567890",
DisplayName: "My MFA Phone",
},
},
},
}

var testUserWithoutMFA = &UserRecord{
UserInfo: &UserInfo{
UID: "testusernomfa",
Email: "testusernomfa@example.com",
PhoneNumber: "+1234567890",
DisplayName: "Test User Without MFA",
PhotoURL: "http://www.example.com/testusernomfa/photo.png",
ProviderID: defaultProviderID,
},
Disabled: false,
EmailVerified: true,
ProviderUserInfo: []*UserInfo{
{
ProviderID: "password",
DisplayName: "Test User Without MFA",
PhotoURL: "http://www.example.com/testusernomfa/photo.png",
Email: "testusernomfa@example.com",
UID: "testuid",
}, {
ProviderID: "phone",
PhoneNumber: "+1234567890",
UID: "testuid",
},
},
TokensValidAfterMillis: 1494364393000,
UserMetadata: &UserMetadata{
CreationTimestamp: 1234567890000,
LastLogInTimestamp: 1233211232000,
},
CustomClaims: map[string]interface{}{"admin": true, "package": "gold"},
TenantID: "testTenant",
MultiFactor: &MultiFactorSettings{},
}

func TestGetUser(t *testing.T) {
Expand Down Expand Up @@ -501,7 +545,7 @@ func TestListUsers(t *testing.T) {
want := []*ExportedUserRecord{
{UserRecord: testUser, PasswordHash: "passwordhash1", PasswordSalt: "salt1"},
{UserRecord: testUser, PasswordHash: "passwordhash2", PasswordSalt: "salt2"},
{UserRecord: testUser, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
{UserRecord: testUserWithoutMFA, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
}

testIterator := func(iter *UserIterator, token string, req string) {
Expand Down Expand Up @@ -1596,6 +1640,14 @@ func TestMakeExportedUser(t *testing.T) {
PhoneNumber: "+1234567890",
UID: "testuid",
}},
MFAInfo: []*multiFactorInfoResponse{
{
PhoneInfo: "+1234567890",
MFAEnrollmentID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
DisplayName: "My MFA Phone",
EnrolledAt: "2021-03-03T13:06:20.542896Z",
},
},
}

want := &ExportedUserRecord{
Expand All @@ -1620,6 +1672,22 @@ func TestMakeExportedUser(t *testing.T) {
}
}

func TestUnsupportedAuthFactor(t *testing.T) {
queryResponse := &userQueryResponse{
UID: "uid1",
MFAInfo: []*multiFactorInfoResponse{
{
MFAEnrollmentID: "enrollementId",
},
},
}

exported, err := queryResponse.makeExportedUserRecord()
if exported != nil || err == nil {
t.Errorf("makeExportedUserRecord() = (%v, %v); want = (nil, error)", exported, err)
}
}

func TestExportedUserRecordShouldClearRedacted(t *testing.T) {
queryResponse := &userQueryResponse{
UID: "uid1",
Expand Down
2 changes: 1 addition & 1 deletion firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import (
var defaultAuthOverrides = make(map[string]interface{})

// Version of the Firebase Go Admin SDK.
const Version = "4.4.0"
const Version = "4.5.0"

// firebaseEnvName is the name of the environment variable with the Config.
const firebaseEnvName = "FIREBASE_CONFIG"
Expand Down
11 changes: 5 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ module firebase.google.com/go/v4
go 1.11

require (
cloud.google.com/go/firestore v1.1.1
cloud.google.com/go/storage v1.0.0
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/api v0.17.0
google.golang.org/appengine v1.6.1
google.golang.org/grpc v1.29.1 // indirect
cloud.google.com/go/firestore v1.5.0
cloud.google.com/go/storage v1.10.0
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99
google.golang.org/api v0.40.0
google.golang.org/appengine v1.6.7
)
Loading

0 comments on commit 4121c50

Please sign in to comment.