Skip to content

Commit 523aa06

Browse files
feat(auth): Added Firebase Auth emulator support (#295)
* feat(auth): Add support for Firebase Authentication Emulator (#289) * Add IdToolkitHostResolver with tests * Add resolution for versions of the API with tests * Simplify and reduce IdToolkitHostResolver to a Util method * Add TestConfig for testing against FirebaseEmulatorHost Add environment variable test that piggybacks on the already established FirebaseUserManagerTest. Also, to make it easier to access and set environment variables a convenience class has been made to help out. This class utilizes the neat builtin get and set mechanism provided by the C# language. By using this class the code somewhat documents itself and prevents stupid spelling mistakes that might occur when running code. Each environment variable can conveniently be documented inside the class. * Add license for new Utils class file * Add documentation for ResolveIdToolkitHost with examples * Use the EnvironmentVariable class to access env variables * Fix formatting * Use inheritance instead of TestConfig to set FirebaseUserManager tests The TestConfig was not being disposed of when expected resulting in the env variable being set in other tests as well and causing them to fail. * Cleanup and rename variable to more meaningful name * Move Utils and EnvironmentVariable class to Auth namespace * Move emulatorHostEnvVar to where it's used for readability * Rename UtilTest to more appropriate name * Rename expected...Host to expected...Url * Use the FirebaseAuthEmulatorHostName const instead of string * Add missing license head * Add missing util import and fix stylecop complaints * Rename AuthUtil to Util and move to Auth test folder * Simplify emulator host environment variable to be a constant in Utils * Use string literal for emulator host in FirebaseUserManagerTest In case a constant values is changed such as the used environment variable, this will cause the tests to fail, which is good to know. * Remove unnecessary tests from FirebaseAuthTest * Use string literal to ensure test fails if constant is changed * Remove the environment simplification since there is only one callsite * Cleanup GetIdToolkitHost documentation * Correct typo in file name. Util -> Utils. * Rename test cass to UtilsTest * Add tenant resolution to GetIdToolkitHost * Add function for resolving GoogleCredentials when in emulator mode * Add test for tenant url resolution * Use if statement instead of ternary for tenantIdPath * Better formatting for GetIdToolkitHost * Add periods to the end of documentation strings * Remove nullable and provide empty string instead * Change to only have one single line at end * Add convenience funcs for getting and checking emulator host env variable * Simplify GetEmulatorHost name * fix(auth): Setting EmulatorHost programatically (#290) * fix(auth): Setting EmulatorHost programatically * fix: Cleaned up tests * fix: Cleaned up user manager tests * feat(auth): Supporting VerifyIdToken() API with emulator (#291) * feat(auth): Emulator support for CreateCustomTokenAsync() API (#293) * feat(auth): Emulator support for CreateCustomTokenAsync() API * fix: Cleaned up unit tests * fix: Removed extra punctuation * feat(auth): Adding emulator support to TenantManager API (#294) * feat(auth): Adding emulator support to TenantManager API * fix: Updated test to check for credentials Co-authored-by: Bjarke <[email protected]>
1 parent f6babbd commit 523aa06

24 files changed

+921
-360
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/Auth/AuthBuilder.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public sealed class AuthBuilder
4141

4242
internal string TenantId { get; set; }
4343

44+
internal string EmulatorHost { get; set; }
45+
46+
internal ISigner Signer { get; set; }
47+
4448
public AbstractFirebaseAuth Build(TestOptions options)
4549
{
4650
if (this.TenantId != null)
@@ -88,6 +92,11 @@ private void PopulateArgs(AbstractFirebaseAuth.Args args, TestOptions options)
8892
$"Session cookie verification not supported on {args.GetType()}");
8993
}
9094
}
95+
96+
if (options.TokenFactory)
97+
{
98+
args.TokenFactory = new Lazy<FirebaseTokenFactory>(this.CreateTokenFactory());
99+
}
91100
}
92101

