实现权限访问控制列表
This commit is contained in:
parent
5a170f3024
commit
258781bdc5
@ -0,0 +1,14 @@
|
||||
using HelloShop.IdentityService.Entities;
|
||||
using HelloShop.IdentityService.EntityFrameworks;
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
|
||||
namespace HelloShop.IdentityService.Authorization;
|
||||
public class LocalPermissionChecker(IHttpContextAccessor httpContextAccessor, IdentityServiceDbContext dbContext, IDistributedCache distributedCache) : PermissionChecker(httpContextAccessor, distributedCache)
|
||||
{
|
||||
public override async Task<bool> IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
return await dbContext.Set<PermissionGranted>().AsNoTracking().AnyAsync(x => x.RoleId == roleId && x.PermissionName == name && x.ResourceType == resourceType && x.ResourceId == resourceId);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
using HelloShop.IdentityService.Entities;
|
||||
using HelloShop.IdentityService.EntityFrameworks;
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using HelloShop.ServiceDefaults.Constants;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class PermissionsController(IdentityServiceDbContext dbContext) : ControllerBase
|
||||
{
|
||||
|
||||
[HttpGet(nameof(PermissionList))]
|
||||
public async Task<ActionResult<IEnumerable<PermissionGrantedResponse>>> PermissionList(int? roleId, string? name, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
var roleIds = HttpContext.User.FindAll(CustomClaimTypes.RoleIdentifier).Select(c => Convert.ToInt32(c.Value));
|
||||
|
||||
if (roleId.HasValue && roleIds.Any(x => x == roleId))
|
||||
{
|
||||
roleIds = [roleId.Value];
|
||||
}
|
||||
|
||||
List<PermissionGrantedResponse> result = [];
|
||||
|
||||
IQueryable<PermissionGranted> queryable = dbContext.Set<PermissionGranted>().Where(x => roleIds.Contains(x.RoleId));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
queryable = queryable.Where(x => x.PermissionName == name);
|
||||
}
|
||||
|
||||
var permissionGrants = await queryable.Where(x => x.ResourceType == resourceType && x.ResourceId == resourceId).Distinct().ToListAsync();
|
||||
|
||||
result.AddRange(permissionGrants.Select(x => new PermissionGrantedResponse
|
||||
{
|
||||
Name = x.PermissionName,
|
||||
ResourceType = x.ResourceType,
|
||||
ResourceId = x.ResourceId,
|
||||
IsGranted = true
|
||||
}));
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
@ -1,20 +1,24 @@
|
||||
// Copyright (c) HelloShop Corporation. All rights reserved.
|
||||
// See the license file in the project root for more information.
|
||||
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HelloShop.IdentityService.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class TestsController : ControllerBase
|
||||
public class TestsController(IPermissionChecker permissionChecker, IAuthorizationService authorizationService) : ControllerBase
|
||||
{
|
||||
[HttpGet(nameof(Foo))]
|
||||
|
||||
[Authorize(IdentityPermissions.Users.Update)]
|
||||
public IActionResult Foo()
|
||||
public async Task<IActionResult> Foo()
|
||||
{
|
||||
var result = await permissionChecker.IsGrantedAsync(IdentityPermissions.Users.Update, "Order", "1");
|
||||
|
||||
var result2 = await authorizationService.AuthorizeAsync(User, IdentityPermissions.Users.Update);
|
||||
|
||||
return Ok("Hello, World!");
|
||||
}
|
||||
|
||||
|
14
src/HelloShop.IdentityService/Entities/PermissionGranted.cs
Normal file
14
src/HelloShop.IdentityService/Entities/PermissionGranted.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace HelloShop.IdentityService.Entities;
|
||||
|
||||
public class PermissionGranted
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int RoleId { get; set; }
|
||||
|
||||
public required string PermissionName { get; set; }
|
||||
|
||||
public string? ResourceType { get; set; }
|
||||
|
||||
public string? ResourceId { get; set; }
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using HelloShop.IdentityService.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace HelloShop.IdentityService.EntityFrameworks.EntityConfigurations;
|
||||
|
||||
public class PermissionGrantedEntityTypeConfiguration : IEntityTypeConfiguration<PermissionGranted>
|
||||
{
|
||||
public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<PermissionGranted> builder)
|
||||
{
|
||||
builder.ToTable("PermissionGranted");
|
||||
|
||||
builder.Property(x => x.Id);
|
||||
builder.Property(x => x.PermissionName).HasMaxLength(64);
|
||||
builder.Property(x => x.ResourceType).HasMaxLength(16);
|
||||
builder.Property(x => x.ResourceId).HasMaxLength(32);
|
||||
|
||||
builder.HasOne<Role>().WithMany().HasForeignKey(x => x.RoleId).IsRequired();
|
||||
|
||||
builder.HasIndex(x => new { x.RoleId, x.PermissionName, x.ResourceType, x.ResourceId }).IsUnique();
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
{
|
||||
[DbContext(typeof(IdentityServiceDbContext))]
|
||||
[Migration("20240316084118_InitialCreate")]
|
||||
[Migration("20240327110650_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@ -25,6 +25,38 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("HelloShop.IdentityService.Entities.PermissionGranted", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("PermissionName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("ResourceType")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId", "PermissionName", "ResourceType", "ResourceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("PermissionGranted", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HelloShop.IdentityService.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -262,6 +294,15 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
b.ToTable("UserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HelloShop.IdentityService.Entities.PermissionGranted", b =>
|
||||
{
|
||||
b.HasOne("HelloShop.IdentityService.Entities.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
|
||||
{
|
||||
b.HasOne("HelloShop.IdentityService.Entities.Role", null)
|
@ -55,6 +55,28 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PermissionGranted",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
RoleId = table.Column<int>(type: "integer", nullable: false),
|
||||
PermissionName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
ResourceType = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true),
|
||||
ResourceId = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PermissionGranted", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PermissionGranted_Roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RoleClaims",
|
||||
columns: table => new
|
||||
@ -161,6 +183,12 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PermissionGranted_RoleId_PermissionName_ResourceType_Resour~",
|
||||
table: "PermissionGranted",
|
||||
columns: new[] { "RoleId", "PermissionName", "ResourceType", "ResourceId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RoleClaims_RoleId",
|
||||
table: "RoleClaims",
|
||||
@ -202,6 +230,9 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PermissionGranted");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RoleClaims");
|
||||
|
@ -22,6 +22,38 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("HelloShop.IdentityService.Entities.PermissionGranted", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("PermissionName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("ResourceType")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId", "PermissionName", "ResourceType", "ResourceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("PermissionGranted", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HelloShop.IdentityService.Entities.Role", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -259,6 +291,15 @@ namespace HelloShop.IdentityService.EntityFrameworks.Migrations
|
||||
b.ToTable("UserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HelloShop.IdentityService.Entities.PermissionGranted", b =>
|
||||
{
|
||||
b.HasOne("HelloShop.IdentityService.Entities.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
|
||||
{
|
||||
b.HasOne("HelloShop.IdentityService.Entities.Role", null)
|
||||
|
@ -1,8 +1,10 @@
|
||||
using HelloShop.IdentityService;
|
||||
using HelloShop.IdentityService.Authorization;
|
||||
using HelloShop.IdentityService.Constants;
|
||||
using HelloShop.IdentityService.DataSeeding;
|
||||
using HelloShop.IdentityService.Entities;
|
||||
using HelloShop.IdentityService.EntityFrameworks;
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using HelloShop.ServiceDefaults.Extensions;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -54,6 +56,10 @@ builder.Services.AddAuthentication(options =>
|
||||
builder.Services.AddDataSeedingProviders();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddPermissionDefinitions();
|
||||
builder.Services.AddHttpClient().AddHttpContextAccessor().AddRemotePermissionChecker(options =>
|
||||
{
|
||||
options.ApiEndpoint = "https://localhost:5001/api/Permissions/PermissionList";
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public interface IPermissionChecker
|
||||
{
|
||||
Task<bool> IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null);
|
||||
|
||||
Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using HelloShop.ServiceDefaults.Constants;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor, IDistributedCache distributedCache) : IPermissionChecker
|
||||
{
|
||||
protected HttpContext HttpContext { get; init; } = httpContextAccessor.HttpContext ?? throw new InvalidOperationException();
|
||||
|
||||
public async Task<bool> IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null) => await IsGrantedAsync(HttpContext.User, name, resourceType, resourceId);
|
||||
|
||||
public async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
var roleIds = claimsPrincipal.FindAll(CustomClaimTypes.RoleIdentifier).Select(c => Convert.ToInt32(c.Value)).ToArray();
|
||||
|
||||
foreach (var roleId in roleIds)
|
||||
{
|
||||
var cacheKey = PermissionGrantCacheItem.CreateCacheKey(roleId, name, resourceType, resourceId);
|
||||
|
||||
if (distributedCache.TryGetValue(cacheKey, out PermissionGrantCacheItem? cacheItem) && cacheItem != null)
|
||||
{
|
||||
if (cacheItem.IsGranted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isGranted = await IsGrantedAsync(roleId, name, resourceType, resourceId);
|
||||
|
||||
await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions
|
||||
{
|
||||
SlidingExpiration = TimeSpan.FromMinutes(10)
|
||||
});
|
||||
|
||||
if (isGranted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract Task<bool> IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class PermissionGrantCacheItem(bool isGranted = false)
|
||||
{
|
||||
public bool IsGranted { get; set; } = isGranted;
|
||||
|
||||
public static string CreateCacheKey(int roleId, string name, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
string cacheKey = $"acl:role:{roleId}:{name}";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(resourceType))
|
||||
{
|
||||
cacheKey += $":{resourceType}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(resourceId))
|
||||
{
|
||||
cacheKey += $":{resourceId}";
|
||||
}
|
||||
|
||||
return cacheKey;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class PermissionGrantedResponse
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
|
||||
public string? ResourceType { get; set; }
|
||||
|
||||
public string? ResourceId { get; set; }
|
||||
|
||||
public bool IsGranted { get; set; }
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class RemotePermissionChecker(IHttpContextAccessor httpContextAccessor, IDistributedCache distributedCache, IHttpClientFactory httpClientFactory, IOptions<RemotePermissionCheckerOptions> options) : PermissionChecker(httpContextAccessor, distributedCache)
|
||||
{
|
||||
private readonly RemotePermissionCheckerOptions _remotePermissionCheckerOptions = options.Value;
|
||||
|
||||
public override async Task<bool> IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null)
|
||||
{
|
||||
string? accessToken = await HttpContext.GetTokenAsync("access_token");
|
||||
|
||||
HttpClient httpClient = httpClientFactory.CreateClient();
|
||||
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||
|
||||
httpClient.BaseAddress = new Uri(_remotePermissionCheckerOptions.ApiEndpoint);
|
||||
|
||||
Dictionary<string, string?> parameters = new()
|
||||
{
|
||||
{ nameof(roleId), roleId.ToString() },
|
||||
{ nameof(name), name },
|
||||
{ nameof(resourceType) , resourceType },
|
||||
{ nameof(resourceId), resourceId }
|
||||
};
|
||||
|
||||
string queryString = QueryHelpers.AddQueryString(string.Empty, parameters);
|
||||
|
||||
var permissionGrants = httpClient.GetFromJsonAsAsyncEnumerable<PermissionGrantedResponse>(queryString);
|
||||
|
||||
await foreach (var permissionGrant in permissionGrants)
|
||||
{
|
||||
if (permissionGrant != null && permissionGrant.IsGranted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace HelloShop.ServiceDefaults.Authorization;
|
||||
|
||||
public class RemotePermissionCheckerOptions
|
||||
{
|
||||
public string ApiEndpoint { get; set; } = default!;
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.Extensions.Caching.Distributed;
|
||||
|
||||
public static class CustomDistributedCacheExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets a string in the specified cache with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache in which to store the data.</param>
|
||||
/// <param name="key">The key to store the data in.</param>
|
||||
/// <param name="value">The data to store in the cache.</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
|
||||
public static void SetObject<TItem>(this IDistributedCache cache, string key, TItem value)
|
||||
{
|
||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
cache.Set(key, bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a string in the specified cache with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache in which to store the data.</param>
|
||||
/// <param name="key">The key to store the data in.</param>
|
||||
/// <param name="value">The data to store in the cache.</param>
|
||||
/// <param name="options">The cache options for the entry.</param>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
|
||||
public static void SetObject<TItem>(this IDistributedCache cache, string key, TItem value, DistributedCacheEntryOptions options)
|
||||
{
|
||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
cache.Set(key, bytes, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sets a string in the specified cache with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache in which to store the data.</param>
|
||||
/// <param name="key">The key to store the data in.</param>
|
||||
/// <param name="value">The data to store in the cache.</param>
|
||||
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous set operation.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
|
||||
public static Task SetObjectAsync<TItem>(this IDistributedCache cache, string key, TItem value, CancellationToken token = default)
|
||||
{
|
||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
return cache.SetAsync(key, bytes, new DistributedCacheEntryOptions(), token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously sets a string in the specified cache with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache in which to store the data.</param>
|
||||
/// <param name="key">The key to store the data in.</param>
|
||||
/// <param name="value">The data to store in the cache.</param>
|
||||
/// <param name="options">The cache options for the entry.</param>
|
||||
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous set operation.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
|
||||
public static Task SetObjectAsync<TItem>(this IDistributedCache cache, string key, TItem value, DistributedCacheEntryOptions options, CancellationToken token = default)
|
||||
{
|
||||
var bytes = JsonSerializer.SerializeToUtf8Bytes(value);
|
||||
return cache.SetAsync(key, bytes, options, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string from the specified cache with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache in which to store the data.</param>
|
||||
/// <param name="key">The key to get the stored data for.</param>
|
||||
/// <returns>The T value from the stored cache key.</returns>
|
||||
public static TItem? GetObject<TItem>(this IDistributedCache cache, string key)
|
||||
{
|
||||
byte[]? data = cache.Get(key);
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TItem>(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously gets a string from the specified cache with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache in which to store the data.</param>
|
||||
/// <param name="key">The key to get the stored data for.</param>
|
||||
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
|
||||
/// <returns>A task that gets the T value from the stored cache key.</returns>
|
||||
public static async Task<TItem?> GetObjectAsync<TItem>(this IDistributedCache cache, string key, CancellationToken token = default)
|
||||
{
|
||||
byte[]? data = await cache.GetAsync(key, token).ConfigureAwait(false);
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TItem>(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the value associated with the given key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the object to get.</typeparam>
|
||||
/// <param name="cache">The <see cref="IDistributedCache"/> instance this method extends.</param>
|
||||
/// <param name="key">The key of the value to get.</param>
|
||||
/// <param name="value">The value associated with the given key.</param>
|
||||
/// <returns><c>true</c> if the key was found. <c>false</c> otherwise.</returns>
|
||||
public static bool TryGetValue<TItem>(this IDistributedCache cache, string key, out TItem? value)
|
||||
{
|
||||
var data = cache.Get(key);
|
||||
|
||||
value = default;
|
||||
|
||||
try
|
||||
{
|
||||
value = JsonSerializer.Deserialize<TItem>(data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with this key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the object to get.</typeparam>
|
||||
/// <param name="cache">The <see cref="IDistributedCache"/> instance this method extends.</param>
|
||||
/// <param name="key">The key of the entry to look for or create.</param>
|
||||
/// <param name="factory">The factory that creates the value associated with this key if the key does not exist in the cache.</param>
|
||||
/// <param name="createOptions">The options to be applied to the <see cref="ICacheEntry"/> if the key does not exist in the cache.</param>
|
||||
/// <returns>The value associated with this key.</returns>
|
||||
public static TItem? GetOrCreate<TItem>(this IDistributedCache cache, string key, Func<DistributedCacheEntryOptions, TItem> factory, DistributedCacheEntryOptions? createOptions = null)
|
||||
{
|
||||
if (!cache.TryGetValue(key, out object? result))
|
||||
{
|
||||
createOptions ??= new DistributedCacheEntryOptions();
|
||||
|
||||
result = factory(createOptions);
|
||||
|
||||
cache.SetObject(key, result, createOptions);
|
||||
}
|
||||
|
||||
return (TItem?)result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously gets the value associated with this key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the object to get.</typeparam>
|
||||
/// <param name="cache">The <see cref="IDistributedCache"/> instance this method extends.</param>
|
||||
/// <param name="key">The key of the entry to look for or create.</param>
|
||||
/// <param name="factory">The factory task that creates the value associated with this key if the key does not exist in the cache.</param>
|
||||
/// <param name="createOptions">The options to be applied to the <see cref="ICacheEntry"/> if the key does not exist in the cache.</param>
|
||||
/// <returns>The task object representing the asynchronous operation.</returns>
|
||||
public static async Task<TItem?> GetOrCreateAsync<TItem>(this IDistributedCache cache, string key, Func<DistributedCacheEntryOptions, Task<TItem>> factory, DistributedCacheEntryOptions? createOptions = null)
|
||||
{
|
||||
if (!cache.TryGetValue(key, out object? result))
|
||||
{
|
||||
createOptions ??= new DistributedCacheEntryOptions();
|
||||
|
||||
result = await factory(createOptions).ConfigureAwait(false);
|
||||
|
||||
await cache.SetObjectAsync(key, result, createOptions);
|
||||
}
|
||||
|
||||
return (TItem?)result;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using HelloShop.ServiceDefaults.Permissions;
|
||||
using HelloShop.ServiceDefaults.Authorization;
|
||||
using HelloShop.ServiceDefaults.Permissions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
@ -62,4 +63,13 @@ public static class PermissionExtensions
|
||||
|
||||
return routeGroup;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddRemotePermissionChecker(this IServiceCollection services, Action<RemotePermissionCheckerOptions> configureOptions)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
|
||||
services.AddTransient<IPermissionChecker, RemotePermissionChecker>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user