Skip to content

Instantly share code, notes, and snippets.

@Frisch12
Created December 8, 2020 09:43
Show Gist options
  • Select an option

  • Save Frisch12/0c061fc14d5fd38b123f1ba3db92d367 to your computer and use it in GitHub Desktop.

Select an option

Save Frisch12/0c061fc14d5fd38b123f1ba3db92d367 to your computer and use it in GitHub Desktop.
AuthorizationHandler becomes no Endpoint resource
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace TestApplication
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<NeedToBeAuthorizedHandler>();
services.AddAuthentication()
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(
AuthenticationSchemaNames.BasicAuthentication,
null);
services.AddAuthorization(
options =>
{
var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(AuthenticationSchemaNames.BasicAuthentication)
.AddRequirements(new NeedToBeAuthorizedRequirement())
.Build();
options.DefaultPolicy = policy;
options.FallbackPolicy = policy;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
[Route("/test")]
public class TestController : ControllerBase
{
[HttpGet("info")]
[NoAuthorization]
public IActionResult TestActionPublic()
{
return Ok("Test result public");
}
[HttpGet("info-private")]
[AuthorizeOwn(Action = "read")]
public IActionResult TestActionPrivate()
{
return Ok("Test private");
}
[HttpGet("info-private")]
[AuthorizeOwn(Action = "write")]
public IActionResult TestActionPrivateNoAccess()
{
return Ok("Test no access");
}
}
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
ClaimsPrincipal user;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
if (!string.Equals("Basic", authHeader.Scheme, StringComparison.InvariantCultureIgnoreCase))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] {':'}, 2);
var username = credentials[0];
var password = credentials[1];
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
return Task.FromResult(AuthenticateResult.Fail("Username and Password are mandatory"));
}
user = Validate(username, password);
if (user == null)
{
return Task.FromResult(AuthenticateResult.Fail("Invalid Username or Password"));
}
}
catch
{
return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header"));
}
var ticket = new AuthenticationTicket(user, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
private ClaimsPrincipal Validate(string username, string password)
{
var isValid = IsUserNameAndPasswordValid(username, password);
if (!isValid)
{
return null;
}
return new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new("username", username) }, "BasicAuth"));
}
private bool IsUserNameAndPasswordValid(string reqUserName, string reqPassword)
{
return reqPassword == "123456" && reqUserName == "user";
}
}
public class NeedToBeAuthorizedHandler : AuthorizationHandler<NeedToBeAuthorizedRequirement, Endpoint>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
NeedToBeAuthorizedRequirement requirement,
Endpoint endpoint)
{
var authorizeAttribute = endpoint.Metadata.GetMetadata<AuthorizeOwnAttribute>();
if (authorizeAttribute == null)
{
var noAuthorizationAttribute = endpoint.Metadata.GetMetadata<NoAuthorizationAttribute>();
if (noAuthorizationAttribute == null)
{
context.Fail();
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
if (context.User.Identity?.IsAuthenticated != true)
{
context.Fail();
return Task.CompletedTask;
}
if (authorizeAttribute.Action == "write")
{
context.Fail();
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class NeedToBeAuthorizedRequirement : IAuthorizationRequirement
{
}
public static class AuthenticationSchemaNames
{
public static readonly string BasicAuthentication = "Basic";
}
public sealed class AuthorizeOwnAttribute : AuthorizeAttribute
{
public string Action { get; set; }
}
public class NoAuthorizationAttribute : AuthorizeAttribute
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment