Skip to content

Commit

Permalink
introduce DisableAutoProtection and ExplicitResourceProtection; Refac…
Browse files Browse the repository at this point in the history
…tor Middleware; Extend example app
  • Loading branch information
Friedemann Braune committed May 4, 2024
1 parent afb7d27 commit 377c015
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 14 deletions.
14 changes: 14 additions & 0 deletions samples/AutoAuthorization/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace AutoAuthorization.Controllers;

using Keycloak.AuthServices.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
Expand All @@ -8,4 +10,16 @@ public class ApiController : ControllerBase
[Route("/api/auto-auth")]
[ProducesResponseType(statusCode: 200, type: typeof(string))]
public IActionResult GetData() => this.Ok("Auto Authorization works.");

[HttpGet]
[Route("/api/no-auth")]
[ProducesResponseType(statusCode: 200, type: typeof(string))]
[DisableUriBasedResourceProtection]
public IActionResult GetDataWithoutAuth() => this.Ok("Auto Authorization works, but is disabled by attribute.");

[HttpGet]
[Route("/api/explicit-auth")]
[ProducesResponseType(statusCode: 200, type: typeof(string))]
[ExplicitResourceProtection("/api/auto-auth", "GET")]
public IActionResult GetDataWithExplicitAuth() => this.Ok("Auto Authorization works with explicit choosing resource name and scope.");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Keycloak.AuthServices.Authorization;

/// <summary>
/// Use this attribute on a method when the <see cref="UriBasedResourceProtectionMiddleware"/>
/// to exclude the method from uri based resource protection and set a specific resource name and scope instead.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class ExplicitResourceProtectionAttribute : Attribute
{
/// <summary>
/// <see cref="ExplicitResourceProtectionAttribute"/>
/// </summary>
/// <param name="resourceName">The name of the resource a configured in Keycloak mandatory and unique "Name" field (also referred as RESOURCE_ID).</param>
/// <param name="scope">A valid scope from the list of applied scopes to the resource.</param>
public ExplicitResourceProtectionAttribute(string resourceName, string scope)
{
this.ResourceName = resourceName;
this.Scope = scope;
}

/// <summary>
/// The resource name to authorize for.
/// </summary>
public string ResourceName { get; }

/// <summary>
/// The scope to authorize the resource for.
/// </summary>
public string Scope { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Keycloak.AuthServices.Authorization;

/// <summary>
/// Use this attribute on a method when the <see cref="UriBasedResourceProtectionMiddleware"/>
/// to exclude the method from uri based resource protection.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class DisableUriBasedResourceProtectionAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,64 @@ public UriBasedResourceProtectionMiddleware(RequestDelegate next, IKeycloakProte
/// <inheritdoc/>
public async Task InvokeAsync(HttpContext context)
{
var targetAllowsAnonymous = context.Features?
if (this.EvaluateAuthorization(context).Result)
{
await this.next(context);
}
}

private async Task<bool> EvaluateAuthorization(HttpContext context)
{
var attributes = context.Features?
.Get<IEndpointFeature>()?
.Endpoint?
.Metadata
.Any(attribute => attribute is AllowAnonymousAttribute) ?? false;
.Metadata;

if (UriBasedProtectionDisabled(attributes))
{
return true;
}

if (!targetAllowsAnonymous)
var resourceName = "";
var scope = "";

if (attributes != null && attributes
.SingleOrDefault(attribute => attribute is ExplicitResourceProtectionAttribute)
is ExplicitResourceProtectionAttribute explicitResourceProtectionAttribute)
{
resourceName = explicitResourceProtectionAttribute.ResourceName;
scope = explicitResourceProtectionAttribute.Scope;
}
else
{
var isAuthorized = await this.client.VerifyAccessToResource(
context.Request.Path, context.Request.Method, CancellationToken.None);
resourceName = context.Request.Path;
scope = context.Request.Method;
}

if (!isAuthorized)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
var isAuthorized = await this.client.VerifyAccessToResource(
resourceName, scope, CancellationToken.None);

if (isAuthorized)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return true;
}

await this.next(context);
return false;
}

private static bool UriBasedProtectionDisabled(EndpointMetadataCollection? attributes) => attributes != null && attributes.Any(attribute => attribute is AllowAnonymousAttribute
or DisableUriBasedResourceProtectionAttribute);
}

/// <summary/>
public static class MiddlewareExtensions
{
/// <summary>
/// Extension method to enable automatic resource protection.
/// Extension method to enable automatic uri based resource protection.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/> isntance.</param>
/// <returns>The <see cref="IApplicationBuilder"/> isntance with <see cref="UriBasedResourceProtectionMiddleware"/> usage.</returns>
public static IApplicationBuilder UseUriBasedKeycloakEndpointProtection(
this IApplicationBuilder builder) => builder.UseMiddleware<UriBasedResourceProtectionMiddleware>();
}
}

0 comments on commit 377c015

Please sign in to comment.