Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for role and permissions with treedictionary #63

Merged
merged 13 commits into from
Oct 25, 2023
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using EasyMicroservices.AuthenticationsMicroservice.VirtualServerForTests;
using EasyMicroservices.AuthenticationsMicroservice.VirtualServerForTests.TestResources;
using EasyMicroservices.Cores.AspCoreApi.Authorizations;
using EasyMicroservices.Cores.AspCoreApi.Interfaces;
using EasyMicroservices.Cores.AspEntityFrameworkCoreApi.Interfaces;
using EasyMicroservices.ServiceContracts;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Net.Http.Headers;
using System.Text;

namespace EasyMicroservices.Cores.AspCore.Tests
{
public class AuthorizationRolePermissionsTests : BasicTests
{
public override int AppPort => 4566;
public AuthorizationRolePermissionsTests() : base()
{ }

protected override void InitializeTestHost(bool isUseAuthorization, Action<IServiceCollection> serviceCollection)
{
serviceCollection = (services) =>
{
services.AddScoped<IAuthorization, AspCoreAuthorization>();
services.AddScoped(service => service.GetService<IUnitOfWork>().GetUniqueIdentityManager());
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://github.com/easymicroservices",
ValidAudience = "easymicroservices",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("VGhpc0lzQVNlY3JldEtleUZvckp3dEF1dGhlbnRpY2F0aW9u="))
};
});
};
base.InitializeTestHost(true, serviceCollection);
}

protected override void AssertTrue(MessageContract messageContract)
{
Assert.False(messageContract.IsSuccess);
Assert.True(messageContract.Error.FailedReasonType == FailedReasonType.AccessDenied, messageContract.ToString());
}

protected override void AssertFalse(MessageContract messageContract)
{
Assert.False(messageContract);
AssertTrue(messageContract);
}

protected override void AuthorizeAssert(MessageContract messageContract)
{
AssertTrue(messageContract);
}

static AuthenticationVirtualTestManager AuthenticationVirtualTestManager = new();
[Theory]
[InlineData("TestExampleFailed", "EndUser", "NoAccess", "NoAccess", false)]
[InlineData("TestExample", "EndUser", "User", "CheckHasAccess", true)]
[InlineData("TestExample", "EndUser", null, "CheckHasAccess", true)]
[InlineData("TestExample", "EndUser", null, null, true)]
[InlineData("TestExample", "EndUser", "User", null, true)]
public async Task WriterRoleTest(string microserviceName, string roleName, string serviceName, string methodName, bool result)
{
int portNumber = 1045;
AspCoreAuthorization.AuthenticationRouteAddress = $"http://{localhost}:{portNumber}";
await AuthenticationVirtualTestManager.OnInitialize(portNumber);
var resources = AuthenticationResource.GetResources(microserviceName, new Dictionary<string, List<TestServicePermissionContract>>()
{
{
roleName ,
new List<TestServicePermissionContract>()
{
new TestServicePermissionContract()
{
MicroserviceName = microserviceName,
MethodName = methodName,
ServiceName = serviceName
}
}
}
});

foreach (var resource in resources)
{
AuthenticationVirtualTestManager.AppendService(portNumber, resource.Key, resource.Value);
}

HttpClient CurrentHttpClient = new HttpClient();
if (result)
{
var loginResult = await CurrentHttpClient.GetFromJsonAsync<MessageContract<string>>($"{GetBaseUrl()}/api/user/login");
Assert.True(loginResult);
CurrentHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResult.Result);
}
var data = await CurrentHttpClient.GetFromJsonAsync<MessageContract>($"{GetBaseUrl()}/api/user/CheckHasAccess");
if (!result)
AssertTrue(data);
else
Assert.True(data, data.ToString());
}

