Skip to content

Commit

Permalink
Merge pull request #3 from Project-MONAI/vchang/logging
Browse files Browse the repository at this point in the history
Update config structure & logging
  • Loading branch information
mocsharp authored Dec 2, 2022
2 parents 74a98b1 + d6a5d06 commit 8893610
Show file tree
Hide file tree
Showing 23 changed files with 423 additions and 119 deletions.
54 changes: 43 additions & 11 deletions src/Authentication/Configurations/AuthenticationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ namespace Monai.Deploy.Security.Authentication.Configurations
{
public class AuthenticationOptions
{
[ConfigurationKeyName("BypassAuthentication")]
[ConfigurationKeyName("bypassAuthentication")]
public bool? BypassAuthentication { get; set; }

[ConfigurationKeyName("OpenId")]
[ConfigurationKeyName("openId")]
public OpenIdOptions? OpenId { get; set; }

public bool BypassAuth(ILogger logger)
Expand All @@ -42,26 +42,58 @@ public bool BypassAuth(ILogger logger)

if (OpenId is null)
{
throw new InvalidOperationException("OpenId configuration is invalid.");
throw new InvalidOperationException("openId configuration is invalid.");
}
if (OpenId.Claims is null || OpenId.Claims.RequiredUserClaims!.IsNullOrEmpty() || OpenId.Claims.RequiredAdminClaims!.IsNullOrEmpty())
if (string.IsNullOrWhiteSpace(OpenId.ClientId))
{
throw new InvalidOperationException("No claims defined for OpenId.");
throw new InvalidOperationException("No clientId defined for OpenId.");
}
if (string.IsNullOrWhiteSpace(OpenId.ClientId))
if (string.IsNullOrWhiteSpace(OpenId.RealmKey))
{
throw new InvalidOperationException("No ClientId defined for OpenId.");
throw new InvalidOperationException("No realmKey defined for OpenId.");
}
if (string.IsNullOrWhiteSpace(OpenId.ServerRealmKey))
if (string.IsNullOrWhiteSpace(OpenId.Realm))
{
throw new InvalidOperationException("No ServerRealmKey defined for OpenId.");
throw new InvalidOperationException("No realm defined for OpenId.");
}
if (string.IsNullOrWhiteSpace(OpenId.ServerRealm))
if (OpenId.Claims is null || OpenId.Claims.UserClaims!.IsNullOrEmpty() || OpenId.Claims.AdminClaims!.IsNullOrEmpty())
{
throw new InvalidOperationException("No ServerRealm defined for OpenId.");
throw new InvalidOperationException("No claimMappings defined for OpenId.");
}

ValidateClaims(OpenId.Claims.UserClaims!, true);
ValidateClaims(OpenId.Claims.AdminClaims!, false);

return false;
}

private void ValidateClaims(List<ClaimMapping> claims, bool validateEndpoints)
{
foreach (var claim in claims)
{
if (string.IsNullOrWhiteSpace(claim.ClaimType))
{
throw new InvalidOperationException("Value for claimType is invalid.");
}

if (claim.ClaimValues.IsNullOrEmpty())
{
throw new InvalidOperationException("Value for claimType is invalid.");
}

foreach (var claimValue in claim.ClaimValues)
{
if (string.IsNullOrWhiteSpace(claimValue))
{
throw new InvalidOperationException($"Invalid claimValue for {claim.ClaimType}.");
}
}

if (validateEndpoints && claim.Endpoints.IsNullOrEmpty())
{
throw new InvalidOperationException("Value for claimType is invalid.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,24 @@

namespace Monai.Deploy.Security.Authentication.Configurations
{
public class Claims
public class ClaimMappings
{
[ConfigurationKeyName("RequiredUserClaims")]
public List<Claim>? RequiredUserClaims { get; set; }
[ConfigurationKeyName("userClaims")]
public List<ClaimMapping>? UserClaims { get; set; }

[ConfigurationKeyName("RequiredAdminClaims")]
public List<Claim>? RequiredAdminClaims { get; set; }
[ConfigurationKeyName("adminClaims")]
public List<ClaimMapping>? AdminClaims { get; set; }
}

public class Claim
public class ClaimMapping
{
[ConfigurationKeyName("user_roles")]
public string? UserRoles { get; set; }
[ConfigurationKeyName("claimType")]
public string ClaimType { get; set; } = string.Empty;

[ConfigurationKeyName("claimValues")]
public List<string> ClaimValues { get; set; } = new List<string>();

[ConfigurationKeyName("endpoints")]
public List<string>? Endpoints { get; set; }
public List<string>? Endpoints { get; set; } = default;
}
}
23 changes: 15 additions & 8 deletions src/Authentication/Configurations/OpenIdOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,32 @@
* limitations under the License.
*/

using System.Security.Claims;
using Microsoft.Extensions.Configuration;

namespace Monai.Deploy.Security.Authentication.Configurations
{
public class OpenIdOptions
{
[ConfigurationKeyName("ServerRealm")]
public string? ServerRealm { get; set; }
[ConfigurationKeyName("realm")]
public string? Realm { get; set; }

[ConfigurationKeyName("ServerRealmKey")]
public string? ServerRealmKey { get; set; }
[ConfigurationKeyName("realmKey")]
public string? RealmKey { get; set; }

[ConfigurationKeyName("ClientId")]
[ConfigurationKeyName("clientId")]
public string? ClientId { get; set; }

[ConfigurationKeyName("Claims")]
public Claims? Claims { get; set; }
[ConfigurationKeyName("claimMappings")]
public ClaimMappings? Claims { get; set; }

[ConfigurationKeyName("Audiences")]
[ConfigurationKeyName("audiences")]
public IList<string>? Audiences { get; set; }

[ConfigurationKeyName("roleClaimType")]
public string RoleClaimType { get; set; } = ClaimTypes.Role;

[ConfigurationKeyName("clearDefaultRoleMappigns")]
public bool ClearDefaultRoleMappigns { get; set; } = true;
}
}
1 change: 0 additions & 1 deletion src/Authentication/Extensions/AuthKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ public static class AuthKeys

// Configuration Keys
public const string OpenId = "OpenId";
public const string UserRoles = "user_roles";
}
}
33 changes: 25 additions & 8 deletions src/Authentication/Extensions/HttpContextExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Monai.Deploy.Security.Authentication.Middleware;
using Monai.Deploy.WorkflowManager.Logging;

namespace Monai.Deploy.Security.Authentication.Extensions
{
Expand All @@ -24,32 +27,46 @@ public static class HttpContextExtension
/// <summary>
/// Gets endpoints specified in config for roles in claims.
/// </summary>
/// <param name="httpcontext"></param>
/// <param name="httpContext"></param>
/// <param name="requiredClaims"></param>
/// <returns></returns>
public static List<string> GetValidEndpoints(this HttpContext httpcontext, List<Configurations.Claim> adminClaims, List<Configurations.Claim> userClaims)
public static List<string> GetValidEndpoints(this HttpContext httpContext, ILogger<EndpointAuthorizationMiddleware> logger, List<Configurations.ClaimMapping> adminClaims, List<Configurations.ClaimMapping> userClaims)
{
Guard.Against.Null(adminClaims);
Guard.Against.Null(userClaims);

foreach (var claim in httpContext.User.Claims)
{
logger.UserClaimFound(claim.Type, claim.Value);

}

foreach (var claim in adminClaims!)
{
if (httpcontext.User.HasClaim(AuthKeys.UserRoles, claim.UserRoles!))
foreach (var role in claim.ClaimValues)
{
return new List<string> { "all" };
logger.CheckingUserClaim(claim.ClaimType, role);
if (httpContext.User.HasClaim(claim.ClaimType, role))
{
return new List<string> { "*" };
}
}
}

var endpoints = new List<string>();
foreach (var claim in userClaims!)
{
if (httpcontext.User.HasClaim(AuthKeys.UserRoles, claim.UserRoles!))
foreach (var role in claim.ClaimValues)
{
return claim.Endpoints!;
logger.CheckingUserClaim(claim.ClaimType, role);
if (httpContext.User.HasClaim(claim.ClaimType, role))
{
endpoints.AddRange(claim.Endpoints!);
}
}

}

return new List<string>();
return endpoints.Distinct().ToList();
}
}
}
68 changes: 27 additions & 41 deletions src/Authentication/Extensions/MonaiAuthenticationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* limitations under the License.
*/

using System.IdentityModel.Tokens.Jwt;
using System.Text;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -49,53 +49,39 @@ public static IServiceCollection AddMonaiAuthentication(
return services;
}

services.AddAuthentication(options =>
if (configurations.Value.OpenId?.ClearDefaultRoleMappigns ?? false)
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, AuthKeys.OpenId, options =>
{
options.Authority = configurations.Value.OpenId!.ServerRealm;
options.Audience = configurations.Value.OpenId!.ServerRealm;
options.RequireHttpsMetadata = false;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("roles");
}

options.TokenValidationParameters = new TokenValidationParameters
services
.AddAuthentication(options =>
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurations.Value.OpenId!.ServerRealmKey!)),
ValidIssuer = configurations.Value.OpenId.ServerRealm,
ValidAudiences = configurations.Value.OpenId.Audiences,
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = true,
};
});

