Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TheDusty01 committed May 8, 2022
0 parents commit b5c1a12
Show file tree
Hide file tree
Showing 79 changed files with 5,288 additions and 0 deletions.
711 changes: 711 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions BlazingAuth.Permissions.Client/AttributeAuthorizationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using System.Reflection;
using System.Linq;

namespace BlazingAuth.Permissions.Client
{
public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement>
where TRequirement : IAuthorizationRequirement
where TAttribute : Attribute
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
{
var attributes = new List<TAttribute>();

if (context.Resource is null)
throw new ArgumentNullException(nameof(context.Resource), "Resource is not specified. Did you forget to set Resource=\"@routeData\" ?");

if (context.Resource is AuthorizePermissionView permissionsView)
{
// Custom AuthorizePermissionView component
return HandleRequirementAsync(context, requirement, permissionsView);
}
else if (context.Resource is RouteData routeData && routeData?.PageType is not null)
{
// Add all AuthorizePermssion attributes to the list
attributes.AddRange(GetAttributes(routeData.PageType));
}

// Handle all AuthorizePermssion attributes (if any)
return HandleRequirementAsync(context, requirement, attributes);
}

protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);
protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, AuthorizePermissionView permissionsView);

private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
{
return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
}
}
}
16 changes: 16 additions & 0 deletions BlazingAuth.Permissions.Client/AuthorizePermissionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Authorization;
using System;

namespace BlazingAuth.Permissions.Client
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class AuthorizePermissionAttribute : AuthorizeAttribute
{
public string[] OrPermissons { get; }

public AuthorizePermissionAttribute(params string[] orPermissons) : base(BlazingAuthPolicies.Permission)
{
OrPermissons = orPermissons;
}
}
}
99 changes: 99 additions & 0 deletions BlazingAuth.Permissions.Client/AuthorizePermissionView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;