[Theory]
[InlineData("TestExampleFailed", "EndUser", "NoAccess")]
[InlineData("TestExample", "EndUser", "User")]
[InlineData("TestExample", "EndUser", null)]
public async Task ReaderRoleTest(string microserviceName, string roleName, string serviceName)
{
int portNumber = 1045;
AspCoreAuthorization.AuthenticationRouteAddress = $"http://{localhost}:{portNumber}";
await AuthenticationVirtualTestManager.OnInitialize(portNumber);
var resources = AuthenticationResource.GetResources(microserviceName, new Dictionary<string, List<TestServicePermissionContract>>()
{
{
roleName ,
new List<TestServicePermissionContract>()
{
new TestServicePermissionContract()
{
MicroserviceName = microserviceName,
MethodName = "CheckHasAccess",
ServiceName = serviceName
}
}
}
});

foreach (var resource in resources)
{
AuthenticationVirtualTestManager.AppendService(portNumber, resource.Key, resource.Value);
}
HttpClient CurrentHttpClient = new HttpClient();
var loginResult = await CurrentHttpClient.GetFromJsonAsync<MessageContract<string>>($"{GetBaseUrl()}/api/user/login");
Assert.True(loginResult);
CurrentHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResult.Result);
var data = await CurrentHttpClient.GetFromJsonAsync<MessageContract>($"{GetBaseUrl()}/api/user/CheckHasNoAccess");
AssertTrue(data);
}
}
}
12 changes: 8 additions & 4 deletions src/CSharp/EasyMicroservices.Cores.AspCore.Tests/BasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public BasicTests()
{
InitializeTestHost(false, null);
}

public virtual int AppPort { get; } = 4564;
protected virtual void InitializeTestHost(bool isUseAuthorization, Action<IServiceCollection> serviceCollection)
{
Expand Down Expand Up @@ -44,7 +43,7 @@ string RouteAddress
{
get
{
return $"http://localhost:{AppPort}";
return $"http://{localhost}:{AppPort}";
}
}

Expand All @@ -67,7 +66,7 @@ public async Task AuthorizeTest()
{
var data = await HttpClient.GetStringAsync($"{GetBaseUrl()}/api/user/AuthorizeError");
var result = JsonConvert.DeserializeObject<MessageContract>(data);
Assert.True(result.Error.FailedReasonType == FailedReasonType.SessionAccessDenied);
AuthorizeAssert(result);
}

[Fact]
Expand All @@ -76,7 +75,7 @@ public async Task InternalErrorTest()
var data = await HttpClient.GetStringAsync($"{GetBaseUrl()}/api/user/InternalError");
var result = JsonConvert.DeserializeObject<MessageContract>(data);
AssertFalse(result);
if (result.Error.FailedReasonType != FailedReasonType.SessionAccessDenied)
if (result.Error.FailedReasonType != FailedReasonType.SessionAccessDenied && result.Error.FailedReasonType != FailedReasonType.AccessDenied)
AssertContains(result.Error.StackTrace, x => x.Contains("UserController.cs"));
}

Expand All @@ -97,6 +96,11 @@ public async Task AddUser()
AssertTrue(users.Result.All(x => DefaultUniqueIdentityManager.DecodeUniqueIdentity(x.UniqueIdentity).Length > 2));
}

protected virtual void AuthorizeAssert(MessageContract messageContract)
{
Assert.True(messageContract.Error.FailedReasonType == FailedReasonType.SessionAccessDenied);
}

protected virtual void AssertTrue(MessageContract messageContract)
{
Assert.True(messageContract);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using EasyMicroservices.Cores.AspCoreApi;
using EasyMicroservices.Cores.AspEntityFrameworkCoreApi.Interfaces;
using EasyMicroservices.Cores.Database.Interfaces;
using EasyMicroservices.Cores.Tests.DatabaseLogics.Database.Entities;
using EasyMicroservices.ServiceContracts;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace EasyMicroservices.Cores.AspCore.Tests.Controllers
{
Expand All @@ -22,6 +24,38 @@ public MessageContract AuthorizeError()
return true;
}

[HttpGet]
public MessageContract CheckHasAccess()
{
return true;
}

[HttpGet]
public MessageContract CheckHasNoAccess()
{
return true;
}

[HttpGet]
[AllowAnonymous]
public MessageContract<string> Login()
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes("VGhpc0lzQVNlY3JldEtleUZvckp3dEF1dGhlbnRpY2F0aW9u=");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "EndUser") }),
Expires = DateTime.UtcNow.AddSeconds(1000),
Issuer = "https://github.com/easymicroservices",
Audience = "easymicroservices",
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);

return tokenString;
}