93102
private FirebaseUserManager CreateUserManager(TestOptions options)
@@ -99,6 +108,7 @@ private FirebaseUserManager CreateUserManager(TestOptions options)
99108
ProjectId = this.ProjectId,
100109
ClientFactory = new MockHttpClientFactory(options.UserManagerRequestHandler),
101110
TenantId = this.TenantId,
111+
EmulatorHost = this.EmulatorHost,
102112
};
103113
return new FirebaseUserManager(args);
104114
}
@@ -107,7 +117,7 @@ private ProviderConfigManager CreateProviderConfigManager(TestOptions options)
107117
{
108118
var args = new ProviderConfigManager.Args
109119
{
110-
RetryOptions = RetryOptions.NoBackOff,
120+
RetryOptions = this.RetryOptions,
111121
ProjectId = this.ProjectId,
112122
ClientFactory = new MockHttpClientFactory(options.ProviderConfigRequestHandler),
113123
TenantId = this.TenantId,
@@ -117,14 +127,31 @@ private ProviderConfigManager CreateProviderConfigManager(TestOptions options)
117127

118128
private FirebaseTokenVerifier CreateIdTokenVerifier()
119129
{
120-
return FirebaseTokenVerifier.CreateIdTokenVerifier(
121-
this.ProjectId, this.KeySource, this.Clock, this.TenantId);
130+
var args = FirebaseTokenVerifier.CreateIdTokenVerifierArgs();
131+
args.ProjectId = this.ProjectId;
132+
args.PublicKeySource = this.KeySource;
133+
args.Clock = this.Clock;
134+
args.TenantId = this.TenantId;
135+
args.IsEmulatorMode = !string.IsNullOrWhiteSpace(this.EmulatorHost);
136+
return new FirebaseTokenVerifier(args);
122137
}
123138

124139
private FirebaseTokenVerifier CreateSessionCookieVerifier()
125140
{
126141
return FirebaseTokenVerifier.CreateSessionCookieVerifier(
127142
this.ProjectId, this.KeySource, this.Clock);
128143
}
144+
145+
private FirebaseTokenFactory CreateTokenFactory()
146+
{
147+
var args = new FirebaseTokenFactory.Args
148+
{
149+
Signer = this.Signer,
150+
Clock = this.Clock,
151+
TenantId = this.TenantId,
152+
IsEmulatorMode = !string.IsNullOrWhiteSpace(this.EmulatorHost),
153+
};
154+
return new FirebaseTokenFactory(args);
155+
}
129156
}
130157
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2021, Google Inc. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using Google.Apis.Auth.OAuth2;
17+
using Xunit;
18+
19+
namespace FirebaseAdmin.Auth.Tests
20+
{
21+
public class FirebaseAuthEmulatorTest : IDisposable
22+
{
23+
private const string AuthEmulatorHost = "FIREBASE_AUTH_EMULATOR_HOST";
24+
25+
private static readonly GoogleCredential MockCredential =
26+
GoogleCredential.FromAccessToken("test-token");
27+
28+
public FirebaseAuthEmulatorTest()
29+
{
30+
Environment.SetEnvironmentVariable(AuthEmulatorHost, "localhost:9090");
31+
}
32+
33+
[Fact]
34+
public void DefaultFirebaseAuth()
35+
{
36+
var app = FirebaseApp.Create(new AppOptions
37+
{
38+
Credential = MockCredential,
39+
ProjectId = "project1",
40+
});
41+
42+
var auth = FirebaseAuth.DefaultInstance;
43+
44+
Assert.Equal("localhost:9090", auth.UserManager.EmulatorHost);
45+
Assert.True(auth.IdTokenVerifier.IsEmulatorMode);
46+
Assert.True(auth.TokenFactory.IsEmulatorMode);
47+
Assert.Equal("localhost:9090", auth.TenantManager.EmulatorHost);
48+
}
49+
50+
[Fact]
51+
public void TenantAwareFirebseAuth()
52+
{
53+
var app = FirebaseApp.Create(new AppOptions
54+
{
55+
Credential = MockCredential,
56+
ProjectId = "project1",
57+
});
58+
59+
var auth = FirebaseAuth.DefaultInstance.TenantManager.AuthForTenant("tenant1");
60+
61+
Assert.Equal("localhost:9090", auth.UserManager.EmulatorHost);
62+
Assert.True(auth.IdTokenVerifier.IsEmulatorMode);
63+
Assert.True(auth.TokenFactory.IsEmulatorMode);
64+
}
65+
66+
public virtual void Dispose()
67+
{
68+
FirebaseApp.DeleteAll();
69+
Environment.SetEnvironmentVariable(AuthEmulatorHost, string.Empty);
70+
}
71+
}
72+
}

FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseAuthTest.cs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ public void UseAfterDelete()
7171
public void NoTenantId()
7272
{
7373
var app = FirebaseApp.Create(new AppOptions
74-
{
75-
Credential = MockCredential,
76-
ProjectId = "project1",
77-
});
74+
{
75+
Credential = MockCredential,
76+
ProjectId = "project1",
77+
});
7878

7979
FirebaseAuth auth = FirebaseAuth.DefaultInstance;
8080

@@ -84,6 +84,23 @@ public void NoTenantId()
8484
Assert.Null(auth.UserManager.TenantId);
8585
}
8686