namespace BlazingAuth.Permissions.Client
{
/// <summary>
/// Displays differing content depending on the user's authorization status.
/// </summary>
public class AuthorizePermissionView : AuthorizeViewCore
{
private readonly IAuthorizeData[] selfAsAuthorizeData;

/// <summary>
/// Constructs an instance of <see cref="AuthorizeView"/>.
/// </summary>
public AuthorizePermissionView()
{
selfAsAuthorizeData = new[] { new AuthorizeDataAdapter(this) };
Resource = this;
}

/// <summary>
/// A comma delimited list of roles that are allowed to display the content.
/// </summary>
[Parameter] public string? Roles { get; set; }

/// <summary>
/// A comma delimited list of permissions that are needed to display the content.
/// </summary>
[Parameter] public string? AndPermissions { get; set; }

/// <summary>
/// A list of permissions that only one of is needed to display the content.
/// Gets overriden by <see cref="AndPermissions"/> if specified.
/// </summary>
[Parameter] public string[]? AndPermissionsList { get; set; }

/// <summary>
/// A comma delimited list of permissions that only one of is needed to display the content.
/// </summary>
[Parameter] public string? OrPermissions { get; set; }

/// <summary>
/// A list of permissions that only one of is needed to display the content.
/// Gets overriden by <see cref="OrPermissions"/> if specified.
/// </summary>
[Parameter] public string[]? OrPermissionsList { get; set; }

/// <summary>
/// Gets the data used for authorization.
/// </summary>
protected override IAuthorizeData[] GetAuthorizeData()
=> selfAsAuthorizeData;

protected override Task OnParametersSetAsync()
{
// Set lists
AndPermissionsList ??= AndPermissions?.Split(',');
OrPermissionsList ??= OrPermissions?.Split(',');

// Proceed with authorization
return base.OnParametersSetAsync();
}
}

// This is so the AuthorizeView can avoid implementing IAuthorizeData (even privately)
internal class AuthorizeDataAdapter : IAuthorizeData
{
private readonly AuthorizePermissionView component;

public AuthorizeDataAdapter(AuthorizePermissionView component)
{
this.component = component ?? throw new ArgumentNullException(nameof(component));
}

public string? Policy
{
get => BlazingAuthPolicies.Permission;
set => throw new NotSupportedException();
}

public string? Roles
{
get => component.Roles;
set => throw new NotSupportedException();
}

// AuthorizeView doesn't expose any such parameter, as it wouldn't be used anyway,
// since we already have the ClaimsPrincipal by the time AuthorizeView gets involved.
public string? AuthenticationSchemes
{
get => null;
set => throw new NotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<nullable>enable</nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company />
<Authors>Dusty</Authors>
<Description>Easy to use, claims and policy based permission authorization for Blazor (client project)</Description>
<RepositoryType>git</RepositoryType>
<PackageTags>blazor auth authorization authentication permission permissions blazing claims claim policy auth secure safe</PackageTags>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<SignAssembly>False</SignAssembly>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<RepositoryUrl>https://github.com/TheDusty01/BlazingAuth.Permissions</RepositoryUrl>
<Version>6.0.0</Version>
</PropertyGroup>

<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>

<None Include="..\LICENSE.txt">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="6.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BlazingAuth.Permissions\BlazingAuth.Permissions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace BlazingAuth.Permissions.Client
{
public class BlazingAuthAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public BlazingAuthAccountClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) : base(accessor)
{
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
if (user.Identity?.IsAuthenticated == true)
{
var identity = (ClaimsIdentity)user.Identity;

ClaimArrayToMultipleClaims(account, identity, identity.RoleClaimType);
ClaimArrayToMultipleClaims(account, identity, BlazingAuthClaims.Permission.Type);
}

return user;
}

protected static void ClaimArrayToMultipleClaims(RemoteUserAccount account, ClaimsIdentity identity, string claimType)
{
var claims = identity.FindAll(claimType).ToArray();
if (!claims.Any())
return;

foreach (var existingClaim in claims)
{
identity.RemoveClaim(existingClaim);
}

var itemElem = account.AdditionalProperties[claimType];
if (itemElem is JsonElement items)
{
if (items.ValueKind == JsonValueKind.Array)
{
foreach (var item in items.EnumerateArray())
{
identity.AddClaim(new Claim(claimType, item.GetString()!));
}
}
else
{
identity.AddClaim(new Claim(claimType, items.GetString()!));
}
}

}
}
}
26 changes: 26 additions & 0 deletions BlazingAuth.Permissions.Client/BlazingAuthExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BlazingAuth.Permissions.Client
{
public static class BlazingAuthExtensions
{
public static IServiceCollection AddBlazingAuthPermissions(this IServiceCollection services)
{
services.AddAuthorizationCore(options =>
{
options.AddPolicy(BlazingAuthPolicies.Permission, BlazingAuthPolicies.PermissionPolicy);
});

services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

return services;
}
}
}
44 changes: 44 additions & 0 deletions BlazingAuth.Permissions.Client/PermissionAuthorizationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using BlazingAuth.Permissions.Requirements;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace BlazingAuth.Permissions.Client
{
public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, AuthorizePermissionAttribute>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<AuthorizePermissionAttribute> attributes)
{
foreach (var permissionAttribute in attributes)
{
if (!context.User.HasAnyPermission(permissionAttribute.OrPermissons))
{
context.Fail(new AuthorizationFailureReason(this, "User doesn't have the required permissions."));
return Task.CompletedTask;
}
}

context.Succeed(requirement);
return Task.CompletedTask;
}

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, AuthorizePermissionView permissionsView)
{
bool andPermissionsValid = permissionsView.AndPermissionsList is null || context.User.HasPermissions(permissionsView.AndPermissionsList);
bool orPermissionsValid = permissionsView.OrPermissionsList is null || context.User.HasAnyPermission(permissionsView.OrPermissionsList);

if (!andPermissionsValid || !orPermissionsValid)
{
context.Fail(new AuthorizationFailureReason(this, "User doesn't have the required permissions."));
return Task.CompletedTask;
}

context.Succeed(requirement);
return Task.CompletedTask;
}
}
}
12 changes: 12 additions & 0 deletions BlazingAuth.Permissions.Server/AuthorizePermissionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;

namespace BlazingAuth.Permissions.Server
{
public class AuthorizePermissionAttribute : TypeFilterAttribute
{
public AuthorizePermissionAttribute(params string[] orPermissons) : base(typeof(PermissionAuthorizationFilter))
{
Arguments = new object[] { orPermissons };
}
}
}
Loading

0 comments on commit b5c1a12

Please sign in to comment.