Skip to content

Commit 6cf48b2

Browse files
authored
feat(auth): Added CreateTenantAsync() and UpdateTenantAsync() APIs (#223)
1 parent 6c1fea1 commit 6cf48b2

File tree

7 files changed

+450
-68
lines changed

7 files changed

+450
-68
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/Auth/Multitenancy/TenantManagerTest.cs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
using FirebaseAdmin.Tests;
2121
using FirebaseAdmin.Util;
2222
using Google.Apis.Auth.OAuth2;
23+
using Google.Apis.Json;
24+
using Newtonsoft.Json.Linq;
2325
using Xunit;
2426

2527
namespace FirebaseAdmin.Auth.Multitenancy.Tests
@@ -45,6 +47,12 @@ public class TenantManagerTest
4547
}
4648
}";
4749

50+
private const string UnknownErrorResponse = @"{
51+
""error"": {
52+
""message"": ""UNKNOWN""
53+
}
54+
}";
55+
4856
private static readonly string ClientVersion =
4957
$"DotNet/Admin/{FirebaseApp.GetSdkVersion()}";
5058

@@ -102,6 +110,214 @@ public async Task GetTenantNotFoundError()
102110
Assert.Null(exception.InnerException);
103111
}
104112

113+
[Fact]
114+
public async Task CreateTenant()
115+
{
116+
var handler = new MockMessageHandler()
117+
{
118+
Response = TenantResponse,
119+
};
120+
var auth = CreateFirebaseAuth(handler);
121+
var args = new TenantArgs()
122+
{
123+
DisplayName = "Test Tenant",
124+
PasswordSignUpAllowed = true,
125+
EmailLinkSignInEnabled = true,
126+
};
127+
128+
var provider = await auth.TenantManager.CreateTenantAsync(args);
129+
130+
AssertTenant(provider);
131+
Assert.Equal(1, handler.Requests.Count);
132+
var request = handler.Requests[0];
133+
Assert.Equal(HttpMethod.Post, request.Method);
134+
Assert.Equal("/v2/projects/project1/tenants", request.Url.PathAndQuery);
135+
AssertClientVersionHeader(request);
136+
137+
var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
138+
handler.LastRequestBody);
139+
Assert.Equal(3, body.Count);
140+
Assert.Equal("Test Tenant", body["displayName"]);
141+
Assert.True((bool)body["allowPasswordSignup"]);
142+
Assert.True((bool)body["enableEmailLinkSignin"]);
143+
}
144+
145+
[Fact]
146+
public async Task CreateTenantMinimal()
147+
{
148+
var handler = new MockMessageHandler()
149+
{
150+
Response = TenantResponse,
151+
};
152+
var auth = CreateFirebaseAuth(handler);
153+
154+
var provider = await auth.TenantManager.CreateTenantAsync(new TenantArgs());
155+
156+
AssertTenant(provider);
157+
Assert.Equal(1, handler.Requests.Count);
158+
var request = handler.Requests[0];
159+
Assert.Equal(HttpMethod.Post, request.Method);
160+
Assert.Equal("/v2/projects/project1/tenants", request.Url.PathAndQuery);
161+
AssertClientVersionHeader(request);
162+
163+
var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
164+
handler.LastRequestBody);
165+
Assert.Empty(body);
166+
}
167+
168+
[Fact]
169+
public async Task CreateTenantNullArgs()
170+
{
171+
var auth = CreateFirebaseAuth();
172+
173+
await Assert.ThrowsAsync<ArgumentNullException>(
174+
() => auth.TenantManager.CreateTenantAsync(null));
175+
}
176+
177+
[Fact]
178+
public async Task CreateTenantError()
179+
{
180+
var handler = new MockMessageHandler()
181+
{
182+
StatusCode = HttpStatusCode.InternalServerError,
183+
Response = UnknownErrorResponse,
184+
};
185+
var auth = CreateFirebaseAuth(handler);
186+
187+
var exception = await Assert.ThrowsAsync<FirebaseAuthException>(
188+
() => auth.TenantManager.CreateTenantAsync(new TenantArgs()));
189+
Assert.Equal(ErrorCode.Internal, exception.ErrorCode);
190+
Assert.Null(exception.AuthErrorCode);
191+
Assert.StartsWith(
192+
"Unexpected HTTP response with status: 500 (InternalServerError)",
193+
exception.Message);
194+
Assert.NotNull(exception.HttpResponse);
195+
Assert.Null(exception.InnerException);
196+
}
197+
198+
[Fact]
199+
public async Task UpdateTenant()
200+
{
201+
var handler = new MockMessageHandler()
202+
{
203+
Response = TenantResponse,
204+
};
205+
var auth = CreateFirebaseAuth(handler);
206+
var args = new TenantArgs()
207+
{
208+
DisplayName = "Test Tenant",
209+
PasswordSignUpAllowed = true,
210+
EmailLinkSignInEnabled = true,
211+
};
212+
213+
var provider = await auth.TenantManager.UpdateTenantAsync("tenant1", args);
214+
215+
AssertTenant(provider);
216+
Assert.Equal(1, handler.Requests.Count);
217+
var request = handler.Requests[0];
218+
Assert.Equal(HttpUtils.Patch, request.Method);
219+
var mask = "allowPasswordSignup,displayName,enableEmailLinkSignin";
220+
Assert.Equal(
221+
$"/v2/projects/project1/tenants/tenant1?updateMask={mask}",
222+
request.Url.PathAndQuery);
223+
AssertClientVersionHeader(request);
224+
225+
var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
226+
handler.LastRequestBody);
227+
Assert.Equal(3, body.Count);
228+
Assert.Equal("Test Tenant", body["displayName"]);
229+
Assert.True((bool)body["allowPasswordSignup"]);
230+
Assert.True((bool)body["enableEmailLinkSignin"]);
231+
}
232+
233+
[Fact]
234+
public async Task UpdateTenantMinimal()
235+
{
236+
var handler = new MockMessageHandler()
237+
{
238+
Response = TenantResponse,
239+
};
240+
var auth = CreateFirebaseAuth(handler);
241+
var args = new TenantArgs()
242+
{
243+
DisplayName = "Test Tenant",
244+
};
245+
246+
var provider = await auth.TenantManager.UpdateTenantAsync("tenant1", args);
247+
248+
AssertTenant(provider);
249+
Assert.Equal(1, handler.Requests.Count);
250+
var request = handler.Requests[0];
251+
Assert.Equal(HttpUtils.Patch, request.Method);
252+
Assert.Equal(
253+
"/v2/projects/project1/tenants/tenant1?updateMask=displayName",
254+
request.Url.PathAndQuery);
255+
AssertClientVersionHeader(request);
256+
257+
var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
258+
handler.LastRequestBody);
259+
Assert.Single(body);
260+
Assert.Equal("Test Tenant", body["displayName"]);
261+
}
262+
263+
[Theory]
264+
[MemberData(nameof(InvalidStrings))]
265+
public async Task UpdateTenantNoId(string tenantId)
266+
{
267+
var auth = CreateFirebaseAuth();
268+
var args = new TenantArgs()
269+
{
270+
DisplayName = "Test Tenant",
271+
};
272+
273+
var exception = await Assert.ThrowsAsync<ArgumentException>(
274+
() => auth.TenantManager.UpdateTenantAsync(tenantId, args));
275+
Assert.Equal("Tenant ID cannot be null or empty.", exception.Message);
276+
}
277+
278+
[Fact]
279+
public async Task UpdateTenantNullArgs()
280+
{
281+
var auth = CreateFirebaseAuth();
282+
283+
await Assert.ThrowsAsync<ArgumentNullException>(
284+
() => auth.TenantManager.UpdateTenantAsync("tenant1", null));
285+
}
286+
287+
[Fact]
288+
public async Task UpdateTenantEmptyArgs()
289+
{
290+
var auth = CreateFirebaseAuth();
291+
292+
await Assert.ThrowsAsync<ArgumentException>(
293+
() => auth.TenantManager.UpdateTenantAsync("tenant1", new TenantArgs()));
294+
}
295+
296+
[Fact]
297+
public async Task UpdateTenantError()
298+
{
299+
var handler = new MockMessageHandler()
300+
{
301+
StatusCode = HttpStatusCode.NotFound,
302+
Response = TenantNotFoundResponse,
303+
};
304+
var auth = CreateFirebaseAuth(handler);
305+
var args = new TenantArgs()
306+
{
307+
DisplayName = "Test Tenant",
308+
};
309+
310+
var exception = await Assert.ThrowsAsync<FirebaseAuthException>(
311+
() => auth.TenantManager.UpdateTenantAsync("tenant1", args));
312+
Assert.Equal(ErrorCode.NotFound, exception.ErrorCode);
313+
Assert.Equal(AuthErrorCode.TenantNotFound, exception.AuthErrorCode);
314+
Assert.Equal(
315+
"No tenant found for the given identifier (TENANT_NOT_FOUND).",
316+
exception.Message);
317+
Assert.NotNull(exception.HttpResponse);
318+
Assert.Null(exception.InnerException);
319+
}
320+
105321
private static FirebaseAuth CreateFirebaseAuth(HttpMessageHandler handler = null)
106322
{
107323
var tenantManager = new TenantManager(new TenantManager.Args

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseToken.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
// limitations under the License.
1414

1515
using System.Collections.Generic;
16-
using Newtonsoft.Json;
1716

1817
namespace FirebaseAdmin.Auth
1918
{

FirebaseAdmin/FirebaseAdmin/Auth/Multitenancy/Tenant.cs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
// limitations under the License.
1414

1515
using Google.Apis.Util;
16-
using Newtonsoft.Json;
1716

1817
namespace FirebaseAdmin.Auth.Multitenancy
1918
{
@@ -39,9 +38,9 @@ namespace FirebaseAdmin.Auth.Multitenancy
3938
/// </summary>
4039
public sealed class Tenant
4140
{
42-
private readonly Args args;
41+
private readonly TenantArgs args;
4342

44-
internal Tenant(Args args)
43+
internal Tenant(TenantArgs args)
4544
{
4645
this.args = args.ThrowIfNull(nameof(args));
4746
}
@@ -59,32 +58,17 @@ internal Tenant(Args args)
5958
/// <summary>
6059
/// Gets a value indicating whether the email sign-in provider is enabled.
6160
/// </summary>
62-
public bool PasswordSignUpAllowed => args.PasswordSignUpAllowed;
61+
public bool PasswordSignUpAllowed => args.PasswordSignUpAllowed ?? false;
6362

6463
/// <summary>
6564
/// Gets a value indicating whether the email link sign-in is enabled.
6665
/// </summary>
67-
public bool EmailLinkSignInEnabled => args.EmailLinkSignInEnabled;
66+
public bool EmailLinkSignInEnabled => args.EmailLinkSignInEnabled ?? false;
6867

6968
private string ExtractResourceId(string resourceName)
7069
{
7170
var segments = resourceName.Split('/');
7271
return segments[segments.Length - 1];
7372
}
74-
75-
internal sealed class Args
76-
{
77-
[JsonProperty("name")]
78-
internal string Name { get; set; }
79-
80-
[JsonProperty("displayName")]
81-
internal string DisplayName { get; set; }
82-
83-
[JsonProperty("allowPasswordSignup")]
84-
internal bool PasswordSignUpAllowed { get; set; }
85-
86-
[JsonProperty("enableEmailLinkSignin")]
87-
internal bool EmailLinkSignInEnabled { get; set; }
88-
}
8973
}
9074
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2020, 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 Newtonsoft.Json;
16+
17+
namespace FirebaseAdmin.Auth.Multitenancy
18+
{
19+
/// <summary>
20+
/// Arguments for creating and updating tenants.
21+
/// </summary>
22+
public sealed class TenantArgs
23+
{
24+
/// <summary>
25+
/// Gets or sets the tenant display name.
26+
/// </summary>
27+
[JsonProperty("displayName")]
28+
public string DisplayName { get; set; }
29+
30+
/// <summary>
31+
/// Gets or sets a value indicating whether the email sign-in provider is enabled.
32+
/// </summary>
33+
[JsonProperty("allowPasswordSignup")]
34+
public bool? PasswordSignUpAllowed { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets a value indicating whether the email link sign-in is enabled.
38+
/// </summary>
39+
[JsonProperty("enableEmailLinkSignin")]
40+
public bool? EmailLinkSignInEnabled { get; set; }
41+
42+
[JsonProperty("name")]
43+
internal string Name { get; set; }
44+
}
45+
}

0 commit comments

Comments
 (0)