Skip to content

Commit 88bcb1b

Browse files
authored
Merge pull request #17 from tekrama/feature-custom-claims
Implemented CustomUserClaims
2 parents 3408e9e + 2f8843e commit 88bcb1b

File tree

6 files changed

+488
-5
lines changed

6 files changed

+488
-5
lines changed

FirebaseAdmin/FirebaseAdmin.IntegrationTests/FirebaseAuthTest.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,34 @@ public async Task CreateCustomTokenWithoutServiceAccount()
9393
}
9494
}
9595

96+
[Fact]
97+
public async Task SetCustomUserClaims()
98+
{
99+
var customClaims = new Dictionary<string, object>()
100+
{
101+
{"admin", true}
102+
};
103+
104+
await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync("testuser", customClaims);
105+
}
106+
107+
[Fact]
108+
public async Task SetCustomUserClaimsWithEmptyClaims()
109+
{
110+
var customClaims = new Dictionary<string, object>();
111+
112+
await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync("testuser", customClaims);
113+
}
114+
115+
[Fact]
116+
public async Task SetCustomUserClaimsWithWrongUid()
117+
{
118+
var customClaims = new Dictionary<string, object>();
119+
120+
await Assert.ThrowsAsync<FirebaseException>(
121+
async () => await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync("mock-uid", customClaims));
122+
}
123+
96124
private static async Task<string> SignInWithCustomTokenAsync(string customToken)
97125
{
98126
var rb = new Google.Apis.Requests.RequestBuilder()

FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseAuthTest.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
using System.Threading;
2222
using System.Threading.Tasks;
2323
using Xunit;
24-
using FirebaseAdmin.Auth;
25-
using Google.Apis.Auth;
2624
using Google.Apis.Auth.OAuth2;
2725

2826
[assembly: CollectionBehavior(DisableTestParallelization = true)]
@@ -173,6 +171,18 @@ private static void VerifyCustomToken(string token, string uid, Dictionary<strin
173171
Assert.True(verified);
174172
}
175173

174+
[Fact]
175+
public async Task SetCustomUserClaimsNoProjectId()
176+
{
177+
FirebaseApp.Create(new AppOptions() { Credential = mockCredential });
178+
var customClaims = new Dictionary<string, object>()
179+
{
180+
{"admin", true}
181+
};
182+
await Assert.ThrowsAsync<ArgumentException>(
183+
async () => await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync("user1", customClaims));
184+
}
185+
176186
public void Dispose()
177187
{
178188
FirebaseApp.DeleteAll();
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright 2019, 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 System.Collections.Generic;
17+
using Xunit;
18+
using Google.Apis.Auth.OAuth2;
19+
using FirebaseAdmin.Tests;
20+
using System.Threading.Tasks;
21+
using System.Net;
22+
23+
namespace FirebaseAdmin.Auth.Tests
24+
{
25+
public class FirebaseUserManagerTest
26+
{
27+
private static readonly GoogleCredential mockCredential =
28+
GoogleCredential.FromAccessToken("test-token");
29+
private const string mockProjectId = "project1";
30+
31+
[Fact]
32+
public void InvalidUidForUserRecord()
33+
{
34+
Assert.Throws<ArgumentException>(() => new UserRecord(null));
35+
Assert.Throws<ArgumentException>(() => new UserRecord(""));
36+
Assert.Throws<ArgumentException>(() => new UserRecord(new string('a', 129)));
37+
}
38+
39+
[Fact]
40+
public void ReservedClaims()
41+
{
42+
foreach (var key in FirebaseTokenFactory.ReservedClaims)
43+
{
44+
var customClaims = new Dictionary<string, object>(){
45+
{key, "value"},
46+
};
47+
Assert.Throws<ArgumentException>(() => new UserRecord("user1") { CustomClaims = customClaims});
48+
}
49+
}
50+
51+
[Fact]
52+
public void EmptyClaims()
53+
{
54+
var emptyClaims = new Dictionary<string, object>(){
55+
{"", "value"},
56+
};
57+
Assert.Throws<ArgumentException>(() => new UserRecord("user1") { CustomClaims = emptyClaims });
58+
}
59+
60+
[Fact]
61+
public void TooLargeClaimsPayload()
62+
{
63+
var customClaims = new Dictionary<string, object>()
64+
{
65+
{ "testClaim", new string('a', 1001) },
66+
};
67+
68+
Assert.Throws<ArgumentException>(() => new UserRecord("user1") { CustomClaims = customClaims });
69+
}
70+
71+
[Fact]
72+
public async Task UpdateUser()
73+
{
74+
var handler = new MockMessageHandler()
75+
{
76+
Response = new UserRecord("user1")
77+
};
78+
var factory = new MockHttpClientFactory(handler);
79+
var userManager = new FirebaseUserManager(
80+
new FirebaseUserManagerArgs
81+
{
82+
Credential = mockCredential,
83+
ProjectId = mockProjectId,
84+
ClientFactory = factory
85+
});
86+
var customClaims = new Dictionary<string, object>(){
87+
{"admin", true},
88+
};
89+
90+
await userManager.UpdateUserAsync(new UserRecord("user1") { CustomClaims = customClaims });
91+
}
92+
93+
[Fact]
94+
public async Task UpdateUserIncorrectResponseObject()
95+
{
96+
var handler = new MockMessageHandler()
97+
{
98+
Response = new object()
99+
};
100+
var factory = new MockHttpClientFactory(handler);
101+
var userManager = new FirebaseUserManager(
102+
new FirebaseUserManagerArgs
103+
{
104+
Credential = mockCredential,
105+
ProjectId = mockProjectId,
106+
ClientFactory = factory
107+
});
108+
var customClaims = new Dictionary<string, object>(){
109+
{"admin", true},
110+
};
111+
112+
await Assert.ThrowsAsync<FirebaseException>(
113+
async () => await userManager.UpdateUserAsync(new UserRecord("user1") { CustomClaims = customClaims }));
114+
}
115+
116+
[Fact]
117+
public async Task UpdateUserIncorrectResponseUid()
118+
{
119+
var handler = new MockMessageHandler()
120+
{
121+
Response = new UserRecord("testuser")
122+
};
123+
var factory = new MockHttpClientFactory(handler);
124+
var userManager = new FirebaseUserManager(
125+
new FirebaseUserManagerArgs
126+
{
127+
Credential = mockCredential,
128+
ProjectId = mockProjectId,
129+
ClientFactory = factory
130+
});
131+
var customClaims = new Dictionary<string, object>(){
132+
{"admin", true},
133+
};
134+
135+
await Assert.ThrowsAsync<FirebaseException>(
136+
async () => await userManager.UpdateUserAsync(new UserRecord("user1") { CustomClaims = customClaims }));
137+
}
138+
139+
[Fact]
140+
public async Task UpdateUserHttpError()
141+
{
142+
var handler = new MockMessageHandler()
143+
{
144+
StatusCode = HttpStatusCode.InternalServerError
145+
};
146+
var factory = new MockHttpClientFactory(handler);
147+
var userManager = new FirebaseUserManager(
148+
new FirebaseUserManagerArgs
149+
{
150+
Credential = mockCredential,
151+
ProjectId = mockProjectId,
152+
ClientFactory = factory
153+
});
154+
var customClaims = new Dictionary<string, object>(){
155+
{"admin", true},
156+
};
157+
158+
await Assert.ThrowsAsync<FirebaseException>(
159+
async () => await userManager.UpdateUserAsync(new UserRecord("user1") { CustomClaims = customClaims }));
160+
}
161+
}
162+
}

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ public sealed class FirebaseAuth: IFirebaseService
2929
private bool _deleted;
3030
private readonly Lazy<FirebaseTokenFactory> _tokenFactory;
3131
private readonly Lazy<FirebaseTokenVerifier> _idTokenVerifier;
32+
private readonly Lazy<FirebaseUserManager> _userManager;
3233
private readonly Object _lock = new Object();
3334

3435
private FirebaseAuth(FirebaseApp app)
3536
{
3637
_app = app;
37-
_tokenFactory = new Lazy<FirebaseTokenFactory>(() =>
38+
_tokenFactory = new Lazy<FirebaseTokenFactory>(() =>
3839
FirebaseTokenFactory.Create(_app), true);
39-
_idTokenVerifier = new Lazy<FirebaseTokenVerifier>(() =>
40+
_idTokenVerifier = new Lazy<FirebaseTokenVerifier>(() =>
4041
FirebaseTokenVerifier.CreateIDTokenVerifier(_app), true);
42+
_userManager = new Lazy<FirebaseUserManager>(() =>
43+
FirebaseUserManager.Create(_app));
4144
}
4245

4346
/// <summary>
@@ -236,6 +239,38 @@ public async Task<FirebaseToken> VerifyIdTokenAsync(
236239
.ConfigureAwait(false);
237240
}
238241

242+
/// <summary>
243+
/// Sets the specified custom claims on an existing user account. A null claims value
244+
/// removes any claims currently set on the user account. The claims should serialize into
245+
/// a valid JSON string. The serialized claims must not be larger than 1000 characters.
246+
/// </summary>
247+
/// <exception cref="ArgumentException">If <paramref name="uid"/> is null, empty or longer
248+
/// than 128 characters. Or, if the serialized <paramref name="claims"/> is larger than 1000
249+
/// characters.</exception>
250+
/// <param name="uid">The user ID string for the custom claims will be set. Must not be null
251+
/// or longer than 128 characters.
252+
/// </param>
253+
/// <param name="claims">The claims to be stored on the user account, and made
254+
/// available to Firebase security rules. These must be serializable to JSON, and after
255+
/// serialization it should not be larger than 1000 characters.</param>
256+
public async Task SetCustomUserClaimsAsync(string uid, IReadOnlyDictionary<string, object> claims)
257+
{
258+
lock (_lock)
259+
{
260+
if (_deleted)
261+
{
262+
throw new InvalidOperationException("Cannot invoke after deleting the app.");
263+
}
264+
}
265+
266+
var user = new UserRecord(uid)
267+
{
268+
CustomClaims = claims
269+
};
270+
271+
await _userManager.Value.UpdateUserAsync(user);
272+
}
273+
239274
void IFirebaseService.Delete()
240275
{
241276
lock (_lock)
@@ -278,7 +313,7 @@ public static FirebaseAuth GetAuth(FirebaseApp app)
278313
{
279314
throw new ArgumentNullException("App argument must not be null.");
280315
}
281-
return app.GetOrInit<FirebaseAuth>(typeof(FirebaseAuth).Name, () =>
316+
return app.GetOrInit<FirebaseAuth>(typeof(FirebaseAuth).Name, () =>
282317
{
283318
return new FirebaseAuth(app);
284319
});

0 commit comments

Comments
 (0)