Skip to content

Commit 8893610

Browse files
authored
Merge pull request #3 from Project-MONAI/vchang/logging
Update config structure & logging
2 parents 74a98b1 + d6a5d06 commit 8893610

23 files changed

+423
-119
lines changed

src/Authentication/Configurations/AuthenticationOptions.cs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ namespace Monai.Deploy.Security.Authentication.Configurations
2424
{
2525
public class AuthenticationOptions
2626
{
27-
[ConfigurationKeyName("BypassAuthentication")]
27+
[ConfigurationKeyName("bypassAuthentication")]
2828
public bool? BypassAuthentication { get; set; }
2929

30-
[ConfigurationKeyName("OpenId")]
30+
[ConfigurationKeyName("openId")]
3131
public OpenIdOptions? OpenId { get; set; }
3232

3333
public bool BypassAuth(ILogger logger)
@@ -42,26 +42,58 @@ public bool BypassAuth(ILogger logger)
4242

4343
if (OpenId is null)
4444
{
45-
throw new InvalidOperationException("OpenId configuration is invalid.");
45+
throw new InvalidOperationException("openId configuration is invalid.");
4646
}
47-
if (OpenId.Claims is null || OpenId.Claims.RequiredUserClaims!.IsNullOrEmpty() || OpenId.Claims.RequiredAdminClaims!.IsNullOrEmpty())
47+
if (string.IsNullOrWhiteSpace(OpenId.ClientId))
4848
{
49-
throw new InvalidOperationException("No claims defined for OpenId.");
49+
throw new InvalidOperationException("No clientId defined for OpenId.");
5050
}
51-
if (string.IsNullOrWhiteSpace(OpenId.ClientId))
51+
if (string.IsNullOrWhiteSpace(OpenId.RealmKey))
5252
{
53-
throw new InvalidOperationException("No ClientId defined for OpenId.");
53+
throw new InvalidOperationException("No realmKey defined for OpenId.");
5454
}
55-
if (string.IsNullOrWhiteSpace(OpenId.ServerRealmKey))
55+
if (string.IsNullOrWhiteSpace(OpenId.Realm))
5656
{
57-
throw new InvalidOperationException("No ServerRealmKey defined for OpenId.");
57+
throw new InvalidOperationException("No realm defined for OpenId.");
5858
}
59-
if (string.IsNullOrWhiteSpace(OpenId.ServerRealm))
59+
if (OpenId.Claims is null || OpenId.Claims.UserClaims!.IsNullOrEmpty() || OpenId.Claims.AdminClaims!.IsNullOrEmpty())
6060
{
61-
throw new InvalidOperationException("No ServerRealm defined for OpenId.");
61+
throw new InvalidOperationException("No claimMappings defined for OpenId.");
6262
}
6363

64+
ValidateClaims(OpenId.Claims.UserClaims!, true);
65+
ValidateClaims(OpenId.Claims.AdminClaims!, false);
66+
6467
return false;
6568
}
69+
70+
private void ValidateClaims(List<ClaimMapping> claims, bool validateEndpoints)
71+
{
72+
foreach (var claim in claims)
73+
{
74+
if (string.IsNullOrWhiteSpace(claim.ClaimType))
75+
{
76+
throw new InvalidOperationException("Value for claimType is invalid.");
77+
}
78+
79+
if (claim.ClaimValues.IsNullOrEmpty())
80+
{
81+
throw new InvalidOperationException("Value for claimType is invalid.");
82+
}
83+
84+
foreach (var claimValue in claim.ClaimValues)
85+
{
86+
if (string.IsNullOrWhiteSpace(claimValue))
87+
{
88+
throw new InvalidOperationException($"Invalid claimValue for {claim.ClaimType}.");
89+
}
90+
}
91+
92+
if (validateEndpoints && claim.Endpoints.IsNullOrEmpty())
93+
{
94+
throw new InvalidOperationException("Value for claimType is invalid.");
95+
}
96+
}
97+
}
6698
}
6799
}

src/Authentication/Configurations/Claims.cs renamed to src/Authentication/Configurations/ClaimMappings.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,24 @@
1818

1919
namespace Monai.Deploy.Security.Authentication.Configurations
2020
{
21-
public class Claims
21+
public class ClaimMappings
2222
{
23-
[ConfigurationKeyName("RequiredUserClaims")]
24-
public List<Claim>? RequiredUserClaims { get; set; }
23+
[ConfigurationKeyName("userClaims")]
24+
public List<ClaimMapping>? UserClaims { get; set; }
2525

26-
[ConfigurationKeyName("RequiredAdminClaims")]
27-
public List<Claim>? RequiredAdminClaims { get; set; }
26+
[ConfigurationKeyName("adminClaims")]
27+
public List<ClaimMapping>? AdminClaims { get; set; }
2828
}
2929

30-
public class Claim
30+
public class ClaimMapping
3131
{
32-
[ConfigurationKeyName("user_roles")]
33-
public string? UserRoles { get; set; }
32+
[ConfigurationKeyName("claimType")]
33+
public string ClaimType { get; set; } = string.Empty;
34+
35+
[ConfigurationKeyName("claimValues")]
36+
public List<string> ClaimValues { get; set; } = new List<string>();
3437

3538
[ConfigurationKeyName("endpoints")]
36-
public List<string>? Endpoints { get; set; }
39+
public List<string>? Endpoints { get; set; } = default;
3740
}
3841
}