87+
[Fact]
88+
public void NoEmulator()
89+
{
90+
var app = FirebaseApp.Create(new AppOptions
91+
{
92+
Credential = MockCredential,
93+
ProjectId = "project1",
94+
});
95+
96+
var auth = FirebaseAuth.DefaultInstance;
97+
98+
Assert.False(auth.TokenFactory.IsEmulatorMode);
99+
Assert.False(auth.IdTokenVerifier.IsEmulatorMode);
100+
Assert.Null(auth.UserManager.EmulatorHost);
101+
Assert.Null(auth.TenantManager.EmulatorHost);
102+
}
103+
87104
[Fact]
88105
public void UserManagerNoProjectId()
89106
{
@@ -123,20 +140,57 @@ public void TenantManagerNoProjectId()
123140
ex.Message);
124141
}
125142

143+
[Fact]
144+
public void ServiceAccountCredential()
145+
{
146+
var options = new AppOptions
147+
{
148+
Credential = GoogleCredential.FromFile("./resources/service_account.json"),
149+
};
150+
var app = FirebaseApp.Create(options);
151+
152+
var tokenFactory = FirebaseAuth.DefaultInstance.TokenFactory;
153+
154+
Assert.IsType<ServiceAccountSigner>(tokenFactory.Signer);
155+
}
156+
126157
[Fact]
127158
public void ServiceAccountId()
128159
{
129-
FirebaseApp.Create(new AppOptions
130-
{
131-
Credential = MockCredential,
132-
ServiceAccountId = "test-service-account",
133-
});
160+
var options = new AppOptions
161+
{
162+
Credential = MockCredential,
163+
ServiceAccountId = "test-service-account",
164+
};
165+
var app = FirebaseApp.Create(options);
134166

135167
var tokenFactory = FirebaseAuth.DefaultInstance.TokenFactory;
136168

137169
Assert.IsType<FixedAccountIAMSigner>(tokenFactory.Signer);
138170
}
139171

172+
[Fact]
173+
public async void InvalidCredential()
174+
{
175+
var options = new AppOptions
176+
{
177+
Credential = MockCredential,
178+
};
179+
var app = FirebaseApp.Create(options);
180+
181+
var tokenFactory = FirebaseAuth.DefaultInstance.TokenFactory;
182+
183+
Assert.IsType<IAMSigner>(tokenFactory.Signer);
184+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
185+
() => FirebaseAuth.DefaultInstance.CreateCustomTokenAsync("user1"));
186+
var errorMessage = "Failed to determine service account ID. Make sure to initialize the SDK "
187+
+ "with service account credentials or specify a service account "
188+
+ "ID with iam.serviceAccounts.signBlob permission. Please refer to "
189+
+ "https://firebase.google.com/docs/auth/admin/create-custom-tokens for "
190+
+ "more details on creating custom tokens.";
191+
Assert.Equal(errorMessage, ex.Message);
192+
}
193+
140194
public void Dispose()
141195
{
142196
FirebaseApp.DeleteAll();

FirebaseAdmin/FirebaseAdmin.Tests/Auth/Jwt/CustomTokenTest.cs

Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616
using System.Collections.Generic;
1717
using System.Threading;
1818
using System.Threading.Tasks;
19-
using Google.Apis.Auth.OAuth2;
19+
using FirebaseAdmin.Auth.Tests;
2020
using Xunit;
2121

2222
namespace FirebaseAdmin.Auth.Jwt.Tests
2323
{
24-
public class CustomTokenTest : IDisposable
24+
public class CustomTokenTest
2525
{
2626
public static readonly IEnumerable<object[]> TestConfigs = new List<object[]>()
2727
{
28-
new object[] { FirebaseAuthTestConfig.DefaultInstance },
29-
new object[] { TenantAwareFirebaseAuthTestConfig.DefaultInstance },
28+
new object[] { TestConfig.ForFirebaseAuth() },
29+
new object[] { TestConfig.ForTenantAwareFirebaseAuth("tenant1") },
30+
new object[] { TestConfig.ForFirebaseAuth().WithEmulator() },
31+
new object[] { TestConfig.ForTenantAwareFirebaseAuth("tenant1").WithEmulator() },
3032
};
3133

3234
[Theory]
@@ -66,78 +68,66 @@ await Assert.ThrowsAsync<OperationCanceledException>(
6668
() => auth.CreateCustomTokenAsync("user1", canceller.Token));
6769
}
6870

69-
[Theory]
70-
[MemberData(nameof(TestConfigs))]
71-
public async Task CreateCustomTokenInvalidCredential(TestConfig config)
72-
{
73-
var options = new AppOptions
74-
{
75-
Credential = GoogleCredential.FromAccessToken("test-token"),
76-
ProjectId = "project1",
77-
};
78-
var auth = config.CreateAuth(options);
79-
80-
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
81-
() => auth.CreateCustomTokenAsync("user1"));
82-
83-
var errorMessage = "Failed to determine service account ID. Make sure to initialize the SDK "
84-
+ "with service account credentials or specify a service account "
85-
+ "ID with iam.serviceAccounts.signBlob permission. Please refer to "
86-
+ "https://firebase.google.com/docs/auth/admin/create-custom-tokens for "
87-
+ "more details on creating custom tokens.";
88-
Assert.Equal(errorMessage, ex.Message);
89-
}
90-
91-
public void Dispose()
71+
public sealed class TestConfig
9272
{
93-
FirebaseApp.DeleteAll();
94-
}
73+
private readonly AuthBuilder authBuilder;
74+
private readonly CustomTokenVerifier tokenVerifier;
9575

96-
public abstract class TestConfig
97-
{
98-
protected static readonly AppOptions DefaultOptions = new AppOptions
76+
private TestConfig(AuthBuilder authBuilder, CustomTokenVerifier tokenVerifier)
9977
{
100-
Credential = GoogleCredential.FromFile("./resources/service_account.json"),
101-
};
102-
103-
internal abstract CustomTokenVerifier TokenVerifier { get; }
78+
this.authBuilder = authBuilder;
79+
this.tokenVerifier = tokenVerifier;
80+
}
10481

105-
internal abstract AbstractFirebaseAuth CreateAuth(AppOptions options = null);
82+
public string TenantId => this.authBuilder.TenantId;
10683

107-
internal void AssertCustomToken(
108-
string token, string uid, Dictionary<string, object> claims = null)
84+
public static TestConfig ForFirebaseAuth()
10985
{
110-
this.TokenVerifier.Verify(token, uid, claims);
86+
var authBuilder = new AuthBuilder
87+
{
88+
Signer = JwtTestUtils.DefaultSigner,
89+
};
90+
var tokenVerifier = CustomTokenVerifier.ForServiceAccount(
91+
JwtTestUtils.DefaultClientEmail, JwtTestUtils.DefaultPublicKey);
92+
return new TestConfig(authBuilder, tokenVerifier);
11193
}
112-
}
11394

