17
17
using System . Linq ;
18
18
using System . Net . Http ;
19
19
using System . Text ;
20
+ using System . Threading ;
20
21
using System . Threading . Tasks ;
21
22
using System . Web ;
22
23
using FirebaseAdmin . Auth ;
@@ -37,6 +38,9 @@ public class FirebaseAuthTest
37
38
private const string VerifyCustomTokenUrl =
38
39
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken" ;
39
40
41
+ private const string VerifyPasswordUrl =
42
+ "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword" ;
43
+
40
44
private const string ContinueUrl = "http://localhost/?a=1&b=2#c=3" ;
41
45
42
46
private static readonly ActionCodeSettings EmailLinkSettings = new ActionCodeSettings ( )
@@ -336,6 +340,37 @@ public async Task GetUserNonExistingEmail()
336
340
Assert . Equal ( AuthErrorCode . UserNotFound , exception . AuthErrorCode ) ;
337
341
}
338
342
343
+ [ Fact ]
344
+ public async Task LastRefreshTime ( )
345
+ {
346
+ var newUserRecord = await NewUserWithParamsAsync ( ) ;
347
+ try
348
+ {
349
+ // New users should not have a LastRefreshTimestamp set.
350
+ Assert . Null ( newUserRecord . UserMetaData . LastRefreshTimestamp ) ;
351
+
352
+ // Login to cause the LastRefreshTimestamp to be set.
353
+ await SignInWithPasswordAsync ( newUserRecord . Email , "password" ) ;
354
+
355
+ var userRecord = await FirebaseAuth . DefaultInstance . GetUserAsync ( newUserRecord . Uid ) ;
356
+
357
+ // Ensure the LastRefreshTimstamp is approximately "now" (with a tollerance of 10 minutes).
358
+ var now = DateTime . UtcNow ;
359
+ int tolleranceMinutes = 10 ;
360
+ var minTime = now . AddMinutes ( - tolleranceMinutes ) ;
361
+ var maxTime = now . AddMinutes ( tolleranceMinutes ) ;
362
+ Assert . NotNull ( userRecord . UserMetaData . LastRefreshTimestamp ) ;
363
+ Assert . InRange (
364
+ userRecord . UserMetaData . LastRefreshTimestamp . Value ,
365
+ minTime ,
366
+ maxTime ) ;
367
+ }
368
+ finally
369
+ {
370
+ await FirebaseAuth . DefaultInstance . DeleteUserAsync ( newUserRecord . Uid ) ;
371
+ }
372
+ }
373
+
339
374
[ Fact ]
340
375
public async Task UpdateUserNonExistingUid ( )
341
376
{
@@ -359,6 +394,76 @@ public async Task DeleteUserNonExistingUid()
359
394
Assert . Equal ( AuthErrorCode . UserNotFound , exception . AuthErrorCode ) ;
360
395
}
361
396
397
+ [ Fact ]
398
+ public async Task DeleteUsers ( )
399
+ {
400
+ UserRecord user1 = await NewUserWithParamsAsync ( ) ;
401
+ UserRecord user2 = await NewUserWithParamsAsync ( ) ;
402
+ UserRecord user3 = await NewUserWithParamsAsync ( ) ;
403
+
404
+ DeleteUsersResult deleteUsersResult = await this . SlowDeleteUsersAsync (
405
+ new List < string > { user1 . Uid , user2 . Uid , user3 . Uid } ) ;
406
+
407
+ Assert . Equal ( 3 , deleteUsersResult . SuccessCount ) ;
408
+ Assert . Equal ( 0 , deleteUsersResult . FailureCount ) ;
409
+ Assert . Empty ( deleteUsersResult . Errors ) ;
410
+
411
+ GetUsersResult getUsersResult = await FirebaseAuth . DefaultInstance . GetUsersAsync (
412
+ new List < UserIdentifier >
413
+ {
414
+ new UidIdentifier ( user1 . Uid ) ,
415
+ new UidIdentifier ( user2 . Uid ) ,
416
+ new UidIdentifier ( user3 . Uid ) ,
417
+ } ) ;
418
+
419
+ Assert . Empty ( getUsersResult . Users ) ;
420
+ Assert . Equal ( 3 , getUsersResult . NotFound . Count ( ) ) ;
421
+ }
422
+
423
+ [ Fact ]
424
+ public async Task DeleteExistingAndNonExistingUsers ( )
425
+ {
426
+ UserRecord user1 = await NewUserWithParamsAsync ( ) ;
427
+
428
+ DeleteUsersResult deleteUsersResult = await this . SlowDeleteUsersAsync (
429
+ new List < string > { user1 . Uid , "uid-that-doesnt-exist" } ) ;
430
+
431
+ Assert . Equal ( 2 , deleteUsersResult . SuccessCount ) ;
432
+ Assert . Equal ( 0 , deleteUsersResult . FailureCount ) ;
433
+ Assert . Empty ( deleteUsersResult . Errors ) ;
434
+
435
+ GetUsersResult getUsersResult = await FirebaseAuth . DefaultInstance . GetUsersAsync (
436
+ new List < UserIdentifier >
437
+ {
438
+ new UidIdentifier ( user1 . Uid ) ,
439
+ new UidIdentifier ( "uid-that-doesnt-exist" ) ,
440
+ } ) ;
441
+
442
+ Assert . Empty ( getUsersResult . Users ) ;
443
+ Assert . Equal ( 2 , getUsersResult . NotFound . Count ( ) ) ;
444
+ }
445
+
446
+ [ Fact ]
447
+ public async Task DeleteUsersIsIdempotent ( )
448
+ {
449
+ UserRecord user1 = await NewUserWithParamsAsync ( ) ;
450
+
451
+ DeleteUsersResult result = await this . SlowDeleteUsersAsync (
452
+ new List < string > { user1 . Uid } ) ;
453
+
454
+ Assert . Equal ( 1 , result . SuccessCount ) ;
455
+ Assert . Equal ( 0 , result . FailureCount ) ;
456
+ Assert . Empty ( result . Errors ) ;
457
+
458
+ // Delete the user again, ensuring that everything still counts as a success.
459
+ result = await this . SlowDeleteUsersAsync (
460
+ new List < string > { user1 . Uid } ) ;
461
+
462
+ Assert . Equal ( 1 , result . SuccessCount ) ;
463
+ Assert . Equal ( 0 , result . FailureCount ) ;
464
+ Assert . Empty ( result . Errors ) ;
465
+ }
466
+
362
467
[ Fact ]
363
468
public async Task ListUsers ( )
364
469
{
@@ -520,6 +625,24 @@ public async Task SessionCookie()
520
625
Assert . Equal ( "testuser" , decoded . Uid ) ;
521
626
}
522
627
628
+ private static async Task < UserRecord > NewUserWithParamsAsync ( )
629
+ {
630
+ // TODO(rsgowman): This function could be used throughout this file
631
+ // (similar to the other ports).
632
+ RandomUser randomUser = RandomUser . Create ( ) ;
633
+ var args = new UserRecordArgs ( )
634
+ {
635
+ Uid = randomUser . Uid ,
636
+ Email = randomUser . Email ,
637
+ PhoneNumber = randomUser . PhoneNumber ,
638
+ DisplayName = "Random User" ,
639
+ PhotoUrl = "https://example.com/photo.png" ,
640
+ Password = "password" ,
641
+ } ;
642
+
643
+ return await FirebaseAuth . DefaultInstance . CreateUserAsync ( args ) ;
644
+ }
645
+
523
646
private static async Task < UserRecord > CreateUserForActionLinksAsync ( )
524
647
{
525
648
var randomUser = RandomUser . Create ( ) ;
@@ -558,6 +681,33 @@ private static async Task<string> SignInWithCustomTokenAsync(string customToken)
558
681
}
559
682
}
560
683
684
+ private static async Task < string > SignInWithPasswordAsync ( string email , string password )
685
+ {
686
+ var rb = new Google . Apis . Requests . RequestBuilder ( )
687
+ {
688
+ Method = Google . Apis . Http . HttpConsts . Post ,
689
+ BaseUri = new Uri ( VerifyPasswordUrl ) ,
690
+ } ;
691
+ rb . AddParameter ( RequestParameterType . Query , "key" , IntegrationTestUtils . GetApiKey ( ) ) ;
692
+ var request = rb . CreateRequest ( ) ;
693
+ var jsonSerializer = Google . Apis . Json . NewtonsoftJsonSerializer . Instance ;
694
+ var payload = jsonSerializer . Serialize ( new VerifyPasswordRequest
695
+ {
696
+ Email = email ,
697
+ Password = password ,
698
+ ReturnSecureToken = true ,
699
+ } ) ;
700
+ request . Content = new StringContent ( payload , Encoding . UTF8 , "application/json" ) ;
701
+ using ( var client = new HttpClient ( ) )
702
+ {
703
+ var response = await client . SendAsync ( request ) ;
704
+ response . EnsureSuccessStatusCode ( ) ;
705
+ var json = await response . Content . ReadAsStringAsync ( ) ;
706
+ var parsed = jsonSerializer . Deserialize < VerifyPasswordResponse > ( json ) ;
707
+ return parsed . IdToken ;
708
+ }
709
+ }
710
+
561
711
private static async Task < string > SignInWithEmailLinkAsync ( string email , string oobCode )
562
712
{
563
713
var rb = new Google . Apis . Requests . RequestBuilder ( )
@@ -610,6 +760,132 @@ private static async Task<string> ResetPasswordAsync(ResetPasswordRequest data)
610
760
return ( string ) parsed [ "email" ] ;
611
761
}
612
762
}
763
+
764
+ /**
765
+ * The {@code batchDelete} endpoint is currently rate limited to 1qps. Use this test helper
766
+ * to ensure you don't run into quota exceeded errors.
767
+ */
768
+ // TODO(rsgowman): When/if the rate limit is relaxed, eliminate this helper.
769
+ private async Task < DeleteUsersResult > SlowDeleteUsersAsync ( IReadOnlyList < string > uids )
770
+ {
771
+ await Task . Delay ( millisecondsDelay : 1000 ) ;
772
+ return await FirebaseAuth . DefaultInstance . DeleteUsersAsync ( uids ) ;
773
+ }
774
+
775
+ public class GetUsersFixture : IDisposable
776
+ {
777
+ public GetUsersFixture ( )
778
+ {
779
+ IntegrationTestUtils . EnsureDefaultApp ( ) ;
780
+
781
+ this . TestUser1 = NewUserWithParamsAsync ( ) . Result ;
782
+ this . TestUser2 = NewUserWithParamsAsync ( ) . Result ;
783
+ this . TestUser3 = NewUserWithParamsAsync ( ) . Result ;
784
+
785
+ // The C# port doesn't support importing users, so unlike the other ports, there's
786
+ // no way to create a user with a linked federated provider.
787
+ // TODO(rsgowman): Once either FirebaseAuth.ImportUser() exists (or the UpdateUser()
788
+ // method supports ProviderToLink (#143)), then use it here and
789
+ // adjust the VariousIdentifiers() test below.
790
+ }
791
+
792
+ public UserRecord TestUser1 { get ; }
793
+
794
+ public UserRecord TestUser2 { get ; }
795
+
796
+ public UserRecord TestUser3 { get ; }
797
+
798
+ public void Dispose ( )
799
+ {
800
+ // TODO(rsgowman): deleteUsers (plural) would make more sense here, but it's
801
+ // currently rate limited to 1qps. When/if that's relaxed, change this to just
802
+ // delete them all at once.
803
+ var auth = FirebaseAuth . DefaultInstance ;
804
+ auth . DeleteUserAsync ( this . TestUser1 . Uid ) . Wait ( ) ;
805
+ auth . DeleteUserAsync ( this . TestUser2 . Uid ) . Wait ( ) ;
806
+ auth . DeleteUserAsync ( this . TestUser3 . Uid ) . Wait ( ) ;
807
+ }
808
+ }
809
+
810
+ public class GetUsers : IClassFixture < GetUsersFixture >
811
+ {
812
+ private FirebaseAuth auth ;
813
+ private UserRecord testUser1 ;
814
+ private UserRecord testUser2 ;
815
+ private UserRecord testUser3 ;
816
+
817
+ public GetUsers ( GetUsersFixture fixture )
818
+ {
819
+ this . auth = FirebaseAuth . DefaultInstance ;
820
+ this . testUser1 = fixture . TestUser1 ;
821
+ this . testUser2 = fixture . TestUser2 ;
822
+ this . testUser3 = fixture . TestUser3 ;
823
+ }
824
+
825
+ [ Fact ]
826
+ public async void VariousIdentifiers ( )
827
+ {
828
+ var getUsersResult = await this . auth . GetUsersAsync ( new List < UserIdentifier > ( )
829
+ {
830
+ new UidIdentifier ( this . testUser1 . Uid ) ,
831
+ new EmailIdentifier ( this . testUser2 . Email ) ,
832
+ new PhoneIdentifier ( this . testUser3 . PhoneNumber ) ,
833
+ // TODO(rsgowman): Once we're able to create a user with a
834
+ // provider, do so above and fetch the user like this:
835
+ // new ProviderIdentifier("google.com", "google_" + importUserUid),
836
+ } ) ;
837
+
838
+ var uids = getUsersResult . Users . Select ( userRecord => userRecord . Uid ) ;
839
+ var expectedUids = new List < string > ( ) { this . testUser1 . Uid , this . testUser2 . Uid , this . testUser3 . Uid } ;
840
+ Assert . True ( expectedUids . All ( expectedUid => uids . Contains ( expectedUid ) ) ) ;
841
+ Assert . Empty ( getUsersResult . NotFound ) ;
842
+ }
843
+
844
+ [ Fact ]
845
+ public async void IgnoresNonExistingUsers ( )
846
+ {
847
+ var doesntExistId = new UidIdentifier ( "uid_that_doesnt_exist" ) ;
848
+ var getUsersResult = await this . auth . GetUsersAsync ( new List < UserIdentifier > ( )
849
+ {
850
+ new UidIdentifier ( this . testUser1 . Uid ) ,
851
+ doesntExistId ,
852
+ new UidIdentifier ( this . testUser3 . Uid ) ,
853
+ } ) ;
854
+
855
+ var uids = getUsersResult . Users . Select ( userRecord => userRecord . Uid ) ;
856
+ var expectedUids = new List < string > ( ) { this . testUser1 . Uid , this . testUser3 . Uid } ;
857
+ Assert . True ( expectedUids . All ( expectedUid => uids . Contains ( expectedUid ) ) ) ;
858
+ Assert . Equal ( doesntExistId , getUsersResult . NotFound . Single ( ) ) ;
859
+ }
860
+
861
+ [ Fact ]
862
+ public async void OnlyNonExistingUsers ( )
863
+ {
864
+ var doesntExistId = new UidIdentifier ( "uid_that_doesnt_exist" ) ;
865
+ var getUsersResult = await this . auth . GetUsersAsync ( new List < UserIdentifier > ( )
866
+ {
867
+ doesntExistId ,
868
+ } ) ;
869
+
870
+ Assert . Empty ( getUsersResult . Users ) ;
871
+ Assert . Equal ( doesntExistId , getUsersResult . NotFound . Single ( ) ) ;
872
+ }
873
+
874
+ [ Fact ]
875
+ public async void DedupsDuplicateUsers ( )
876
+ {
877
+ var getUsersResult = await this . auth . GetUsersAsync ( new List < UserIdentifier > ( )
878
+ {
879
+ new UidIdentifier ( this . testUser1 . Uid ) ,
880
+ new UidIdentifier ( this . testUser1 . Uid ) ,
881
+ } ) ;
882
+
883
+ var uids = getUsersResult . Users . Select ( userRecord => userRecord . Uid ) ;
884
+ var expectedUids = new List < string > ( ) { this . testUser3 . Uid } ;
885
+ Assert . Equal ( this . testUser1 . Uid , getUsersResult . Users . Single ( ) . Uid ) ;
886
+ Assert . Empty ( getUsersResult . NotFound ) ;
887
+ }
888
+ }
613
889
}
614
890
615
891
/**
@@ -656,6 +932,24 @@ internal class SignInResponse
656
932
public string IdToken { get ; set ; }
657
933
}
658
934
935
+ internal class VerifyPasswordRequest
936
+ {
937
+ [ Newtonsoft . Json . JsonProperty ( "email" ) ]
938
+ public string Email { get ; set ; }
939
+
940
+ [ Newtonsoft . Json . JsonProperty ( "password" ) ]
941
+ public string Password { get ; set ; }
942
+
943
+ [ Newtonsoft . Json . JsonProperty ( "returnSecureToken" ) ]
944
+ public bool ReturnSecureToken { get ; set ; }
945
+ }
946
+
947
+ internal class VerifyPasswordResponse
948
+ {
949
+ [ Newtonsoft . Json . JsonProperty ( "idToken" ) ]
950
+ public string IdToken { get ; set ; }
951
+ }
952
+
659
953
internal class RandomUser
660
954
{
661
955
internal string Uid { get ; private set ; }
0 commit comments