src/Authentication/Configurations/OpenIdOptions.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,32 @@
1414
* limitations under the License.
1515
*/
1616

17+
using System.Security.Claims;
1718
using Microsoft.Extensions.Configuration;
1819

1920
namespace Monai.Deploy.Security.Authentication.Configurations
2021
{
2122
public class OpenIdOptions
2223
{
23-
[ConfigurationKeyName("ServerRealm")]
24-
public string? ServerRealm { get; set; }
24+
[ConfigurationKeyName("realm")]
25+
public string? Realm { get; set; }
2526

26-
[ConfigurationKeyName("ServerRealmKey")]
27-
public string? ServerRealmKey { get; set; }
27+
[ConfigurationKeyName("realmKey")]
28+
public string? RealmKey { get; set; }
2829

29-
[ConfigurationKeyName("ClientId")]
30+
[ConfigurationKeyName("clientId")]
3031
public string? ClientId { get; set; }
3132

32-
[ConfigurationKeyName("Claims")]
33-
public Claims? Claims { get; set; }
33+
[ConfigurationKeyName("claimMappings")]
34+
public ClaimMappings? Claims { get; set; }
3435

35-
[ConfigurationKeyName("Audiences")]
36+
[ConfigurationKeyName("audiences")]
3637
public IList<string>? Audiences { get; set; }
38+
39+
[ConfigurationKeyName("roleClaimType")]
40+
public string RoleClaimType { get; set; } = ClaimTypes.Role;
41+
42+
[ConfigurationKeyName("clearDefaultRoleMappigns")]
43+
public bool ClearDefaultRoleMappigns { get; set; } = true;
3744
}
3845
}

src/Authentication/Extensions/AuthKeys.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ public static class AuthKeys
2525

2626
// Configuration Keys
2727
public const string OpenId = "OpenId";
28-
public const string UserRoles = "user_roles";
2928
}
3029
}

src/Authentication/Extensions/HttpContextExtension.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
using Ardalis.GuardClauses;
1818
using Microsoft.AspNetCore.Http;
19+
using Microsoft.Extensions.Logging;
20+
using Monai.Deploy.Security.Authentication.Middleware;
21+
using Monai.Deploy.WorkflowManager.Logging;
1922

2023
namespace Monai.Deploy.Security.Authentication.Extensions
2124
{
@@ -24,32 +27,46 @@ public static class HttpContextExtension
2427
/// <summary>
2528
/// Gets endpoints specified in config for roles in claims.
2629
/// </summary>
27-
/// <param name="httpcontext"></param>
30+
/// <param name="httpContext"></param>
2831
/// <param name="requiredClaims"></param>
2932
/// <returns></returns>
30-
public static List<string> GetValidEndpoints(this HttpContext httpcontext, List<Configurations.Claim> adminClaims, List<Configurations.Claim> userClaims)
33+
public static List<string> GetValidEndpoints(this HttpContext httpContext, ILogger<EndpointAuthorizationMiddleware> logger, List<Configurations.ClaimMapping> adminClaims, List<Configurations.ClaimMapping> userClaims)
3134
{
3235
Guard.Against.Null(adminClaims);
3336
Guard.Against.Null(userClaims);
3437

38+
foreach (var claim in httpContext.User.Claims)
39+
{
40+
logger.UserClaimFound(claim.Type, claim.Value);
41+
42+
}
43+
3544
foreach (var claim in adminClaims!)
3645
{
37-
if (httpcontext.User.HasClaim(AuthKeys.UserRoles, claim.UserRoles!))
46+
foreach (var role in claim.ClaimValues)
3847
{
39-
return new List<string> { "all" };
48+
logger.CheckingUserClaim(claim.ClaimType, role);
49+
if (httpContext.User.HasClaim(claim.ClaimType, role))
50+
{
51+
return new List<string> { "*" };
52+
}
4053
}
4154
}
4255

56+
var endpoints = new List<string>();
4357
foreach (var claim in userClaims!)
4458
{
45-
if (httpcontext.User.HasClaim(AuthKeys.UserRoles, claim.UserRoles!))
59+
foreach (var role in claim.ClaimValues)
4660
{
47-
return claim.Endpoints!;
61+
logger.CheckingUserClaim(claim.ClaimType, role);
62+
if (httpContext.User.HasClaim(claim.ClaimType, role))
63+
{
64+
endpoints.AddRange(claim.Endpoints!);
65+
}
4866
}
49-
5067
}
5168

52-
return new List<string>();
69+
return endpoints.Distinct().ToList();
5370
}
5471
}
5572
}