[HttpGet]
public MessageContract InternalError()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.11" />
<PackageReference Include="EasyMicroservices.AuthenticationsMicroservice.VirtualServerForTests" Version="0.0.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.12" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -25,7 +26,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing">
<Version>7.0.11</Version>
<Version>7.0.12</Version>
</PackageReference>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using EasyMicroservices.Cores.AspCoreApi.Interfaces;
using EasyMicroservices.Cores.Database.Interfaces;
using EasyMicroservices.ServiceContracts;
using EasyMicroservices.Utilities.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using ServicePermissionContract = Authentications.GeneratedServices.ServicePermissionContract;
namespace EasyMicroservices.Cores.AspCoreApi.Authorizations
{
/// <summary>
///
/// </summary>
public class AspCoreAuthorization : IAuthorization
{
/// <summary>
///
/// </summary>
public AspCoreAuthorization(IUniqueIdentityManager uniqueIdentityManager)
{
UniqueIdentityManager = uniqueIdentityManager;
}

/// <summary>
///
/// </summary>
public IUniqueIdentityManager UniqueIdentityManager { get; }
/// <summary>
///
/// </summary>
public static string AuthenticationRouteAddress { get; set; }

/// <summary>
///
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<MessageContract> CheckIsAuthorized(HttpContext httpContext)
{
var hasPermission = await HasPermission(httpContext).AsCheckedResult();
if (!hasPermission)
return (FailedReasonType.AccessDenied, "Sorry, you cannot call this service, you have not enough permissions!");
return true;
}

static ConcurrentDictionary<string, ICollection<ServicePermissionContract>> CachedPermissions { get; set; } = new();
static readonly ConcurrentTreeDictionary TreeDictionary = new ConcurrentTreeDictionary();
async Task<MessageContract> FetchData(string roleName)
{
var servicePermissionClient = new Authentications.GeneratedServices.ServicePermissionClient(AuthenticationRouteAddress, new System.Net.Http.HttpClient());
var permissionsResult = await servicePermissionClient.GetAllPermissionsByAsync(new Authentications.GeneratedServices.ServicePermissionRequestContract()
{
MicroserviceName = UniqueIdentityManager.MicroserviceName,
RoleName = roleName
});
if (permissionsResult.IsSuccess)
{
CachedPermissions[roleName] = permissionsResult.Result;
AddToTreeDictionary(roleName, permissionsResult.Result);
return true;
}
return permissionsResult.ToContract();
}

void AddToTreeDictionary(string roleName, ICollection<ServicePermissionContract> permissions)
{
foreach (var permission in permissions)
{
TreeDictionary.TryAdd(roleName, permission.MicroserviceName, permission.ServiceName, permission.MethodName, true);
}
}

async Task<MessageContract<bool>> HasPermission(HttpContext httpContext)
{
var controllerActionDescriptor = httpContext.GetEndpoint().Metadata.GetMetadata<ControllerActionDescriptor>();
if (controllerActionDescriptor.ControllerTypeInfo.GetCustomAttributes(typeof(AllowAnonymousAttribute)).Any() ||
controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(AllowAnonymousAttribute)).Any())
return true;

string controllerName = httpContext.Request.RouteValues["controller"].ToString();
string actionName = httpContext.Request.RouteValues["action"].ToString();
List<Claim> roleClaims = httpContext.User.FindAll(ClaimTypes.Role).ToList();
if (roleClaims.Count == 0)
return (FailedReasonType.AccessDenied, "There is no claim role founded! did you forgot to use services.AddAuthentication?");
if (!roleClaims.All(x => CachedPermissions.ContainsKey(x.Value)))
{
foreach (var role in roleClaims)
{
var fetchDataResult = await FetchData(role.Value);
if (!fetchDataResult)
return fetchDataResult.ToContract<bool>();
}
}
return roleClaims.Any(role => TreeDictionary.TryGetValue(new object[] { role.Value, UniqueIdentityManager.MicroserviceName, controllerName, actionName }, out IList<object> permissions)
&& permissions.LastOrDefault() is bool value && value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Platforms>AnyCPU;x64;x86</Platforms>
<Authors>EasyMicroservices</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.0.0.38</Version>
<Version>0.0.0.41</Version>
<Description>asp core servces.</Description>
<Copyright>[email protected]</Copyright>
<PackageTags>core,cores,base,database,services,asp,aspnet</PackageTags>
Expand All @@ -25,4 +25,12 @@
<None Include="..\..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="EasyMicroservices.AuthenticationsMicroservice.Clients" Version="0.0.0.10" />
</ItemGroup>

<ItemGroup>
<Folder Include="Attributes\" />
</ItemGroup>

</Project>
Loading
Loading