-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathKcBearerAuthorizationHandlerTests.cs
More file actions
808 lines (689 loc) · 34.7 KB
/
KcBearerAuthorizationHandlerTests.cs
File metadata and controls
808 lines (689 loc) · 34.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using NETCore.Keycloak.Client.Authorization.Handlers;
using NETCore.Keycloak.Client.Authorization.Requirements;
using NETCore.Keycloak.Client.Authorization.Store;
using NETCore.Keycloak.Client.Exceptions;
using NETCore.Keycloak.Client.Models.Auth;
using NETCore.Keycloak.Client.Models.Common;
using NETCore.Keycloak.Client.Models.Users;
using NETCore.Keycloak.Client.Tests.Abstraction;
using NETCore.Keycloak.Client.Tests.MockData;
using Newtonsoft.Json;
namespace NETCore.Keycloak.Client.Tests.Modules.KcAuthorizationTests;
/// <summary>
/// Tests for <see cref="KcBearerAuthorizationHandler"/>, which is responsible for validating
/// user sessions and permissions for accessing protected resources.
/// </summary>
[TestClass]
[TestCategory("Final")]
public class KcBearerAuthorizationHandlerTests : KcTestingModule
{
/// <summary>
/// Represents the context of the current test.
/// Used for consistent naming and environment variable management across tests in this class.
/// </summary>
private const string TestContext = $"{nameof(KcBearerAuthorizationHandlerTests)}";
/// <summary>
/// Mock service provider for resolving dependencies such as logging services.
/// </summary>
private Mock<IServiceProvider> _mockProvider;
/// <summary>
/// Mock for <see cref="IHttpContextAccessor"/>, used to simulate HTTP context in tests.
/// </summary>
private Mock<IHttpContextAccessor> _mockHttpContextAccessor;
/// <summary>
/// Instance of <see cref="KcBearerAuthorizationHandler"/> under test.
/// </summary>
private KcTestableBearerAuthorizationHandler _handler;
/// <summary>
/// Represents the password for the test Keycloak user.
/// </summary>
private static string TestUserPassword
{
// Retrieve the test user's password from the environment variable.
get => Environment.GetEnvironmentVariable("KCUSER_PASSWORD");
// Store the test user's password in the environment variable.
set => Environment.SetEnvironmentVariable("KCUSER_PASSWORD", value);
}
/// <summary>
/// Gets or sets the Keycloak authorized user used for testing.
/// </summary>
private static KcUser TestAuthorizedUser
{
get
{
try
{
// Retrieve and deserialize the user object from the environment variable.
return JsonConvert.DeserializeObject<KcUser>(
Environment.GetEnvironmentVariable(
$"{nameof(KcBearerAuthorizationHandlerTests)}_AUTHORIZED_KCUSER") ??
string.Empty);
}
catch ( Exception e )
{
// Fail the test if deserialization fails.
Assert.Fail(e.Message);
return null;
}
}
set => Environment.SetEnvironmentVariable($"{nameof(KcBearerAuthorizationHandlerTests)}_AUTHORIZED_KCUSER",
JsonConvert.SerializeObject(value));
}
/// <summary>
/// Gets or sets the Keycloak unauthorized user used for testing.
/// </summary>
private static KcUser TestUnAuthorizedUser
{
get
{
try
{
// Retrieve and deserialize the user object from the environment variable.
return JsonConvert.DeserializeObject<KcUser>(
Environment.GetEnvironmentVariable(
$"{nameof(KcBearerAuthorizationHandlerTests)}_UNAUTHORIZED_KCUSER") ?? string.Empty
);
}
catch ( Exception e )
{
// Fail the test if deserialization fails.
Assert.Fail(e.Message);
return null;
}
}
set => Environment.SetEnvironmentVariable($"{nameof(KcBearerAuthorizationHandlerTests)}_UNAUTHORIZED_KCUSER",
JsonConvert.SerializeObject(value));
}
/// <summary>
/// Initializes the test environment for <see cref="KcBearerAuthorizationHandlerTests"/>.
/// </summary>
[TestInitialize]
public void SetUp()
{
Assert.IsNotNull(KeycloakRestClient.Auth);
Assert.IsNotNull(KeycloakRestClient.Users);
Assert.IsNotNull(KeycloakRestClient.RoleMappings);
// Initialize mock dependencies for the test environment
_mockHttpContextAccessor = new Mock<IHttpContextAccessor>();
// Set up a mock service scope and factory
var mockScope = new Mock<IServiceScope>();
var mockScopeFactory = new Mock<IServiceScopeFactory>();
_mockProvider = new Mock<IServiceProvider>();
// Configure the mock service provider to resolve the IServiceScopeFactory
_ = _mockProvider.Setup(p => p.GetService(typeof(IServiceScopeFactory)))
.Returns(mockScopeFactory.Object);
// Configure the mock IServiceScopeFactory to return a mock scope
_ = mockScopeFactory.Setup(factory => factory.CreateScope())
.Returns(mockScope.Object);
// Set up a mock logger to simulate logging functionality
var mockLogger = new Mock<ILogger<IKcRealmAdminTokenHandler>>();
_ = mockLogger.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
// Configure the mock scope to resolve services
_ = mockScope.Setup(x => x.ServiceProvider).Returns(_mockProvider.Object);
// Mock resolution of ILogger from the service provider
_ = _mockProvider.Setup(x => x.GetService(typeof(ILogger<IKcRealmAdminTokenHandler>)))
.Returns(mockLogger.Object);
// Mock resolution of IHttpContextAccessor from the service provider
_ = _mockProvider.Setup(sp => sp.GetService(typeof(IHttpContextAccessor)))
.Returns(_mockHttpContextAccessor.Object);
// Mock resolution of IKcRealmAdminTokenHandler from the service provider
_ = _mockProvider.Setup(sp => sp.GetService(typeof(IKcRealmAdminTokenHandler)))
.Returns(SetUpMockRealmAdminTokenHandler);
}
/// <summary>
/// Tests the creation of an instance of <see cref="KcTestableBearerAuthorizationHandler"/>.
/// </summary>
[TestMethod]
public void A_ShouldCreateInstance()
{
// Set up a mock HTTP context with an authorization header
SetUpMockHttpContextAccessor("Bearer test");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Assert that the handler instance is successfully created
Assert.IsNotNull(_handler);
}
/// <summary>
/// Validates the behavior of <see cref="KcTestableBearerAuthorizationHandler"/> when processing
/// an <see cref="AuthorizationHandlerContext"/>.
/// </summary>
[TestMethod]
public async Task B_ShouldValidateAuthorizationHandlerContext()
{
// Set up a mock HTTP context with an authorization header
SetUpMockHttpContextAccessor("Bearer test");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Verify that the handler instance is successfully created
Assert.IsNotNull(_handler);
// Verify that TestHandleRequirementAsync throws ArgumentNullException for null arguments
_ = await Assert.ThrowsExceptionAsync<ArgumentNullException>(async () =>
await _handler.TestHandleRequirementAsync(null, null).ConfigureAwait(false))
.ConfigureAwait(false);
}
/// <summary>
/// Validates the behavior of <see cref="KcTestableBearerAuthorizationHandler"/> when processing
/// a <see cref="KcAuthorizationRequirement"/>.
/// </summary>
[TestMethod]
public async Task C_ShouldValidateKcAuthorizationRequirement()
{
// Set up a mock HTTP context with an authorization header
SetUpMockHttpContextAccessor("Bearer test");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Verify that the handler instance is successfully created
Assert.IsNotNull(_handler);
// Create a mock AuthorizationHandlerContext with a mock JWT token and authorization requirement
var mockAuthorizationHandlerContext = CreateAuthorizationHandlerContext(
KcJwtTokenMock.CreateMockJwtToken(),
SetUpKcAuthorizationRequirement()
);
// Verify that TestHandleRequirementAsync throws ArgumentNullException when the requirement is null
_ = await Assert.ThrowsExceptionAsync<ArgumentNullException>(async () =>
await _handler.TestHandleRequirementAsync(mockAuthorizationHandlerContext, null).ConfigureAwait(false))
.ConfigureAwait(false);
}
/// <summary>
/// Verifies the creation of both authorized and unauthorized users by assigning attributes
/// and invoking the user creation logic asynchronously.
/// </summary>
[TestMethod]
public async Task D_CreateUsers()
{
// Generate a test password for the user creation process.
TestUserPassword = KcTestPasswordCreator.Create();
// Define user attributes for the authorized user.
var attributes = new Dictionary<string, object>
{
{
"account_owner", 1
},
{
"business_account_owner", 1
}
};
// Create an authorized user with specified attributes and store the result.
TestAuthorizedUser = await CreateAndGetRealmUserAsync(TestContext, TestUserPassword, attributes)
.ConfigureAwait(false);
// Create an unauthorized user without additional attributes and store the result.
TestUnAuthorizedUser = await CreateAndGetRealmUserAsync(TestContext, TestUserPassword)
.ConfigureAwait(false);
}
/// <summary>
/// Verifies that realm roles can be assigned to an authorized user.
/// </summary>
[TestMethod]
public async Task E_ShouldAssignRealmRolesToAuthorizedUser()
{
// Retrieve the realm administrator access token.
var accessToken = await GetRealmAdminTokenAsync(TestContext).ConfigureAwait(false);
// Validate that the access token is retrieved successfully.
Assert.IsNotNull(accessToken);
// Send a request to fetch the available realm roles for the unauthorized user.
var getUserAvailableRolesResponse = await KeycloakRestClient.RoleMappings
.ListUserAvailableRealmRoleMappingsAsync(
TestEnvironment.TestingRealm.Name,
accessToken.AccessToken,
TestUnAuthorizedUser.Id)
.ConfigureAwait(false);
// Validate that the response is not null and does not indicate an error.
Assert.IsNotNull(getUserAvailableRolesResponse);
Assert.IsFalse(getUserAvailableRolesResponse.IsError);
// Validate that the response contains a list of available realm roles.
Assert.IsNotNull(getUserAvailableRolesResponse.Response);
Assert.IsTrue(getUserAvailableRolesResponse.Response.Any());
// Validate monitoring metrics for the role listing request.
KcCommonAssertion.AssertResponseMonitoringMetrics(getUserAvailableRolesResponse.MonitoringMetrics,
HttpStatusCode.OK, HttpMethod.Get);
// Filter and identify the specific role to be assigned.
var roles = getUserAvailableRolesResponse.Response
.Where(role => role.Name == "kc_client_role_1")
.ToList();
// Send a request to map the identified realm role to the authorized user.
var mapRolesToUserResponse = await KeycloakRestClient.RoleMappings.AddUserRealmRoleMappingsAsync(
TestEnvironment.TestingRealm.Name,
accessToken.AccessToken,
TestAuthorizedUser.Id,
roles)
.ConfigureAwait(false);
// Validate that the role mapping response is not null and does not indicate an error.
Assert.IsNotNull(mapRolesToUserResponse);
Assert.IsFalse(mapRolesToUserResponse.IsError);
// Validate that the response content is null, as expected for this type of request.
Assert.IsNull(mapRolesToUserResponse.Response);
// Validate monitoring metrics for the role mapping request.
KcCommonAssertion.AssertResponseMonitoringMetrics(mapRolesToUserResponse.MonitoringMetrics,
HttpStatusCode.NoContent, HttpMethod.Post);
}
/// <summary>
/// Validates that access is denied and a <see cref="KcUserNotFoundException"/> is thrown
/// when the user cannot be found during authorization handling.
/// </summary>
[TestMethod]
public async Task FA_ShouldDenyAccessWithKcUserNotFoundException()
{
// Retrieve an access token using resource owner password credentials
var tokenResponse = await KeycloakRestClient.Auth.GetResourceOwnerPasswordTokenAsync(
TestEnvironment.TestingRealm.Name,
new KcClientCredentials
{
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId
},
new KcUserLogin
{
Username = TestUnAuthorizedUser.UserName,
Password = TestUserPassword
}).ConfigureAwait(false);
// Set up the authorization requirement
var requirement = SetUpKcAuthorizationRequirement();
// Create an AuthorizationHandlerContext with the retrieved access token
var context = CreateAuthorizationHandlerContext(tokenResponse.Response.AccessToken, requirement, false);
// Set up a mock HTTP context with the retrieved access token
SetUpMockHttpContextAccessor($"Bearer {tokenResponse.Response.AccessToken}");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Verify that TestHandleRequirementAsync throws KcUserNotFoundException for the given context and requirement
_ = await Assert.ThrowsExceptionAsync<KcUserNotFoundException>(async () =>
await _handler.TestHandleRequirementAsync(context, requirement).ConfigureAwait(false))
.ConfigureAwait(false);
}
/// <summary>
/// Validates that access is denied and a <see cref="KcUserNotFoundException"/> is thrown
/// when the user cannot be found during authorization handling with additional context parameters.
/// </summary>
[TestMethod]
public async Task FB_ShouldDenyAccessWithKcUserNotFoundException()
{
// Retrieve an access token using resource owner password credentials
var tokenResponse = await KeycloakRestClient.Auth.GetResourceOwnerPasswordTokenAsync(
TestEnvironment.TestingRealm.Name,
new KcClientCredentials
{
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId
},
new KcUserLogin
{
Username = TestUnAuthorizedUser.UserName,
Password = TestUserPassword
}).ConfigureAwait(false);
// Set up the authorization requirement
var requirement = SetUpKcAuthorizationRequirement();
// Create an AuthorizationHandlerContext with the retrieved access token and additional parameters
var context = CreateAuthorizationHandlerContext(
tokenResponse.Response.AccessToken,
requirement,
mapSubjectClaim: true,
removeSessionIdClaim: false,
mockSubject: Guid.NewGuid().ToString()
);
// Set up a mock HTTP context with the retrieved access token
SetUpMockHttpContextAccessor($"Bearer {tokenResponse.Response.AccessToken}");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Verify that TestHandleRequirementAsync throws KcUserNotFoundException for the given context and requirement
_ = await Assert.ThrowsExceptionAsync<KcUserNotFoundException>(async () =>
await _handler.TestHandleRequirementAsync(context, requirement).ConfigureAwait(false))
.ConfigureAwait(false);
}
/// <summary>
/// Validates that access is denied and a <see cref="KcSessionClosedException"/> is thrown
/// when the user's session is closed during the authorization handling process.
/// </summary>
[TestMethod]
public async Task GA_ShouldDenyAccessWithKcSessionClosedException()
{
// Retrieve an access token using resource owner password credentials
var tokenResponse = await KeycloakRestClient.Auth.GetResourceOwnerPasswordTokenAsync(
TestEnvironment.TestingRealm.Name,
new KcClientCredentials
{
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId
},
new KcUserLogin
{
Username = TestUnAuthorizedUser.UserName,
Password = TestUserPassword
}).ConfigureAwait(false);
// Set up the authorization requirement
var requirement = SetUpKcAuthorizationRequirement();
// Create an AuthorizationHandlerContext with the retrieved access token and specific parameters
var context = CreateAuthorizationHandlerContext(
tokenResponse.Response.AccessToken,
requirement,
mapSubjectClaim: true,
removeSessionIdClaim: true
);
// Set up a mock HTTP context with the retrieved access token
SetUpMockHttpContextAccessor($"Bearer {tokenResponse.Response.AccessToken}");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Verify that TestHandleRequirementAsync throws KcSessionClosedException for the given context and requirement
_ = await Assert.ThrowsExceptionAsync<KcSessionClosedException>(async () =>
await _handler.TestHandleRequirementAsync(context, requirement).ConfigureAwait(false))
.ConfigureAwait(false);
}
/// <summary>
/// Validates that access is denied and a <see cref="KcSessionClosedException"/> is thrown
/// when the user's session is closed during authorization handling with additional mock data for subject and session ID.
/// </summary>
[TestMethod]
public async Task GB_ShouldDenyAccessWithKcSessionClosedException()
{
// Retrieve an access token using resource owner password credentials
var tokenResponse = await KeycloakRestClient.Auth.GetResourceOwnerPasswordTokenAsync(
TestEnvironment.TestingRealm.Name,
new KcClientCredentials
{
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId
},
new KcUserLogin
{
Username = TestUnAuthorizedUser.UserName,
Password = TestUserPassword
}).ConfigureAwait(false);
// Set up the authorization requirement
var requirement = SetUpKcAuthorizationRequirement();
// Create an AuthorizationHandlerContext with the retrieved access token, additional parameters, and mock data
var context = CreateAuthorizationHandlerContext(
tokenResponse.Response.AccessToken,
requirement,
mapSubjectClaim: true,
removeSessionIdClaim: false,
mockSubject: null,
mockSessionId: Guid.NewGuid().ToString()
);
// Set up a mock HTTP context with the retrieved access token
SetUpMockHttpContextAccessor($"Bearer {tokenResponse.Response.AccessToken}");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Verify that TestHandleRequirementAsync throws KcSessionClosedException for the given context and requirement
_ = await Assert.ThrowsExceptionAsync<KcSessionClosedException>(async () =>
await _handler.TestHandleRequirementAsync(context, requirement).ConfigureAwait(false))
.ConfigureAwait(false);
}
/// <summary>
/// Validates that access is denied for an unauthorized user during the authorization handling process.
/// </summary>
[TestMethod]
public async Task H_ShouldDenyAccessForUnAuthorizedUser()
{
// Retrieve an access token using resource owner password credentials for an unauthorized user
var tokenResponse = await KeycloakRestClient.Auth.GetResourceOwnerPasswordTokenAsync(
TestEnvironment.TestingRealm.Name,
new KcClientCredentials
{
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId
},
new KcUserLogin
{
Username = TestUnAuthorizedUser.UserName,
Password = TestUserPassword
}).ConfigureAwait(false);
// Set up the authorization requirement
var requirement = SetUpKcAuthorizationRequirement();
// Create an AuthorizationHandlerContext with the retrieved access token
var context = CreateAuthorizationHandlerContext(
tokenResponse.Response.AccessToken,
requirement
);
// Set up a mock HTTP context with the retrieved access token
SetUpMockHttpContextAccessor($"Bearer {tokenResponse.Response.AccessToken}");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Process the authorization context
await _handler.TestHandleRequirementAsync(context, requirement).ConfigureAwait(false);
// Verify that the authorization context has not succeeded
Assert.IsFalse(context.HasSucceeded);
}
/// <summary>
/// Validates that access is allowed for an authorized user during the authorization handling process.
/// </summary>
[TestMethod]
public async Task I_ShouldAllowAccessForAuthorizedUser()
{
// Retrieve an access token using resource owner password credentials for an authorized user
var tokenResponse = await KeycloakRestClient.Auth.GetResourceOwnerPasswordTokenAsync(
TestEnvironment.TestingRealm.Name,
new KcClientCredentials
{
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId
},
new KcUserLogin
{
Username = TestAuthorizedUser.UserName,
Password = TestUserPassword
}).ConfigureAwait(false);
// Set up the authorization requirement
var requirement = SetUpKcAuthorizationRequirement();
// Create an AuthorizationHandlerContext with the retrieved access token
var context = CreateAuthorizationHandlerContext(
tokenResponse.Response.AccessToken,
requirement
);
// Set up a mock HTTP context with the retrieved access token
SetUpMockHttpContextAccessor($"Bearer {tokenResponse.Response.AccessToken}");
// Initialize the KcAuthorizationHandler using the mocked service provider
_handler = new KcTestableBearerAuthorizationHandler(_mockProvider.Object);
// Process the authorization context
await _handler.TestHandleRequirementAsync(context, requirement).ConfigureAwait(false);
// Verify that the authorization context has succeeded
Assert.IsTrue(context.HasSucceeded);
}
/// <summary>
/// Verifies that test users are successfully deleted from the Keycloak realm.
/// </summary>
[TestMethod]
public async Task Z_ShouldDeleteTestUsers()
{
// Ensure that both test user instances are not null.
Assert.IsNotNull(TestAuthorizedUser, "TestAuthorizedUser must not be null.");
Assert.IsNotNull(TestUnAuthorizedUser, "TestUnAuthorizedUser must not be null.");
// Create a list of users to be deleted.
var usersList = new List<KcUser>
{
TestAuthorizedUser,
TestUnAuthorizedUser
};
// Retrieve an access token for the realm admin to perform the user deletion.
var accessToken = await GetRealmAdminTokenAsync(TestContext).ConfigureAwait(false);
Assert.IsNotNull(accessToken, "Access token for realm admin must not be null.");
// Iterate over the user list and delete each user.
foreach ( var user in usersList )
{
// Execute the user deletion operation using the Keycloak REST client.
var deleteUserResponse = await KeycloakRestClient.Users
.DeleteAsync(TestEnvironment.TestingRealm.Name, accessToken.AccessToken, user.Id)
.ConfigureAwait(false);
// Validate the deletion response.
Assert.IsNotNull(deleteUserResponse, $"Delete response for user {user.Id} must not be null.");
Assert.IsFalse(deleteUserResponse.IsError,
$"Delete request for user {user.Id} should not return an error.");
// Validate the monitoring metrics for the deletion request.
KcCommonAssertion.AssertResponseMonitoringMetrics(deleteUserResponse.MonitoringMetrics,
HttpStatusCode.NoContent, HttpMethod.Delete);
}
}
/// <summary>
/// Configures the mock <see cref="IHttpContextAccessor"/> with a specified authorization header.
/// </summary>
private void SetUpMockHttpContextAccessor(string authorization)
{
// Create a mock HTTP context
var mockHttpContext = new DefaultHttpContext();
// Assign the specified authorization header to the mock HTTP request
mockHttpContext.Request.Headers.Authorization = authorization;
// Configure the mock IHttpContextAccessor to return the mock HTTP context
_ = _mockHttpContextAccessor.Setup(a => a.HttpContext).Returns(mockHttpContext);
}
/// <summary>
/// Creates an <see cref="AuthorizationHandlerContext"/> instance for testing purposes using a JWT token.
/// </summary>
/// <param name="jwtToken">The JWT token containing the claims to be used in the authorization context.</param>
/// <param name="requirement">The <see cref="KcAuthorizationRequirement"/> that defines the resource and scope to be authorized.</param>
/// <param name="mapSubjectClaim">
/// Indicates whether the subject claim ("sub") from the JWT should be mapped to a <see cref="ClaimTypes.NameIdentifier"/> claim.
/// Defaults to <c>true</c>.
/// </param>
/// <param name="removeSessionIdClaim">
/// Indicates whether the session ID claim ("sid") should be removed from the claims. Defaults to <c>false</c>.
/// </param>
/// <param name="mockSubject">
/// An optional mocked subject value to override the subject claim in the JWT. If specified, this value will be added as
/// a <see cref="ClaimTypes.NameIdentifier"/> claim.
/// </param>
/// <param name="mockSessionId">
/// An optional mocked session ID value to override the session ID claim in the JWT. If specified, this value will be
/// added as a "sid" claim if <paramref name="removeSessionIdClaim"/> is <c>false</c>.
/// </param>
/// <returns>
/// An <see cref="AuthorizationHandlerContext"/> instance initialized with the specified JWT token, requirement, and mock values.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="jwtToken"/> is null, empty, or consists only of whitespace.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the provided <paramref name="jwtToken"/> is not a valid JWT.
/// </exception>
private static AuthorizationHandlerContext CreateAuthorizationHandlerContext(string jwtToken,
KcAuthorizationRequirement requirement, bool mapSubjectClaim = true, bool removeSessionIdClaim = false,
string mockSubject = null, string mockSessionId = null)
{
// Validate that the JWT token is provided and not null or empty
if ( string.IsNullOrWhiteSpace(jwtToken) )
{
throw new ArgumentNullException(nameof(jwtToken), "JWT token is required.");
}
var tokenHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwt;
try
{
// Parse the JWT token
jwt = tokenHandler.ReadJwtToken(jwtToken);
}
catch ( Exception ex )
{
throw new ArgumentException("The provided JWT token is invalid.", nameof(jwtToken), ex);
}
// Extract claims from the parsed JWT token
var userClaims = jwt.Claims.ToList();
var subject = userClaims.FirstOrDefault(c => c.Type == "sub")?.Value;
if ( mapSubjectClaim && string.IsNullOrWhiteSpace(mockSubject) && !string.IsNullOrWhiteSpace(subject) )
{
userClaims.Add(new Claim(ClaimTypes.NameIdentifier, subject));
}
if ( mapSubjectClaim && !string.IsNullOrWhiteSpace(mockSubject) )
{
userClaims.Add(new Claim(ClaimTypes.NameIdentifier, mockSubject));
}
switch ( removeSessionIdClaim )
{
case true:
userClaims = userClaims.Where(c => c.Type != "sid").ToList();
break;
case false when !string.IsNullOrWhiteSpace(mockSessionId):
userClaims = userClaims.Where(c => c.Type != "sid").ToList();
userClaims.Add(new Claim("sid", mockSessionId));
break;
}
// Create a ClaimsPrincipal from the JWT claims
var user = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "Bearer"));
// Return a new AuthorizationHandlerContext initialized with the specified requirement and user
return new AuthorizationHandlerContext([requirement], user, null);
}
/// <summary>
/// Sets up and returns a <see cref="KcAuthorizationRequirement"/> instance for testing purposes.
/// </summary>
/// <remarks>
/// This method configures a mocked <see cref="KcProtectedResourceStore"/> to simulate the protected resource data
/// and retrieves resource policy information from the test environment.
/// </remarks>
/// <returns>
/// A configured instance of <see cref="KcAuthorizationRequirement"/>.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown if no permissions or scopes are defined in the test environment, or if the resource access data is invalid.
/// </exception>
private KcAuthorizationRequirement SetUpKcAuthorizationRequirement()
{
// Mock the protected resource store and set up its behavior.
var mockProtectedResourceStore = new Mock<KcProtectedResourceStore>();
_ = mockProtectedResourceStore.Setup(store => store.GetRealmProtectedResources()).Returns(() =>
new List<KcRealmProtectedResources>
{
new()
{
Realm = TestEnvironment.TestingRealm.Name,
ProtectedResourceName = TestEnvironment.TestingRealm.PrivateClient.ClientId
}
});
// Retrieve the first resource policy defined in the test environment.
var resourcePolicy = TestEnvironment.TestingRealm.User.Permissions.FirstOrDefault()?.Scopes.FirstOrDefault();
// Ensure a resource policy is available.
Assert.IsNotNull(resourcePolicy);
// Split the resource policy into resource name and scope.
var resourceAccessData = resourcePolicy.Split('#');
// Validate that the resource access data contains exactly two parts.
Assert.IsTrue(resourceAccessData.Length == 2,
"Resource access data must contain both resource name and scope.");
// Return a new authorization requirement using the mocked resource store and resource access data.
return new KcAuthorizationRequirement(
mockProtectedResourceStore.Object,
resourceAccessData[0], // Resource name
resourceAccessData[1] // Resource scope
);
}
/// <summary>
/// Configures a mock <see cref="KcRealmAdminTokenHandler"/> for use in tests.
/// </summary>
private KcRealmAdminTokenHandler SetUpMockRealmAdminTokenHandler()
{
// Create mocks for required dependencies
var mockScope = new Mock<IServiceScope>();
var mockScopeFactory = new Mock<IServiceScopeFactory>();
var mockConfigStore = new Mock<KcRealmAdminConfigurationStore>();
var mockProvider = new Mock<IServiceProvider>();
// Configure the mock service provider to resolve the IServiceScopeFactory
_ = mockProvider.Setup(p => p.GetService(typeof(IServiceScopeFactory)))
.Returns(mockScopeFactory.Object);
// Configure the mock IServiceScopeFactory to return a mock scope
_ = mockScopeFactory.Setup(factory => factory.CreateScope())
.Returns(mockScope.Object);
// Set up a mock logger to simulate logging functionality
var mockLogger = new Mock<ILogger<IKcRealmAdminTokenHandler>>();
_ = mockLogger.Setup(logger => logger.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
// Configure the mock scope to resolve services
_ = mockScope.Setup(x => x.ServiceProvider).Returns(mockProvider.Object);
// Mock resolution of ILogger from the service provider
_ = mockProvider.Setup(x => x.GetService(typeof(ILogger<IKcRealmAdminTokenHandler>)))
.Returns(mockLogger.Object);
// Define a test-specific configuration for the Keycloak realm
var testConfig = new KcRealmAdminConfiguration
{
KeycloakBaseUrl = TestEnvironment.BaseUrl,
Realm = TestEnvironment.TestingRealm.Name,
ClientId = TestEnvironment.TestingRealm.PublicClient.ClientId,
RealmAdminCredentials = new KcUserLogin
{
Username = TestEnvironment.TestingRealm.User.Username,
Password = TestEnvironment.TestingRealm.User.Password
}
};
// Configure the mock configuration store to return the test configuration
_ = mockConfigStore.Setup(c => c.GetRealmsAdminConfiguration())
.Returns(new List<KcRealmAdminConfiguration>
{
testConfig
});
// Return a new instance of KcRealmAdminTokenHandler with the mocked dependencies
return new KcRealmAdminTokenHandler(mockConfigStore.Object, mockProvider.Object);
}
}