src/Authentication/Extensions/MonaiAuthenticationExtensions.cs

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17+
using System.IdentityModel.Tokens.Jwt;
1718
using System.Text;
1819
using Microsoft.AspNetCore.Authentication;
1920
using Microsoft.AspNetCore.Authentication.JwtBearer;
20-
using Microsoft.AspNetCore.Authorization;
2121
using Microsoft.Extensions.Configuration;
2222
using Microsoft.Extensions.DependencyInjection;
2323
using Microsoft.Extensions.Logging;
@@ -49,53 +49,39 @@ public static IServiceCollection AddMonaiAuthentication(
4949
return services;
5050
}
5151

52-
services.AddAuthentication(options =>
52+
if (configurations.Value.OpenId?.ClearDefaultRoleMappigns ?? false)
5353
{
54-
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
55-
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
56-
})
57-
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, AuthKeys.OpenId, options =>
58-
{
59-
options.Authority = configurations.Value.OpenId!.ServerRealm;
60-
options.Audience = configurations.Value.OpenId!.ServerRealm;
61-
options.RequireHttpsMetadata = false;
54+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
55+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("roles");
56+
}
6257

63-
options.TokenValidationParameters = new TokenValidationParameters
58+
services
59+
.AddAuthentication(options =>
6460
{
65-
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurations.Value.OpenId!.ServerRealmKey!)),
66-
ValidIssuer = configurations.Value.OpenId.ServerRealm,
67-
ValidAudiences = configurations.Value.OpenId.Audiences,
68-
ValidateIssuerSigningKey = true,
69-
ValidateIssuer = true,
70-
ValidateLifetime = true,
71-
ValidateAudience = true,
72-
};
73-
});
74-
75-
services.AddAuthorization(options =>
76-
{
77-
if (configurations.Value.OpenId!.Claims!.RequiredAdminClaims!.Any())
61+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
62+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
63+
})
64+
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, AuthKeys.OpenId, options =>
7865
{
79-
AddPolicy(options, configurations.Value.OpenId!.Claims!.RequiredAdminClaims!, AuthKeys.AdminPolicyName);
80-
}
66+
options.Authority = configurations.Value.OpenId!.Realm;
67+
options.Audience = configurations.Value.OpenId!.Realm;
68+
options.RequireHttpsMetadata = false;
8169

82-
if (configurations.Value.OpenId!.Claims!.RequiredUserClaims!.Any())
83-
{
84-
AddPolicy(options, configurations.Value.OpenId!.Claims!.RequiredUserClaims!, AuthKeys.UserPolicyName);
85-
}
86-
});
70+
options.TokenValidationParameters = new TokenValidationParameters
71+
{
72+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurations.Value.OpenId!.RealmKey!)),
73+
RoleClaimType = configurations.Value.OpenId.RoleClaimType,
74+
ValidIssuer = configurations.Value.OpenId.Realm,
75+
ValidAudiences = configurations.Value.OpenId.Audiences,
76+
ValidateIssuerSigningKey = true,
77+
ValidateIssuer = true,
78+
ValidateLifetime = true,
79+
ValidateAudience = true,
80+
};
81+
});
8782

83+
services.AddAuthorization();
8884
return services;
8985
}
90-
91-
private static void AddPolicy(AuthorizationOptions options, List<Configurations.Claim> claims, string policyName)
92-
{
93-
foreach (var dict in claims)
94-
{
95-
options.AddPolicy(policyName, policy => policy
96-
.RequireAuthenticatedUser()
97-
.RequireClaim("user_roles", dict.UserRoles!));
98-
}
99-
}
10086
}
10187
}

src/Authentication/Logging.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,19 @@ namespace Monai.Deploy.WorkflowManager.Logging
2020
{
2121
public static partial class Log
2222
{
23-
[LoggerMessage(EventId = 500000, Level = LogLevel.Information, Message = "BYpass authentication.")]
23+
[LoggerMessage(EventId = 500000, Level = LogLevel.Information, Message = "Bypass authentication.")]
2424
public static partial void BypassAuthentication(this ILogger logger);
25+
26+
[LoggerMessage(EventId = 500001, Level = LogLevel.Debug, Message = "User '{user}' attempting to access controller '{controller}'.")]
27+
public static partial void UserAccessingController(this ILogger logger, string? user, string controller);
28+
29+
[LoggerMessage(EventId = 500002, Level = LogLevel.Debug, Message = "User '{user}' access denied due to limited permissions: '{permissions}'.")]
30+
public static partial void UserAccessDenied(this ILogger logger, string? user, string? permissions);
31+
32+
[LoggerMessage(EventId = 500003, Level = LogLevel.Trace, Message = "User claim {claim}={value}.")]
33+
public static partial void UserClaimFound(this ILogger logger, string? claim, string? value);
34+
35+
[LoggerMessage(EventId = 500004, Level = LogLevel.Trace, Message = "Checking user claim {claim}={value}.")]
36+
public static partial void CheckingUserClaim(this ILogger logger, string? claim, string? value);
2537
}
2638
}

0 commit comments

Comments
 (0)