services.AddAuthorization(options =>
{
if (configurations.Value.OpenId!.Claims!.RequiredAdminClaims!.Any())
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, AuthKeys.OpenId, options =>
{
AddPolicy(options, configurations.Value.OpenId!.Claims!.RequiredAdminClaims!, AuthKeys.AdminPolicyName);
}
options.Authority = configurations.Value.OpenId!.Realm;
options.Audience = configurations.Value.OpenId!.Realm;
options.RequireHttpsMetadata = false;

if (configurations.Value.OpenId!.Claims!.RequiredUserClaims!.Any())
{
AddPolicy(options, configurations.Value.OpenId!.Claims!.RequiredUserClaims!, AuthKeys.UserPolicyName);
}
});
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurations.Value.OpenId!.RealmKey!)),
RoleClaimType = configurations.Value.OpenId.RoleClaimType,
ValidIssuer = configurations.Value.OpenId.Realm,
ValidAudiences = configurations.Value.OpenId.Audiences,
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = true,
};
});

services.AddAuthorization();
return services;
}

private static void AddPolicy(AuthorizationOptions options, List<Configurations.Claim> claims, string policyName)
{
foreach (var dict in claims)
{
options.AddPolicy(policyName, policy => policy
.RequireAuthenticatedUser()
.RequireClaim("user_roles", dict.UserRoles!));
}
}
}
}
14 changes: 13 additions & 1 deletion src/Authentication/Logging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,19 @@ namespace Monai.Deploy.WorkflowManager.Logging
{
public static partial class Log
{
[LoggerMessage(EventId = 500000, Level = LogLevel.Information, Message = "BYpass authentication.")]
[LoggerMessage(EventId = 500000, Level = LogLevel.Information, Message = "Bypass authentication.")]
public static partial void BypassAuthentication(this ILogger logger);

[LoggerMessage(EventId = 500001, Level = LogLevel.Debug, Message = "User '{user}' attempting to access controller '{controller}'.")]
public static partial void UserAccessingController(this ILogger logger, string? user, string controller);

[LoggerMessage(EventId = 500002, Level = LogLevel.Debug, Message = "User '{user}' access denied due to limited permissions: '{permissions}'.")]
public static partial void UserAccessDenied(this ILogger logger, string? user, string? permissions);

[LoggerMessage(EventId = 500003, Level = LogLevel.Trace, Message = "User claim {claim}={value}.")]
public static partial void UserClaimFound(this ILogger logger, string? claim, string? value);

[LoggerMessage(EventId = 500004, Level = LogLevel.Trace, Message = "Checking user claim {claim}={value}.")]
public static partial void CheckingUserClaim(this ILogger logger, string? claim, string? value);
}
}
Loading

0 comments on commit 8893610

Please sign in to comment.