From 7a186dcd64fc4641a794ee974450e1a56b02a89b Mon Sep 17 00:00:00 2001 From: rainxh11 Date: Fri, 5 Aug 2022 21:36:46 +0100 Subject: [PATCH] + API Cleanup, IBlackList Interface overhaul + Added EasyCaching + Stackexchange Redis BlackList Store fixed --- Revoke.NET.Akavache/AkavacheBlackList.cs | 103 +++++++++++ Revoke.NET.Akavache/AkavacheBlackListStore.cs | 129 -------------- Revoke.NET.Akavache/README.md | 4 - .../Revoke.NET.Akavache.csproj | 12 +- Revoke.NET.Akavache/RevokeService.cs | 6 +- .../Revoke.NET.AspNetCore.csproj | 10 +- Revoke.NET.AspNetCore/RevokeHttpMiddleware.cs | 10 +- Revoke.NET.AspNetCore/RevokeService.cs | 16 +- .../EasyCachingBlackList.cs | 94 ++++++++++ .../Revoke.NET.EasyCaching.csproj | 39 +++++ Revoke.NET.EasyCaching/RevokeService.cs | 43 +++++ Revoke.NET.MinimalAPIExample/Program.cs | 2 +- .../Revoke.NET.MinimalAPIExample.csproj | 10 +- Revoke.NET.MongoDB/MongoBlackList.cs | 131 ++++++++++++++ Revoke.NET.MongoDB/MongoBlackListStore.cs | 127 -------------- Revoke.NET.MongoDB/README.md | 4 - Revoke.NET.MongoDB/Revoke.NET.MongoDB.csproj | 7 +- Revoke.NET.MongoDB/RevokeService.cs | 4 +- Revoke.NET.MonkeyCache/Class1.cs | 9 - .../Revoke.NET.EasyCaching.csproj | 8 - Revoke.NET.Redis/LICENSE | 21 --- Revoke.NET.Redis/README.md | 4 - Revoke.NET.Redis/RedisBlackList.cs | 71 ++++++++ Revoke.NET.Redis/RedisBlackListStore.cs | 103 ----------- Revoke.NET.Redis/Revoke.NET.Redis.csproj | 19 +- Revoke.NET.Redis/RevokeService.cs | 4 +- Revoke.NET.Redis/assets/revoke.net.png | Bin 34256 -> 0 bytes Revoke.NET.sln | 2 +- .../{IBlackListStore.cs => IBlackList.cs} | 12 +- Revoke.NET/IBlackListItem.cs | 28 --- Revoke.NET/MemoryBlackList.cs | 163 ++++++++++++++++++ Revoke.NET/MemoryBlackListStore.cs | 121 ------------- Revoke.NET/README.md | 23 ++- Revoke.NET/Revoke.NET.csproj | 9 +- Revoke.NET/RevokeService.cs | 29 +++- Test/Program.cs | 8 +- Test/Test.csproj | 4 +- .../assets => assets}/revoke.net.png | Bin 38 files changed, 746 insertions(+), 643 deletions(-) create mode 100644 Revoke.NET.Akavache/AkavacheBlackList.cs delete mode 100644 Revoke.NET.Akavache/AkavacheBlackListStore.cs create mode 100644 Revoke.NET.EasyCaching/EasyCachingBlackList.cs create mode 100644 Revoke.NET.EasyCaching/Revoke.NET.EasyCaching.csproj create mode 100644 Revoke.NET.EasyCaching/RevokeService.cs create mode 100644 Revoke.NET.MongoDB/MongoBlackList.cs delete mode 100644 Revoke.NET.MongoDB/MongoBlackListStore.cs delete mode 100644 Revoke.NET.MonkeyCache/Class1.cs delete mode 100644 Revoke.NET.MonkeyCache/Revoke.NET.EasyCaching.csproj delete mode 100644 Revoke.NET.Redis/LICENSE create mode 100644 Revoke.NET.Redis/RedisBlackList.cs delete mode 100644 Revoke.NET.Redis/RedisBlackListStore.cs delete mode 100644 Revoke.NET.Redis/assets/revoke.net.png rename Revoke.NET/{IBlackListStore.cs => IBlackList.cs} (51%) delete mode 100644 Revoke.NET/IBlackListItem.cs create mode 100644 Revoke.NET/MemoryBlackList.cs delete mode 100644 Revoke.NET/MemoryBlackListStore.cs rename {Revoke.NET.Akavache/assets => assets}/revoke.net.png (100%) diff --git a/Revoke.NET.Akavache/AkavacheBlackList.cs b/Revoke.NET.Akavache/AkavacheBlackList.cs new file mode 100644 index 0000000..0bb827c --- /dev/null +++ b/Revoke.NET.Akavache/AkavacheBlackList.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Akavache; +using Registrations = Akavache.Registrations; + +namespace Revoke.NET.Akavache +{ + public class AkavacheBlackList : IBlackList + { + private readonly IBlobCache _blackList; + private static TimeSpan? _defaultTtl; + + private AkavacheBlackList(IBlobCache blobcache) + { + this._blackList = blobcache; + } + + public static async Task CreateStoreAsync(string cacheName, IBlobCache blobCache, + TimeSpan? defaultTtl = null) + { + _defaultTtl = defaultTtl; + Registrations.Start(cacheName); + await blobCache.Vacuum(); + return new AkavacheBlackList(blobCache); + } + + + public async Task Delete(string key) + { + try + { + await _blackList.Invalidate(key); + return true; + } + catch + { + return false; + } + } + + public async Task DeleteAll() + { + await _blackList.InvalidateAll(); + } + + public async Task IsRevoked(string key) + { + try + { + await _blackList.Vacuum(); + var exist = await _blackList.Get(key); + return exist.Length > 0; + } + catch + { + return false; + } + } + + public async Task Revoke(string key) + { + try + { + await _blackList.InsertObject(key, key, DateTimeOffset.Now.Add(_defaultTtl ?? TimeSpan.MaxValue)); + return true; + } + catch + { + return false; + } + } + + public async Task Revoke(string key, TimeSpan expireAfter) + { + try + { + await _blackList.InsertObject(key, key, DateTimeOffset.Now.Add(expireAfter)); + + return true; + } + catch + { + return false; + } + } + + public async Task Revoke(string key, DateTime expireOn) + { + try + { + await _blackList.InsertObject(key, key, expireOn); + + return true; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/Revoke.NET.Akavache/AkavacheBlackListStore.cs b/Revoke.NET.Akavache/AkavacheBlackListStore.cs deleted file mode 100644 index 2ebd0a0..0000000 --- a/Revoke.NET.Akavache/AkavacheBlackListStore.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Akavache; -using Registrations = Akavache.Registrations; - -namespace Revoke.NET.Akavache -{ - public class AkavacheBlackListStore : IBlackListStore - { - private readonly IBlobCache blacklist; - - private AkavacheBlackListStore(IBlobCache blobcache) - { - this.blacklist = blobcache; - } - - public static async Task CreateStoreAsync(string cacheName, IBlobCache blobCache) - { - Registrations.Start(cacheName); - await blobCache.Vacuum(); - return new AkavacheBlackListStore(blobCache); - } - - public async Task Delete(string key) - { - try - { - await blacklist.Invalidate(key); - return true; - } - catch - { - return false; - } - } - - public async Task DeleteAll() - { - await blacklist.InvalidateAll(); - } - - - public async Task DeleteExpired() - { - await blacklist.Vacuum(); - ; - } - - public async Task Get(string key) where T : IBlackListItem - { - return await blacklist.GetObject(key); - } - - public async Task> GetAll() where T : IBlackListItem - { - return await blacklist.GetAllObjects(); - } - - public async Task IsRevoked(string key) - { - try - { - await blacklist.Vacuum(); - var exist = await blacklist.Get(key); - return exist.Length > 0; - } - catch - { - return false; - } - } - - public async Task Revoke(string key) - { - try - { - await blacklist.InsertObject(key, new BlackListItem(key, DateTimeOffset.MaxValue), - DateTimeOffset.MaxValue); - return true; - } - catch - { - return false; - } - } - - public async Task Revoke(string key, TimeSpan expireAfter) - { - try - { - await blacklist.InsertObject(key, new BlackListItem(key, DateTimeOffset.Now.Add(expireAfter)), - expireAfter); - return true; - } - catch - { - return false; - } - } - - public async Task Revoke(string key, DateTimeOffset expireOn) - { - try - { - await blacklist.InsertObject(key, new BlackListItem(key, expireOn), expireOn); - return true; - } - catch - { - return false; - } - } - - public async Task Revoke(T item) where T : IBlackListItem - { - try - { - await blacklist.InsertObject(item.Key, item, item.ExpireOn); - return true; - } - catch - { - return false; - } - } - } -} \ No newline at end of file diff --git a/Revoke.NET.Akavache/README.md b/Revoke.NET.Akavache/README.md index 9948c49..8c4c345 100644 --- a/Revoke.NET.Akavache/README.md +++ b/Revoke.NET.Akavache/README.md @@ -21,10 +21,6 @@ var key = "[ID String of something to be blacklisted]"; await store.Revoke(key, TimeSpan.FromHours(24)); // Revoke access to a key for 24 hours -await store.Revoke(model); // Revoke an item with custom type - -var item = store.Get(key); // Retrieve a blacklisted item, SomeType must implement interface 'IBlackListItem' - await store.IsRevoked(key); // Check if key is blacklisted await store.Delete(key); // Delete a key from blacklist diff --git a/Revoke.NET.Akavache/Revoke.NET.Akavache.csproj b/Revoke.NET.Akavache/Revoke.NET.Akavache.csproj index e33b8cc..2792eaf 100644 --- a/Revoke.NET.Akavache/Revoke.NET.Akavache.csproj +++ b/Revoke.NET.Akavache/Revoke.NET.Akavache.csproj @@ -7,7 +7,7 @@ readme.md LICENSE Library - 1.0.6 + 2.0.0 Chakhoum Ahmed (github.com/rainxh11) Revoke.NET Akavache Store Extension @@ -24,12 +24,12 @@ - + - - - - + + + + diff --git a/Revoke.NET.Akavache/RevokeService.cs b/Revoke.NET.Akavache/RevokeService.cs index 8186edf..1453872 100644 --- a/Revoke.NET.Akavache/RevokeService.cs +++ b/Revoke.NET.Akavache/RevokeService.cs @@ -11,7 +11,7 @@ public static class RevokeService public static IServiceCollection AddRevokeAkavacheSQLiteStore(this IServiceCollection services) { return services - .AddSingleton(provider => AkavacheBlackListStore + .AddSingleton(provider => AkavacheBlackList .CreateStoreAsync("RevokeStore", BlobCache.LocalMachine) .GetAwaiter() .GetResult()); @@ -20,7 +20,7 @@ public static IServiceCollection AddRevokeAkavacheSQLiteStore(this IServiceColle public static IServiceCollection AddRevokeAkavacheInMemoryStore(this IServiceCollection services) { return services - .AddSingleton(provider => AkavacheBlackListStore + .AddSingleton(provider => AkavacheBlackList .CreateStoreAsync("RevokeStore", BlobCache.InMemory) .GetAwaiter() .GetResult()); @@ -30,7 +30,7 @@ public static IServiceCollection AddRevokeAkavacheStore(this IServiceCollection Func configBlobCache) { return services - .AddSingleton(provider => AkavacheBlackListStore + .AddSingleton(provider => AkavacheBlackList .CreateStoreAsync("RevokeStore", configBlobCache(provider)) .GetAwaiter() .GetResult()); diff --git a/Revoke.NET.AspNetCore/Revoke.NET.AspNetCore.csproj b/Revoke.NET.AspNetCore/Revoke.NET.AspNetCore.csproj index 6bca796..f43ebe7 100644 --- a/Revoke.NET.AspNetCore/Revoke.NET.AspNetCore.csproj +++ b/Revoke.NET.AspNetCore/Revoke.NET.AspNetCore.csproj @@ -7,7 +7,7 @@ readme.md LICENSE Library - 1.0.7 + 2.0.0 Chakhoum Ahmed (github.com/rainxh11) Revoke.NET ASP.NET Core Extension @@ -27,10 +27,10 @@ - - - - + + + + diff --git a/Revoke.NET.AspNetCore/RevokeHttpMiddleware.cs b/Revoke.NET.AspNetCore/RevokeHttpMiddleware.cs index 036ac79..d858d4f 100644 --- a/Revoke.NET.AspNetCore/RevokeHttpMiddleware.cs +++ b/Revoke.NET.AspNetCore/RevokeHttpMiddleware.cs @@ -11,7 +11,7 @@ namespace Revoke.NET.AspNetCore { public class RevokeHttpMiddleware : IMiddleware { - private readonly IBlackListStore store; + private readonly IBlackList store; private readonly Func selector; #nullable enable @@ -19,7 +19,7 @@ public class RevokeHttpMiddleware : IMiddleware private Func>? responseFunc; #nullable disable - public RevokeHttpMiddleware(IBlackListStore store, ILogger logger, + public RevokeHttpMiddleware(IBlackList store, ILogger logger, Func selector) { this.store = store; @@ -27,13 +27,13 @@ public RevokeHttpMiddleware(IBlackListStore store, ILogger this.selector = selector; } - public RevokeHttpMiddleware(IBlackListStore store, Func selector) + public RevokeHttpMiddleware(IBlackList store, Func selector) { this.store = store; this.selector = selector; } - public RevokeHttpMiddleware(IBlackListStore store, ILogger logger, + public RevokeHttpMiddleware(IBlackList store, ILogger logger, Func selector, Func> responseFunc) { this.store = store; @@ -42,7 +42,7 @@ public RevokeHttpMiddleware(IBlackListStore store, ILogger this.responseFunc = responseFunc; } - public RevokeHttpMiddleware(IBlackListStore store, Func selector, + public RevokeHttpMiddleware(IBlackList store, Func selector, Func> responseFunc) { this.store = store; diff --git a/Revoke.NET.AspNetCore/RevokeService.cs b/Revoke.NET.AspNetCore/RevokeService.cs index 9012fb0..de743f5 100644 --- a/Revoke.NET.AspNetCore/RevokeService.cs +++ b/Revoke.NET.AspNetCore/RevokeService.cs @@ -18,7 +18,7 @@ public static IServiceCollection AddHttpContextRevokeMiddleware(this IServiceCol return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, selector); }); @@ -37,7 +37,7 @@ public static IServiceCollection AddHttpContextRevokeMiddleware(this IServiceCol return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, selector, responseFunc); }); @@ -64,7 +64,7 @@ public static IServiceCollection AddJWTBearerTokenRevokeMiddleware(this IService return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, bearerTokenSelector); }); @@ -93,7 +93,7 @@ public static IServiceCollection AddJWTBearerTokenRevokeMiddleware(this IService return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, bearerTokenSelector, responseFunc); }); @@ -111,7 +111,7 @@ public static IServiceCollection AddIPRevokeMiddleware(this IServiceCollection s return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, ipSelector); }); @@ -131,7 +131,7 @@ public static IServiceCollection AddIPRevokeMiddleware(this IServiceCollection s return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, ipSelector, responseFunc); }); @@ -149,7 +149,7 @@ public static IServiceCollection AddUserIdRevokeMiddleware(this IServiceCollecti return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, ipSelector); }); @@ -169,7 +169,7 @@ public static IServiceCollection AddUserIdRevokeMiddleware(this IServiceCollecti return services .AddSingleton(provider => { - var store = provider.GetService(); + var store = provider.GetService(); var logger = provider.GetService>(); return new RevokeHttpMiddleware(store, logger, ipSelector, responseFunc); }); diff --git a/Revoke.NET.EasyCaching/EasyCachingBlackList.cs b/Revoke.NET.EasyCaching/EasyCachingBlackList.cs new file mode 100644 index 0000000..bb07317 --- /dev/null +++ b/Revoke.NET.EasyCaching/EasyCachingBlackList.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using EasyCaching.Core; + +namespace Revoke.NET.EasyCaching; + +internal class EasyCachingBlackList : IBlackList +{ + private readonly IEasyCachingProvider _easyCaching; + private readonly TimeSpan? _defaultTtl; + + public EasyCachingBlackList(IEasyCachingProvider easyCaching, TimeSpan? defaultTtl = null) + { + _defaultTtl = defaultTtl; + _easyCaching = easyCaching; + } + + public async Task Revoke(string key, TimeSpan expireAfter) + { + try + { + await _easyCaching.SetAsync(key, key, expireAfter); + return true; + } + catch + { + return false; + } + } + + public async Task Revoke(string key, DateTime expireOn) + { + try + { + await _easyCaching.SetAsync(key, key, expireOn - DateTime.Now); + return true; + } + catch + { + return false; + } + } + + public async Task Revoke(string key) + { + try + { + await _easyCaching.SetAsync(key, key, _defaultTtl ?? TimeSpan.MaxValue); + return true; + } + catch + { + return false; + } + } + + public async Task Delete(string key) + { + try + { + await _easyCaching.RemoveAsync(key); + return true; + } + catch + { + return false; + } + } + + public async Task DeleteAll() + { + try + { + await _easyCaching.FlushAsync(); + } + catch + { + } + } + + public async Task IsRevoked(string key) + { + try + { + return await _easyCaching.ExistsAsync(key); + } + catch + { + return false; + } + } +} \ No newline at end of file diff --git a/Revoke.NET.EasyCaching/Revoke.NET.EasyCaching.csproj b/Revoke.NET.EasyCaching/Revoke.NET.EasyCaching.csproj new file mode 100644 index 0000000..db04b9e --- /dev/null +++ b/Revoke.NET.EasyCaching/Revoke.NET.EasyCaching.csproj @@ -0,0 +1,39 @@ + + + + netstandard2.0 + 10.0 + revoke.net.png + LICENSE + Library + 2.0.0 + + Chakhoum Ahmed (github.com/rainxh11) + Revoke.NET EasyCaching Store Extension + © 2022 Chakhoum Ahmed + LICENSE + https://github.com/rainxh11/Revoke.NET + https://github.com/rainxh11/Revoke.NET/tree/main/Revoke.NET.EasyCaching + github + true + revoke;easycaching;permission;deny;blacklist;expiration;invalidate;store + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + False + True + + + + + + + + + + + + + + + + diff --git a/Revoke.NET.EasyCaching/RevokeService.cs b/Revoke.NET.EasyCaching/RevokeService.cs new file mode 100644 index 0000000..a0f5256 --- /dev/null +++ b/Revoke.NET.EasyCaching/RevokeService.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; +using EasyCaching.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Revoke.NET.EasyCaching; + +namespace Revoke.NET.EasyCaching +{ + public static class RevokeService + { + public static IServiceCollection AddRevokeEasyCaching(this IServiceCollection services, + IEasyCachingProvider easyCachingProvider, TimeSpan? defaultTtl = null) + { + return services + .AddSingleton(provider => + new EasyCachingBlackList(easyCachingProvider, defaultTtl)); + } + + public static IServiceCollection AddRevokeEasyCaching(this IServiceCollection services, + Func easyCachingConfig, TimeSpan? defaultTtl = null) + { + return services + .AddSingleton(provider => + { + var factory = provider.GetService(); + return new EasyCachingBlackList(easyCachingConfig?.Invoke(factory), defaultTtl); + }); + } + + public static IServiceCollection AddRevokeEasyCaching(this IServiceCollection services, + TimeSpan? defaultTtl = null) + { + return services + .AddSingleton(provider => + { + var easyCachingProvider = provider.GetService(); + return new EasyCachingBlackList(easyCachingProvider, defaultTtl); + }); + } + } +} \ No newline at end of file diff --git a/Revoke.NET.MinimalAPIExample/Program.cs b/Revoke.NET.MinimalAPIExample/Program.cs index a01d5f5..635b70d 100644 --- a/Revoke.NET.MinimalAPIExample/Program.cs +++ b/Revoke.NET.MinimalAPIExample/Program.cs @@ -16,7 +16,7 @@ app.UseAuthorization(); app.UseAuthentication(); -app.MapGet("/logout", async ([FromServices] IBlackListStore store, HttpRequest request) => +app.MapGet("/logout", async ([FromServices] IBlackList store, HttpRequest request) => { var token = AuthenticationHeaderValue.Parse(request.Headers.Authorization).Parameter; diff --git a/Revoke.NET.MinimalAPIExample/Revoke.NET.MinimalAPIExample.csproj b/Revoke.NET.MinimalAPIExample/Revoke.NET.MinimalAPIExample.csproj index fcf810f..38b9831 100644 --- a/Revoke.NET.MinimalAPIExample/Revoke.NET.MinimalAPIExample.csproj +++ b/Revoke.NET.MinimalAPIExample/Revoke.NET.MinimalAPIExample.csproj @@ -9,13 +9,9 @@ - - - - - - - + + + diff --git a/Revoke.NET.MongoDB/MongoBlackList.cs b/Revoke.NET.MongoDB/MongoBlackList.cs new file mode 100644 index 0000000..9c46c40 --- /dev/null +++ b/Revoke.NET.MongoDB/MongoBlackList.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using Revoke.NET; + +namespace Revoke.NET.MongoDB +{ + public class MongoBlackListItem + { + [BsonId] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); + + public MongoBlackListItem(string key, DateTime expireOn) + { + Key = key; + ExpireOn = expireOn; + } + + public string Key { get; set; } + public DateTime ExpireOn { get; set; } + } + + public class MongoBlackList : IBlackList + { + private readonly IMongoCollection _blacklist; + + private MongoBlackList(IMongoCollection blacklist) + { + this._blacklist = blacklist; + } + + public static async Task CreateStoreAsync(string dbName, + MongoClientSettings clientSettings) + { + var client = new MongoClient(clientSettings); + + var db = client.GetDatabase(dbName); + + var keyIndex = Builders.IndexKeys.Ascending(x => x.Key); + var ttlIndex = Builders.IndexKeys.Ascending(x => x.ExpireOn); + + var collection = db.GetCollection(nameof(MongoBlackListItem)); + + await collection.Indexes.CreateOneAsync( + new CreateIndexModel(keyIndex, new CreateIndexOptions() { Unique = true })); + await collection.Indexes.CreateOneAsync( + new CreateIndexModel(ttlIndex, + new CreateIndexOptions() { ExpireAfter = TimeSpan.FromMinutes(1) })); + + return new MongoBlackList(collection); + } + + public async Task Revoke(string key, TimeSpan expireAfter) + { + try + { + await _blacklist.InsertOneAsync(new MongoBlackListItem(key, DateTime.Now.Add(expireAfter))); + return true; + } + catch + { + return false; + } + } + + public async Task Revoke(string key, DateTime expireOn) + { + try + { + await _blacklist.InsertOneAsync(new MongoBlackListItem(key, expireOn)); + return true; + } + catch + { + return false; + } + } + + public async Task Revoke(string key) + { + try + { + await _blacklist.InsertOneAsync(new MongoBlackListItem(key, DateTime.MaxValue)); + return true; + } + catch + { + return false; + } + } + + public async Task Delete(string key) + { + try + { + var delete = await _blacklist.DeleteOneAsync(x => x.Key == key); + return delete.IsAcknowledged; + } + catch + { + return false; + } + } + + public async Task DeleteAll() + { + try + { + await _blacklist.DeleteManyAsync(x => true); + } + catch + { + } + } + + public async Task IsRevoked(string key) + { + try + { + var item = await _blacklist.Find(x => x.Key == key).SingleAsync(); + return item.ExpireOn > DateTime.Now; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/Revoke.NET.MongoDB/MongoBlackListStore.cs b/Revoke.NET.MongoDB/MongoBlackListStore.cs deleted file mode 100644 index a283a1c..0000000 --- a/Revoke.NET.MongoDB/MongoBlackListStore.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MongoDB.Driver; -using Revoke.NET; - -namespace Revoke.NET.MongoDB -{ - public class MongoBlackListStore : IBlackListStore - { - private readonly IMongoCollection blacklist; - - private MongoBlackListStore(IMongoCollection blacklist) - { - this.blacklist = blacklist; - } - - public static async Task CreateStoreAsync(string dbName, - MongoClientSettings clientSettings) - { - var client = new MongoClient(clientSettings); - - var db = client.GetDatabase(dbName); - - var keyIndex = Builders.IndexKeys.Ascending(x => x.Key); - var ttlIndex = Builders.IndexKeys.Ascending(x => x.ExpireOn); - - var collection = db.GetCollection(nameof(BlackListItem)); - - await collection.Indexes.CreateOneAsync( - new CreateIndexModel(keyIndex, new CreateIndexOptions() { Unique = true })); - await collection.Indexes.CreateOneAsync( - new CreateIndexModel(ttlIndex, - new CreateIndexOptions() { ExpireAfter = TimeSpan.FromMinutes(1) })); - - return new MongoBlackListStore(collection); - } - - public async Task Delete(string key) - { - var result = await blacklist.DeleteOneAsync(x => x.Key == key); - return result.IsAcknowledged; - } - - public async Task DeleteAll() - { - await blacklist.DeleteManyAsync(x => true); - } - - - public async Task DeleteExpired() - { - await blacklist.DeleteManyAsync(x => x.ExpireOn < DateTimeOffset.Now); - } - - public async Task Get(string key) where T : IBlackListItem - { - return await blacklist.Database.GetCollection(nameof(IBlackListItem)).Find(x => x.Key == key) - .FirstAsync(); - } - - public async Task> GetAll() where T : IBlackListItem - { - return await blacklist.Database.GetCollection(nameof(IBlackListItem)).Find(x => true).ToListAsync(); - } - - public async Task IsRevoked(string key) - { - var result = await blacklist.Find(x => x.Key == key).CountDocumentsAsync(); - return result > 0; - } - - public async Task Revoke(string key) - { - try - { - await blacklist.InsertOneAsync(new BlackListItem(key, DateTimeOffset.MaxValue)); - return true; - } - catch - { - return false; - } - } - - public async Task Revoke(string key, TimeSpan expireAfter) - { - try - { - await blacklist.InsertOneAsync(new BlackListItem(key, DateTimeOffset.Now.Add(expireAfter))); - return true; - } - catch - { - return false; - } - } - - public async Task Revoke(string key, DateTimeOffset expireOn) - { - try - { - await blacklist.InsertOneAsync(new BlackListItem(key, expireOn)); - return true; - } - catch - { - return false; - } - } - - public async Task Revoke(T item) where T : IBlackListItem - { - try - { - await blacklist.Database - .GetCollection(nameof(IBlackListItem)) - .InsertOneAsync(item); - return true; - } - catch - { - return false; - } - } - } -} \ No newline at end of file diff --git a/Revoke.NET.MongoDB/README.md b/Revoke.NET.MongoDB/README.md index 989d884..e9a3041 100644 --- a/Revoke.NET.MongoDB/README.md +++ b/Revoke.NET.MongoDB/README.md @@ -20,10 +20,6 @@ var key = "[ID String of something to be blacklisted]"; await store.Revoke(key, TimeSpan.FromHours(24)); // Revoke access to a key for 24 hours -await store.Revoke(model); // Revoke an item with custom type - -var item = store.Get(key); // Retrieve a blacklisted item, SomeType must implement interface 'IBlackListItem' - await store.IsRevoked(key); // Check if key is blacklisted await store.Delete(key); // Delete a key from blacklist diff --git a/Revoke.NET.MongoDB/Revoke.NET.MongoDB.csproj b/Revoke.NET.MongoDB/Revoke.NET.MongoDB.csproj index 219fc6c..df0763d 100644 --- a/Revoke.NET.MongoDB/Revoke.NET.MongoDB.csproj +++ b/Revoke.NET.MongoDB/Revoke.NET.MongoDB.csproj @@ -7,7 +7,7 @@ readme.md LICENSE Library - 1.0.7 + 2.0.0 Chakhoum Ahmed (github.com/rainxh11) Revoke.NET MongoDB Store Extension @@ -26,8 +26,9 @@ - - + + + diff --git a/Revoke.NET.MongoDB/RevokeService.cs b/Revoke.NET.MongoDB/RevokeService.cs index 2584f3b..8330fc7 100644 --- a/Revoke.NET.MongoDB/RevokeService.cs +++ b/Revoke.NET.MongoDB/RevokeService.cs @@ -11,7 +11,7 @@ public static class RevokeService public static IServiceCollection AddRevokeMongoStore(this IServiceCollection services) { return services - .AddSingleton(provider => MongoBlackListStore.CreateStoreAsync( + .AddSingleton(provider => MongoBlackList.CreateStoreAsync( "RevokeStore", MongoClientSettings.FromConnectionString("mongodb://127.0.0.1:27017/RevokeStore")) .GetAwaiter() @@ -22,7 +22,7 @@ public static IServiceCollection AddRevokeMongoStore(this IServiceCollection ser MongoClientSettings settings) { return services - .AddSingleton(provider => MongoBlackListStore.CreateStoreAsync( + .AddSingleton(provider => MongoBlackList.CreateStoreAsync( dbName, settings) .GetAwaiter() diff --git a/Revoke.NET.MonkeyCache/Class1.cs b/Revoke.NET.MonkeyCache/Class1.cs deleted file mode 100644 index 5f0b6fa..0000000 --- a/Revoke.NET.MonkeyCache/Class1.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Revoke.NET.MonkeyCache -{ - public class Class1 - { - - } -} diff --git a/Revoke.NET.MonkeyCache/Revoke.NET.EasyCaching.csproj b/Revoke.NET.MonkeyCache/Revoke.NET.EasyCaching.csproj deleted file mode 100644 index 6c58211..0000000 --- a/Revoke.NET.MonkeyCache/Revoke.NET.EasyCaching.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - netstandard2.0 - True - - - diff --git a/Revoke.NET.Redis/LICENSE b/Revoke.NET.Redis/LICENSE deleted file mode 100644 index 52130b0..0000000 --- a/Revoke.NET.Redis/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022-present, CHAKHOUM AHMED - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Revoke.NET.Redis/README.md b/Revoke.NET.Redis/README.md index 92df46b..374dcf9 100644 --- a/Revoke.NET.Redis/README.md +++ b/Revoke.NET.Redis/README.md @@ -18,10 +18,6 @@ var key = "[ID String of something to be blacklisted]"; await store.Revoke(key, TimeSpan.FromHours(24)); // Revoke access to a key for 24 hours -await store.Revoke(model); // Revoke an item with custom type - -var item = store.Get(key); // Retrieve a blacklisted item, SomeType must implement interface 'IBlackListItem' - await store.IsRevoked(key); // Check if key is blacklisted await store.Delete(key); // Delete a key from blacklist diff --git a/Revoke.NET.Redis/RedisBlackList.cs b/Revoke.NET.Redis/RedisBlackList.cs new file mode 100644 index 0000000..9f5e5e8 --- /dev/null +++ b/Revoke.NET.Redis/RedisBlackList.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using StackExchange.Redis; +using System.Text.Json; + +namespace Revoke.NET.Redis +{ + public class RedisBlackList : IBlackList + { + private IDatabase _blackList; + private IEnumerable servers; + private static TimeSpan? _defaultTtl; + + private RedisBlackList(IDatabase blackList, IEnumerable servers) + { + this._blackList = blackList; + this.servers = servers; + } + + public static async Task CreateStoreAsync(string connectionString, TimeSpan? defaultTtl = null) + { + _defaultTtl = defaultTtl; + var options = ConfigurationOptions.Parse(connectionString); + options.AllowAdmin = true; + var redis = await ConnectionMultiplexer.ConnectAsync(options); + var blacklist = redis.GetDatabase(); + var servers = redis.GetEndPoints().Select(x => redis.GetServer(x)); + + return new RedisBlackList(blacklist, servers); + } + + public async Task Delete(string key) + { + return await _blackList.KeyDeleteAsync(key); + } + + public async Task DeleteAll() + { + foreach (var key in servers.SelectMany(x => x.Keys())) + { + await _blackList.KeyDeleteAsync(key); + } + } + + public async Task IsRevoked(string key) + { + var value = await _blackList.StringGetAsync(key); + return !value.HasValue; + } + + public async Task Revoke(string key) + { + var value = await _blackList.StringSetAndGetAsync(key, key, _defaultTtl ?? TimeSpan.MaxValue); + return value.HasValue; + } + + public async Task Revoke(string key, TimeSpan expireAfter) + { + var value = await _blackList.StringSetAndGetAsync(key, key, expireAfter); + return value.HasValue; + } + + public async Task Revoke(string key, DateTime expireOn) + { + var value = await _blackList.StringSetAndGetAsync(key, key, expireOn - DateTimeOffset.Now); + return value.HasValue; + } + } +} \ No newline at end of file diff --git a/Revoke.NET.Redis/RedisBlackListStore.cs b/Revoke.NET.Redis/RedisBlackListStore.cs deleted file mode 100644 index 1c54e64..0000000 --- a/Revoke.NET.Redis/RedisBlackListStore.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using StackExchange.Redis; -using System.Text.Json; - -namespace Revoke.NET.Redis -{ - public class RedisBlackListStore : IBlackListStore - { - private IDatabase blacklist; - private IEnumerable servers; - - private RedisBlackListStore(IDatabase blacklist, IEnumerable servers) - { - this.blacklist = blacklist; - this.servers = servers; - } - - public static async Task CreateStoreAsync(string connectionString) - { - var options = ConfigurationOptions.Parse(connectionString); - options.AllowAdmin = true; - var redis = await ConnectionMultiplexer.ConnectAsync(options); - var blacklist = redis.GetDatabase(); - var servers = redis.GetEndPoints().Select(x => redis.GetServer(x)); - - return new RedisBlackListStore(blacklist, servers); - } - - public async Task Delete(string key) - { - return await blacklist.KeyDeleteAsync(key); - } - - public async Task DeleteAll() - { - foreach (var key in servers.SelectMany(x => x.Keys())) - { - await blacklist.KeyDeleteAsync(key); - } - } - - - public Task DeleteExpired() - { - return Task.CompletedTask; - } - - public async Task Get(string key) where T : IBlackListItem - { - var value = await blacklist.StringGetAsync(key); - return JsonSerializer.Deserialize((string)value.Box()); - } - - public async Task> GetAll() where T : IBlackListItem - { - var temp = new List(); - foreach (var key in servers.SelectMany(x => x.Keys())) - { - var value = await blacklist.StringGetAsync(key); - temp.Add(JsonSerializer.Deserialize((string)value.Box())); - } - - return temp; - } - - public async Task IsRevoked(string key) - { - var value = await blacklist.StringGetAsync(key); - return !value.HasValue; - } - - public async Task Revoke(string key) - { - var value = await blacklist.StringSetAndGetAsync(key, - JsonSerializer.Serialize(new BlackListItem(key, DateTimeOffset.MaxValue))); - return value.HasValue; - } - - public async Task Revoke(string key, TimeSpan expireAfter) - { - var value = await blacklist.StringSetAndGetAsync(key, - JsonSerializer.Serialize(new BlackListItem(key, DateTimeOffset.MaxValue)), expireAfter); - return value.HasValue; - } - - public async Task Revoke(string key, DateTimeOffset expireOn) - { - var value = await blacklist.StringSetAndGetAsync(key, - JsonSerializer.Serialize(new BlackListItem(key, DateTimeOffset.MaxValue)), - expireOn - DateTimeOffset.Now); - return value.HasValue; - } - - public async Task Revoke(T item) where T : IBlackListItem - { - var value = await blacklist.StringSetAndGetAsync(item.Key, JsonSerializer.Serialize(item)); - return value.HasValue; - } - } -} \ No newline at end of file diff --git a/Revoke.NET.Redis/Revoke.NET.Redis.csproj b/Revoke.NET.Redis/Revoke.NET.Redis.csproj index 5c858a7..aca0ca2 100644 --- a/Revoke.NET.Redis/Revoke.NET.Redis.csproj +++ b/Revoke.NET.Redis/Revoke.NET.Redis.csproj @@ -7,7 +7,7 @@ readme.md LICENSE Library - 1.0.6 + 2.0.0 Chakhoum Ahmed (github.com/rainxh11) Revoke.NET Redis Store Extension @@ -23,15 +23,16 @@ - + - - - - - - - + + + + + + + + diff --git a/Revoke.NET.Redis/RevokeService.cs b/Revoke.NET.Redis/RevokeService.cs index 574720a..4238429 100644 --- a/Revoke.NET.Redis/RevokeService.cs +++ b/Revoke.NET.Redis/RevokeService.cs @@ -7,7 +7,7 @@ public static class RevokeService public static IServiceCollection AddRevokeRedisStore(this IServiceCollection services) { return services - .AddSingleton(provider => RedisBlackListStore.CreateStoreAsync("127.0.0.1:6379") + .AddSingleton(provider => RedisBlackList.CreateStoreAsync("127.0.0.1:6379") .GetAwaiter() .GetResult()); } @@ -15,7 +15,7 @@ public static IServiceCollection AddRevokeRedisStore(this IServiceCollection ser public static IServiceCollection AddRevokeRedisStore(this IServiceCollection services, string connectionString) { return services - .AddSingleton(provider => RedisBlackListStore.CreateStoreAsync(connectionString) + .AddSingleton(provider => RedisBlackList.CreateStoreAsync(connectionString) .GetAwaiter() .GetResult()); } diff --git a/Revoke.NET.Redis/assets/revoke.net.png b/Revoke.NET.Redis/assets/revoke.net.png deleted file mode 100644 index 16774c1b9570c876168aa428631a2e9d1618fdb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34256 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 diff --git a/Revoke.NET.sln b/Revoke.NET.sln index 2e91123..b4e126f 100644 --- a/Revoke.NET.sln +++ b/Revoke.NET.sln @@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Revoke.NET.MongoDB", "Revok EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Revoke.NET.Akavache", "Revoke.NET.Akavache\Revoke.NET.Akavache.csproj", "{E3CECBB3-985E-40B1-A5B2-E312463A3A72}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Revoke.NET.EasyCaching", "Revoke.NET.MonkeyCache\Revoke.NET.EasyCaching.csproj", "{01B7F2DA-A733-4025-8B38-4C8E698D4C92}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Revoke.NET.EasyCaching", "Revoke.NET.EasyCaching\Revoke.NET.EasyCaching.csproj", "{01B7F2DA-A733-4025-8B38-4C8E698D4C92}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Revoke.NET.Redis", "Revoke.NET.Redis\Revoke.NET.Redis.csproj", "{A5F3BC02-9E8F-444F-BDF8-27C3815C2362}" EndProject diff --git a/Revoke.NET/IBlackListStore.cs b/Revoke.NET/IBlackList.cs similarity index 51% rename from Revoke.NET/IBlackListStore.cs rename to Revoke.NET/IBlackList.cs index 0436529..aad12cf 100644 --- a/Revoke.NET/IBlackListStore.cs +++ b/Revoke.NET/IBlackList.cs @@ -5,16 +5,12 @@ namespace Revoke.NET { - public interface IBlackListStore + public interface IBlackList { - Task> GetAll() where T : IBlackListItem; - Task Get(string key) where T : IBlackListItem; - Task Delete(string key); - Task Revoke(string key); Task Revoke(string key, TimeSpan expireAfter); - Task Revoke(string key, DateTimeOffset expireOn); - Task Revoke(T item) where T : IBlackListItem; - Task DeleteExpired(); + Task Revoke(string key, DateTime expireOn); + Task Revoke(string key); + Task Delete(string key); Task DeleteAll(); Task IsRevoked(string key); } diff --git a/Revoke.NET/IBlackListItem.cs b/Revoke.NET/IBlackListItem.cs deleted file mode 100644 index 750aff2..0000000 --- a/Revoke.NET/IBlackListItem.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Revoke.NET -{ - public class BlackListItem : IBlackListItem - { - public BlackListItem(string key, DateTimeOffset expireOn) - { - this.Key = key; - this.ExpireOn = expireOn; - } - - public BlackListItem(string key, TimeSpan expireAfter) - { - this.Key = key; - this.ExpireOn = DateTimeOffset.Now.Add(expireAfter); - } - - public string Key { get; } - public DateTimeOffset ExpireOn { get; } - } - - public interface IBlackListItem - { - string Key { get; } - DateTimeOffset ExpireOn { get; } - } -} \ No newline at end of file diff --git a/Revoke.NET/MemoryBlackList.cs b/Revoke.NET/MemoryBlackList.cs new file mode 100644 index 0000000..52c3b21 --- /dev/null +++ b/Revoke.NET/MemoryBlackList.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; + +namespace Revoke.NET +{ + public class MemoryCacheBlackList : IBlackList + { + private IMemoryCache _memoryCache; + private TimeSpan? _defaultTtl; + private static CancellationTokenSource _resetCacheToken = new CancellationTokenSource(); + + public MemoryCacheBlackList(IMemoryCache memoryCache, TimeSpan? defaultTtl = null) + { + _defaultTtl = defaultTtl; + _memoryCache = memoryCache; + } + + public Task Revoke(string key) + { + var options = new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Normal) + .SetAbsoluteExpiration(_defaultTtl ?? TimeSpan.MaxValue); + options.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token)); + try + { + _memoryCache.Set(key, key, options); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + + public Task Delete(string key) + { + try + { + _memoryCache.Remove(key); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + + public Task DeleteAll() + { + if (_resetCacheToken != null && !_resetCacheToken.IsCancellationRequested && + _resetCacheToken.Token.CanBeCanceled) + { + _resetCacheToken.Cancel(); + _resetCacheToken.Dispose(); + } + + _resetCacheToken = new CancellationTokenSource(); + return Task.CompletedTask; + } + + + public Task IsRevoked(string key) + { + return Task.FromResult(_memoryCache.TryGetValue(key, out _)); + } + + public Task Revoke(string key, TimeSpan expireAfter) + { + var options = new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Normal) + .SetAbsoluteExpiration(expireAfter); + options.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token)); + try + { + _memoryCache.Set(key, key, options); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + + public Task Revoke(string key, DateTime expireOn) + { + var options = new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Normal) + .SetAbsoluteExpiration(expireOn); + options.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token)); + + try + { + _memoryCache.Set(key, key, options); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + } + + + public class MemoryBlackList : IBlackList + { + private ConcurrentDictionary _blackList; + private readonly TimeSpan? defaultTtl; + + private MemoryBlackList(TimeSpan? defaultTtl) + { + this.defaultTtl = defaultTtl; + _blackList = new ConcurrentDictionary(); + } + + public static MemoryBlackList CreateStore(TimeSpan? defaultExpirationDuration = null) + { + return new MemoryBlackList(defaultExpirationDuration); + } + + public Task Delete(string key) + { + return Task.FromResult(_blackList.TryRemove(key, out _)); + } + + public Task Revoke(string key, TimeSpan expireAfter) + { + return Task.FromResult(_blackList.TryAdd(key, DateTime.Now.Add(expireAfter))); + } + + public Task Revoke(string key, DateTime expireOn) + { + if (expireOn < DateTimeOffset.Now) return Task.FromResult(false); + return Task.FromResult(_blackList.TryAdd(key, expireOn)); + } + + public Task DeleteAll() + { + _blackList.Clear(); + return Task.CompletedTask; + } + + public Task IsRevoked(string key) + { + if (_blackList.TryGetValue(key, out var item)) + { + return Task.FromResult(item >= DateTime.Now); + } + + return Task.FromResult(false); + } + + public Task Revoke(string key) + { + if (DateTime.Now.Add(defaultTtl ?? TimeSpan.MaxValue) < DateTime.Now) + return Task.FromResult(false); + return Task.FromResult(_blackList.TryAdd(key, DateTime.Now.Add(defaultTtl ?? TimeSpan.MaxValue))); + } + } +} \ No newline at end of file diff --git a/Revoke.NET/MemoryBlackListStore.cs b/Revoke.NET/MemoryBlackListStore.cs deleted file mode 100644 index 8668056..0000000 --- a/Revoke.NET/MemoryBlackListStore.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Revoke.NET -{ - public class MemoryBlackListStore : IBlackListStore - { - private ConcurrentDictionary blackList; -#nullable enable - private TimeSpan? defaultTtl; -#nullable disable - - private MemoryBlackListStore(TimeSpan? defaultTtl) - { - this.defaultTtl = defaultTtl; - blackList = new ConcurrentDictionary(); - } - - public static MemoryBlackListStore CreateStore(TimeSpan? defaultExpirationDuration = null) - { - return new MemoryBlackListStore(defaultExpirationDuration); - } - - public Task Delete(string key) - { - return Task.FromResult(blackList.TryRemove(key, out _)); - } - - public Task DeleteAll() - { - blackList.Clear(); - return Task.CompletedTask; - } - - public Task DeleteExpired() - { - blackList = new ConcurrentDictionary( - blackList.Where(x => DateTime.Now > x.Value.ExpireOn)); - return Task.CompletedTask; - } - - public Task Get(string key) where T : IBlackListItem - { - if (blackList.TryGetValue(key, out var item)) - { - return Task.FromResult((T)item); - } - - return null; - } - - public Task> GetAll() where T : IBlackListItem - { - var items = blackList.Select(item => item.Value); - return Task.FromResult(items.Cast()); - } - - public Task IsRevoked(string key) - { - if (blackList.TryGetValue(key, out var item)) - { - return Task.FromResult(item.ExpireOn >= DateTimeOffset.Now); - } - - return Task.FromResult(false); - } - - public Task Revoke(string key) - { - var item = new BlackListItem(key, - defaultTtl is null ? DateTimeOffset.Now.Add(defaultTtl ?? TimeSpan.Zero) : DateTimeOffset.MaxValue); - - if (blackList.ContainsKey(key)) - { - return Task.FromResult(blackList.TryUpdate(key, item, item)); - } - else - { - return Task.FromResult(blackList.TryAdd(key, item)); - } - } - - public Task Revoke(string key, TimeSpan expireAfter) - { - var item = new BlackListItem(key, DateTimeOffset.Now.Add(defaultTtl ?? expireAfter)); - - if (blackList.ContainsKey(key)) - { - return Task.FromResult(blackList.TryUpdate(key, item, item)); - } - else - { - return Task.FromResult(blackList.TryAdd(key, item)); - } - } - - public Task Revoke(string key, DateTimeOffset expireOn) - { - var item = new BlackListItem(key, - defaultTtl is null ? DateTimeOffset.Now.Add(defaultTtl ?? TimeSpan.Zero) : expireOn); - - if (blackList.ContainsKey(key)) - { - return Task.FromResult(blackList.TryUpdate(key, item, item)); - } - else - { - return Task.FromResult(blackList.TryAdd(key, item)); - } - } - - public Task Revoke(T item) where T : IBlackListItem - { - return Task.FromResult(blackList.TryAdd(item.Key, item)); - } - } -} \ No newline at end of file diff --git a/Revoke.NET/README.md b/Revoke.NET/README.md index ea3beab..fcf4338 100644 --- a/Revoke.NET/README.md +++ b/Revoke.NET/README.md @@ -4,11 +4,14 @@ - HTTP Request Header Paramters, Query, URL, Host, IP, Cookies, Body, FormData, Claims...etc # Installation -**First**, install the `Revoke.NET` [NuGet package](https://www.nuget.org/packages/Revoke.NET) into your app +**First**, install the [`Revoke.NET`](https://www.nuget.org/packages/Revoke.NET) into your app ```powershell -PM> Install-Package Revoke.NET +Install-Package Revoke.NET +``` +or with dotnet cli: +```powershell +dotnet add package Revoke.NET ``` - # How to use simple create a new BlackList Store of type `IBlackListStore` ```csharp @@ -21,16 +24,22 @@ var key = "[ID String of something to be blacklisted]"; await store.Revoke(key, TimeSpan.FromHours(24)); // Revoke access to a key for 24 hours -var item = store.Get(key); // Retrieve a blacklisted item, SomeType must implement interface 'IBlackListItem' - -await store.Revoke(model); // Revoke an item with custom type +await store.Revoke(key); // Revoke access indefinetly or with the defaulTtl expiration -await store.IsRevoked(key); // Check if key is blacklisted +var revoked = await store.IsRevoked(key); // Check if key is blacklisted await store.Delete(key); // Delete a key from blacklist ``` # Usage with ASP.NET Core +Install the [`Revoke.NET.AspNetCore`](https://www.nuget.org/packages/Revoke.NET.AspNetCore) into your app +```powershell +Install-Package Revoke.NET.AspNetCore +``` +or with dotnet cli: +```powershell +dotnet add package Revoke.NET.AspNetCore +``` ```csharp using Revoke.NET; diff --git a/Revoke.NET/Revoke.NET.csproj b/Revoke.NET/Revoke.NET.csproj index d5fc0f6..9427edb 100644 --- a/Revoke.NET/Revoke.NET.csproj +++ b/Revoke.NET/Revoke.NET.csproj @@ -7,7 +7,7 @@ readme.md LICENSE Library - 1.0.5 + 2.0.1 Chakhoum Ahmed (github.com/rainxh11) .NET Utility to blacklist and revoke access to stuff @@ -25,9 +25,10 @@ - - - + + + + diff --git a/Revoke.NET/RevokeService.cs b/Revoke.NET/RevokeService.cs index e3081cd..4dcedff 100644 --- a/Revoke.NET/RevokeService.cs +++ b/Revoke.NET/RevokeService.cs @@ -3,13 +3,23 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Revoke.NET { public static class RevokeService { + public static IServiceCollection AddRevokeMemoryCacheStore(this IServiceCollection services, + TimeSpan? defaultTtl = null) + { + services.TryAddSingleton(provider => + new MemoryCacheBlackList(provider.GetService(), defaultTtl)); + return services; + } + /// /// Register default InMemory BlackList Store Service /// @@ -19,8 +29,9 @@ public static class RevokeService public static IServiceCollection AddRevokeInMemoryStore(this IServiceCollection services, TimeSpan? defaultTtl = null) { - return services - .AddSingleton(MemoryBlackListStore.CreateStore(defaultTtl)); + services + .TryAddSingleton(MemoryBlackList.CreateStore(defaultTtl)); + return services; } /// @@ -30,10 +41,11 @@ public static IServiceCollection AddRevokeInMemoryStore(this IServiceCollection /// /// public static IServiceCollection AddRevokeStore(this IServiceCollection services, - Func configure) + Func configure) { - return services - .AddSingleton(configure()); + services + .TryAddSingleton(configure()); + return services; } /// @@ -43,10 +55,11 @@ public static IServiceCollection AddRevokeStore(this IServiceCollection services /// /// public static IServiceCollection AddRevokeStore(this IServiceCollection services, - Func configure) + Func configure) { - return services - .AddSingleton(configure); + services + .TryAddSingleton(configure); + return services; } } } \ No newline at end of file diff --git a/Test/Program.cs b/Test/Program.cs index 4d9067d..57ba8b7 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -1,12 +1,12 @@ using Revoke.NET; using Revoke.NET.Redis; -var store = await RedisBlackListStore.CreateStoreAsync("localhost"); +var store = await RedisBlackList.CreateStoreAsync("localhost"); -await store.Revoke("Ahmed", DateTimeOffset.MaxValue); +await store.Revoke("Ahmed", DateTime.MaxValue); -var item = await store.Get("Ahmed"); +var revoked = await store.IsRevoked("Ahmed"); -Console.WriteLine(item.Key); +Console.WriteLine(revoked); Console.ReadKey(); \ No newline at end of file diff --git a/Test/Test.csproj b/Test/Test.csproj index b61d5a8..9adfe28 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/Revoke.NET.Akavache/assets/revoke.net.png b/assets/revoke.net.png similarity index 100% rename from Revoke.NET.Akavache/assets/revoke.net.png rename to assets/revoke.net.png