114-
private sealed class FirebaseAuthTestConfig : TestConfig
115-
{
116-
internal static readonly FirebaseAuthTestConfig DefaultInstance =
117-
new FirebaseAuthTestConfig();
118-
119-
internal override CustomTokenVerifier TokenVerifier =>
120-
CustomTokenVerifier.FromDefaultServiceAccount();
121-
122-
internal override AbstractFirebaseAuth CreateAuth(AppOptions options = null)
95+
public static TestConfig ForTenantAwareFirebaseAuth(string tenantId)
12396
{
124-
FirebaseApp.Create(options ?? DefaultOptions);
125-
return FirebaseAuth.DefaultInstance;
97+
var authBuilder = new AuthBuilder
98+
{
99+
TenantId = tenantId,
100+
Signer = JwtTestUtils.DefaultSigner,
101+
};
102+
var tokenVerifier = CustomTokenVerifier.ForServiceAccount(
103+
JwtTestUtils.DefaultClientEmail, JwtTestUtils.DefaultPublicKey, tenantId);
104+
return new TestConfig(authBuilder, tokenVerifier);
126105
}
127-
}
128106

129-
private sealed class TenantAwareFirebaseAuthTestConfig : TestConfig
130-
{
131-
internal static readonly TenantAwareFirebaseAuthTestConfig DefaultInstance =
132-
new TenantAwareFirebaseAuthTestConfig();
107+
internal TestConfig WithEmulator()
108+
{
109+
var authBuilder = new AuthBuilder
110+
{
111+
TenantId = this.TenantId,
112+
EmulatorHost = "localhost:9090",
113+
};
114+
var tokenVerifier = CustomTokenVerifier.ForEmulator(this.TenantId);
115+
return new TestConfig(authBuilder, tokenVerifier);
116+
}
133117

134-
internal override CustomTokenVerifier TokenVerifier =>
135-
CustomTokenVerifier.FromDefaultServiceAccount("tenant1");
118+
internal AbstractFirebaseAuth CreateAuth()
119+
{
120+
var options = new TestOptions
121+
{
122+
TokenFactory = true,
123+
};
124+
return this.authBuilder.Build(options);
125+
}
136126

137-
internal override AbstractFirebaseAuth CreateAuth(AppOptions options = null)
127+
internal void AssertCustomToken(
128+
string token, string uid, Dictionary<string, object> claims = null)
138129
{
139-
FirebaseApp.Create(options ?? DefaultOptions);
140-
return FirebaseAuth.DefaultInstance.TenantManager.AuthForTenant("tenant1");
130+
this.tokenVerifier.Verify(token, uid, claims);
141131
}
142132
}
143133
}

0 commit comments

Comments
 (0)