Browse Source

Merge pull request #2545 from abpframework/IDS4-deviceflow

Ids4 deviceflow
pull/2550/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
02724445e1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs
  2. 5
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs
  3. 6
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJobExecuter.cs
  4. 3
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs
  5. 3
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  6. 93
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs
  7. 55
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs
  8. 14
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerContext.cs
  9. 22
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs
  10. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs
  11. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs
  12. 3
      framework/test/SimpleConsoleDemo/Program.cs
  13. 4
      framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs
  14. 20
      framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs
  15. 7
      framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs
  16. 1
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj
  17. 36
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs
  18. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/Client.cs
  19. 31
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowCodes.cs
  20. 143
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs
  21. 27
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/IDeviceFlowCodesRepository.cs
  22. 6
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs
  23. 7
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerAutoMapperProfile.cs
  24. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerBuilderExtensions.cs
  25. 34
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs
  26. 34
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupOptions.cs
  27. 88
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupService.cs
  28. 51
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Devices/DeviceFlowCodesRepository.cs
  29. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/AbpIdentityServerEntityFrameworkCoreModule.cs
  30. 3
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IIdentityServerDbContext.cs
  31. 4
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContext.cs
  32. 19
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs
  33. 30
      modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs
  34. 3
      modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/AbpIdentityServerMongoDbContext.cs
  35. 6
      modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/AbpIdentityServerMongoDbContextExtensions.cs
  36. 2
      modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/AbpIdentityServerMongoDbModule.cs
  37. 3
      modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/IAbpIdentityServerMongoDbContext.cs
  38. 51
      modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs
  39. 14
      modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs
  40. 95
      modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/Devices/DeviceFlowStore_Tests.cs
  41. 37
      modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/Tokens/TokenCleanupService_Tests.cs
  42. 52
      modules/identityserver/test/Volo.Abp.IdentityServer.TestBase/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Tab/AbpTabLinkTagHelperService.cs

@ -7,7 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
{
public class AbpTabLinkTagHelperService : AbpTagHelperService<AbpTabLinkTagHelper>
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
SetPlaceholderForNameIfNotProvided();
@ -18,6 +18,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Tab
tabHeaderItems.Add(new TabItem(tabHeader, "", false, TagHelper.Name, TagHelper.ParentDropdownName, false));
output.SuppressOutput();
return Task.CompletedTask;
}
protected virtual string GetTabHeaderItem(TagHelperContext context, TagHelperOutput output)

5
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs

@ -4,7 +4,6 @@ using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs
{
@ -21,7 +20,7 @@ namespace Volo.Abp.BackgroundJobs
Logger = NullLogger<BackgroundJobExecuter>.Instance;
}
public virtual void Execute(JobExecutionContext context)
public virtual async Task ExecuteAsync(JobExecutionContext context)
{
var job = context.ServiceProvider.GetService(context.JobType);
if (job == null)
@ -41,7 +40,7 @@ namespace Volo.Abp.BackgroundJobs
{
if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob<object>.ExecuteAsync))
{
AsyncHelper.RunSync(() => (Task) jobExecuteMethod.Invoke(job, new[] {context.JobArgs}));
await ((Task) jobExecuteMethod.Invoke(job, new[] {context.JobArgs})).ConfigureAwait(false);
}
else
{

6
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IBackgroundJobExecuter.cs

@ -1,7 +1,9 @@
namespace Volo.Abp.BackgroundJobs
using System.Threading.Tasks;
namespace Volo.Abp.BackgroundJobs
{
public interface IBackgroundJobExecuter
{
void Execute(JobExecutionContext context);
Task ExecuteAsync(JobExecutionContext context);
}
}

3
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/HangfireJobExecutionAdapter.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs.Hangfire
{
@ -25,7 +26,7 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
{
var jobType = Options.GetJob(typeof(TArgs)).JobType;
var context = new JobExecutionContext(scope.ServiceProvider, jobType, args);
JobExecuter.Execute(context);
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context));
}
}
}

3
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -10,6 +10,7 @@ using Nito.AsyncEx;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using Volo.Abp.RabbitMQ;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs.RabbitMQ
{
@ -181,7 +182,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
try
{
JobExecuter.Execute(context);
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context));
ChannelAccessor.Channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
}
catch (BackgroundJobExecutionException)

93
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs

@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -9,92 +10,88 @@ using Volo.Abp.Timing;
namespace Volo.Abp.BackgroundJobs
{
public class BackgroundJobWorker : PeriodicBackgroundWorkerBase, IBackgroundJobWorker
public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroundJobWorker
{
protected AbpBackgroundJobOptions JobOptions { get; }
protected AbpBackgroundJobWorkerOptions WorkerOptions { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
public BackgroundJobWorker(
AbpTimer timer,
IOptions<AbpBackgroundJobOptions> jobOptions,
IOptions<AbpBackgroundJobWorkerOptions> workerOptions,
IServiceScopeFactory serviceScopeFactory)
: base(timer)
: base(
timer,
serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
WorkerOptions = workerOptions.Value;
JobOptions = jobOptions.Value;
Timer.Period = WorkerOptions.JobPollPeriod;
}
protected override void DoWork()
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var store = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var store = workerContext.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var waitingJobs = AsyncHelper.RunSync(() => store.GetWaitingJobsAsync(WorkerOptions.MaxJobFetchCount));
var waitingJobs = await store.GetWaitingJobsAsync(WorkerOptions.MaxJobFetchCount).ConfigureAwait(false);
if (!waitingJobs.Any())
{
return;
}
if (!waitingJobs.Any())
{
return;
}
var jobExecuter = workerContext.ServiceProvider.GetRequiredService<IBackgroundJobExecuter>();
var clock = workerContext.ServiceProvider.GetRequiredService<IClock>();
var serializer = workerContext.ServiceProvider.GetRequiredService<IBackgroundJobSerializer>();
var jobExecuter = scope.ServiceProvider.GetRequiredService<IBackgroundJobExecuter>();
var clock = scope.ServiceProvider.GetRequiredService<IClock>();
var serializer = scope.ServiceProvider.GetRequiredService<IBackgroundJobSerializer>();
foreach (var jobInfo in waitingJobs)
{
jobInfo.TryCount++;
jobInfo.LastTryTime = clock.Now;
foreach (var jobInfo in waitingJobs)
try
{
jobInfo.TryCount++;
jobInfo.LastTryTime = clock.Now;
var jobConfiguration = JobOptions.GetJob(jobInfo.JobName);
var jobArgs = serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType);
var context = new JobExecutionContext(workerContext.ServiceProvider, jobConfiguration.JobType, jobArgs);
try
{
var jobConfiguration = JobOptions.GetJob(jobInfo.JobName);
var jobArgs = serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType);
var context = new JobExecutionContext(scope.ServiceProvider, jobConfiguration.JobType, jobArgs);
await jobExecuter.ExecuteAsync(context).ConfigureAwait(false);
try
{
jobExecuter.Execute(context);
await store.DeleteAsync(jobInfo.Id).ConfigureAwait(false);
}
catch (BackgroundJobExecutionException)
{
var nextTryTime = CalculateNextTryTime(jobInfo, clock);
AsyncHelper.RunSync(() => store.DeleteAsync(jobInfo.Id));
if (nextTryTime.HasValue)
{
jobInfo.NextTryTime = nextTryTime.Value;
}
catch (BackgroundJobExecutionException)
else
{
var nextTryTime = CalculateNextTryTime(jobInfo, clock);
if (nextTryTime.HasValue)
{
jobInfo.NextTryTime = nextTryTime.Value;
}
else
{
jobInfo.IsAbandoned = true;
}
TryUpdate(store, jobInfo);
jobInfo.IsAbandoned = true;
}
await TryUpdateAsync(store, jobInfo).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogException(ex);
jobInfo.IsAbandoned = true;
TryUpdate(store, jobInfo);
}
}
catch (Exception ex)
{
Logger.LogException(ex);
jobInfo.IsAbandoned = true;
await TryUpdateAsync(store, jobInfo).ConfigureAwait(false);
}
}
}
protected virtual void TryUpdate(IBackgroundJobStore store, BackgroundJobInfo jobInfo)
protected virtual async Task TryUpdateAsync(IBackgroundJobStore store, BackgroundJobInfo jobInfo)
{
try
{
AsyncHelper.RunSync(() => store.UpdateAsync(jobInfo));
await store.UpdateAsync(jobInfo).ConfigureAwait(false);
}
catch (Exception updateEx)
{

55
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs

@ -0,0 +1,55 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers
{
public abstract class AsyncPeriodicBackgroundWorkerBase : BackgroundWorkerBase
{
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AbpTimer Timer { get; }
protected AsyncPeriodicBackgroundWorkerBase(
AbpTimer timer,
IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
Timer = timer;
Timer.Elapsed += Timer_Elapsed;
}
public override async Task StartAsync(CancellationToken cancellationToken = default)
{
await base.StartAsync(cancellationToken).ConfigureAwait(false);
Timer.Start(cancellationToken);
}
public override async Task StopAsync(CancellationToken cancellationToken = default)
{
Timer.Stop(cancellationToken);
await base.StopAsync(cancellationToken).ConfigureAwait(false);
}
private void Timer_Elapsed(object sender, System.EventArgs e)
{
try
{
using (var scope = ServiceScopeFactory.CreateScope())
{
AsyncHelper.RunSync(
() => DoWorkAsync(new PeriodicBackgroundWorkerContext(scope.ServiceProvider))
);
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
}
protected abstract Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext);
}
}

14
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerContext.cs

@ -0,0 +1,14 @@
using System;
namespace Volo.Abp.BackgroundWorkers
{
public class PeriodicBackgroundWorkerContext
{
public IServiceProvider ServiceProvider { get; }
public PeriodicBackgroundWorkerContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
}
}

22
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs

@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Threading;
@ -11,14 +12,14 @@ namespace Volo.Abp.BackgroundWorkers
/// </summary>
public abstract class PeriodicBackgroundWorkerBase : BackgroundWorkerBase
{
protected readonly AbpTimer Timer;
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected AbpTimer Timer { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PeriodicBackgroundWorkerBase"/> class.
/// </summary>
/// <param name="timer">A timer.</param>
protected PeriodicBackgroundWorkerBase(AbpTimer timer)
protected PeriodicBackgroundWorkerBase(
AbpTimer timer,
IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
Timer = timer;
Timer.Elapsed += Timer_Elapsed;
}
@ -34,12 +35,15 @@ namespace Volo.Abp.BackgroundWorkers
Timer.Stop(cancellationToken);
await base.StopAsync(cancellationToken).ConfigureAwait(false);
}
private void Timer_Elapsed(object sender, System.EventArgs e)
{
try
{
DoWork();
using (var scope = ServiceScopeFactory.CreateScope())
{
DoWork(new PeriodicBackgroundWorkerContext(scope.ServiceProvider));
}
}
catch (Exception ex)
{
@ -50,6 +54,6 @@ namespace Volo.Abp.BackgroundWorkers
/// <summary>
/// Periodic works should be done by implementing this method.
/// </summary>
protected abstract void DoWork();
protected abstract void DoWork(PeriodicBackgroundWorkerContext workerContext);
}
}

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs

@ -24,12 +24,12 @@ namespace Volo.Abp.Cli.Commands
AbpCliOptions = cliOptions.Value;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
if (string.IsNullOrWhiteSpace(commandLineArgs.Target))
{
Logger.LogInformation(GetUsageInfo());
return;
return Task.CompletedTask;
}
var commandType = AbpCliOptions.Commands[commandLineArgs.Target];
@ -39,6 +39,8 @@ namespace Volo.Abp.Cli.Commands
var command = (IConsoleCommand) scope.ServiceProvider.GetRequiredService(commandType);
Logger.LogInformation(command.GetUsageInfo());
}
return Task.CompletedTask;
}
public string GetUsageInfo()

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs

@ -48,7 +48,7 @@ namespace Volo.Abp.Cli.ProjectModification
.ConfigureAwait(false)).ConfigureAwait(false);
}
public async Task AddAsync(string projectFile, NugetPackageInfo package)
public Task AddAsync(string projectFile, NugetPackageInfo package)
{
using (DirectoryHelper.ChangeCurrentDirectory(Path.GetDirectoryName(projectFile)))
{
@ -71,6 +71,8 @@ namespace Volo.Abp.Cli.ProjectModification
Logger.LogInformation("Successfully installed.");
}
return Task.CompletedTask;
}
protected virtual async Task<NugetPackageInfo> FindNugetPackageInfoAsync(string moduleName)

3
framework/test/SimpleConsoleDemo/Program.cs

@ -10,7 +10,7 @@ namespace SimpleConsoleDemo
{
class Program
{
static async Task Main(string[] args)
static void Main(string[] args)
{
using (var application = AbpApplicationFactory.Create<MyConsoleModule>(options =>
{
@ -42,7 +42,6 @@ namespace SimpleConsoleDemo
writer.Write();
}
Console.WriteLine();
Console.WriteLine("Press ENTER to exit!");
Console.ReadLine();

4
framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundJobs/BackgroundJobExecuter_Tests.cs

@ -23,7 +23,7 @@ namespace Volo.Abp.BackgroundJobs
//Act
_backgroundJobExecuter.Execute(
await _backgroundJobExecuter.ExecuteAsync(
new JobExecutionContext(
ServiceProvider,
typeof(MyJob),
@ -46,7 +46,7 @@ namespace Volo.Abp.BackgroundJobs
//Act
_backgroundJobExecuter.Execute(
await _backgroundJobExecuter.ExecuteAsync(
new JobExecutionContext(
ServiceProvider,
typeof(MyAsyncJob),

20
framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs

@ -38,7 +38,7 @@ namespace Volo.Abp.Caching
}
[Fact]
public async Task GetOrAddAsync()
public void GetOrAdd()
{
var personCache = GetRequiredService<IDistributedCache<PersonCacheItem>>();
@ -49,12 +49,12 @@ namespace Volo.Abp.Caching
bool factoryExecuted = false;
var cacheItem = await personCache.GetOrAddAsync(cacheKey,
async () =>
var cacheItem = personCache.GetOrAdd(cacheKey,
() =>
{
factoryExecuted = true;
return new PersonCacheItem(personName);
}).ConfigureAwait(false);
});
factoryExecuted.ShouldBeTrue();
cacheItem.Name.ShouldBe(personName);
@ -63,12 +63,12 @@ namespace Volo.Abp.Caching
factoryExecuted = false;
cacheItem = await personCache.GetOrAddAsync(cacheKey,
async () =>
cacheItem = personCache.GetOrAdd(cacheKey,
() =>
{
factoryExecuted = true;
return new PersonCacheItem(personName);
}).ConfigureAwait(false);
});
factoryExecuted.ShouldBeFalse();
cacheItem.Name.ShouldBe(personName);
@ -165,12 +165,12 @@ namespace Volo.Abp.Caching
bool factoryExecuted = false;
var cacheItem = await personCache.GetOrAddAsync(cacheKey,
async () =>
var cacheItem = personCache.GetOrAdd(cacheKey,
() =>
{
factoryExecuted = true;
return new PersonCacheItem(personName);
}).ConfigureAwait(false);
});
factoryExecuted.ShouldBeTrue();
cacheItem.Name.ShouldBe(personName);

7
framework/test/Volo.Abp.Uow.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs

@ -24,7 +24,12 @@ namespace Volo.Abp.Uow
using (var uow = _unitOfWorkManager.Begin())
{
uow.OnCompleted(async () => completed = true);
uow.OnCompleted(() =>
{
completed = true;
return Task.CompletedTask;
});
uow.Disposed += (sender, args) => disposed = true;
await uow.CompleteAsync().ConfigureAwait(false);

1
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo.Abp.IdentityServer.Domain.csproj

@ -20,6 +20,7 @@
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Security\Volo.Abp.Security.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Validation\Volo.Abp.Validation.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundWorkers\Volo.Abp.BackgroundWorkers.csproj" />
</ItemGroup>
<ItemGroup>

36
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs

@ -1,11 +1,13 @@
using System.Security.Cryptography.X509Certificates;
using IdentityServer4.Services;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Volo.Abp.AutoMapper;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Caching;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Tokens;
using Volo.Abp.Modularity;
using Volo.Abp.Security;
using Volo.Abp.Validation;
@ -18,7 +20,8 @@ namespace Volo.Abp.IdentityServer
typeof(AbpIdentityDomainModule),
typeof(AbpSecurityModule),
typeof(AbpCachingModule),
typeof(AbpValidationModule)
typeof(AbpValidationModule),
typeof(AbpBackgroundWorkersModule)
)]
public class AbpIdentityServerDomainModule : AbpModule
{
@ -28,12 +31,12 @@ namespace Volo.Abp.IdentityServer
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<ClientAutoMapperProfile>(validate: true);
options.AddProfile<IdentityServerAutoMapperProfile>(validate: true);
});
AddIdentityServer(context.Services);
}
private static void AddIdentityServer(IServiceCollection services)
{
var configuration = services.GetConfiguration();
@ -58,7 +61,12 @@ namespace Volo.Abp.IdentityServer
if (!services.IsAdded<IPersistedGrantService>())
{
identityServerBuilder.AddInMemoryPersistedGrants();
services.TryAddSingleton<IPersistedGrantStore, InMemoryPersistedGrantStore>();
}
if (!services.IsAdded<IDeviceFlowStore>())
{
services.TryAddSingleton<IDeviceFlowStore, InMemoryDeviceFlowStore>();
}
if (!services.IsAdded<IClientStore>())
@ -72,5 +80,19 @@ namespace Volo.Abp.IdentityServer
identityServerBuilder.AddInMemoryIdentityResources(configuration.GetSection("IdentityServer:IdentityResources"));
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<TokenCleanupOptions>>().Value;
if (options.IsCleanupEnabled)
{
context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.Add(
context.ServiceProvider
.GetRequiredService<TokenCleanupBackgroundWorker>()
);
}
}
}
}

2
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/Client.cs

@ -108,10 +108,10 @@ namespace Volo.Abp.IdentityServer.Clients
}
public Client(Guid id, [NotNull] string clientId)
: base(id)
{
Check.NotNull(clientId, nameof(clientId));
Id = id;
ClientId = clientId;
//TODO: Replace magics with constants?

31
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowCodes.cs

@ -0,0 +1,31 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Volo.Abp.IdentityServer.Devices
{
public class DeviceFlowCodes : CreationAuditedAggregateRoot<Guid>
{
public virtual string DeviceCode { get; set; }
public virtual string UserCode { get; set; }
public virtual string SubjectId { get; set; }
public virtual string ClientId { get; set; }
public virtual DateTime? Expiration { get; set; }
public virtual string Data { get; set; }
private DeviceFlowCodes()
{
}
public DeviceFlowCodes(Guid id)
: base(id)
{
}
}
}

143
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/DeviceFlowStore.cs

@ -0,0 +1,143 @@
using System;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using IdentityServer4.Stores.Serialization;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
namespace Volo.Abp.IdentityServer.Devices
{
public class DeviceFlowStore : IDeviceFlowStore, ITransientDependency
{
protected IDeviceFlowCodesRepository DeviceFlowCodesRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IPersistentGrantSerializer PersistentGrantSerializer { get; }
public DeviceFlowStore(
IDeviceFlowCodesRepository deviceFlowCodesRepository,
IGuidGenerator guidGenerator,
IPersistentGrantSerializer persistentGrantSerializer)
{
DeviceFlowCodesRepository = deviceFlowCodesRepository;
GuidGenerator = guidGenerator;
PersistentGrantSerializer = persistentGrantSerializer;
}
public async Task StoreDeviceAuthorizationAsync(string deviceCode, string userCode, DeviceCode data)
{
Check.NotNull(deviceCode, nameof(deviceCode));
Check.NotNull(userCode, nameof(userCode));
Check.NotNull(data, nameof(data));
await DeviceFlowCodesRepository
.InsertAsync(
new DeviceFlowCodes(GuidGenerator.Create())
{
DeviceCode = deviceCode,
UserCode = userCode,
ClientId = data.ClientId,
SubjectId = data.Subject?.FindFirst(JwtClaimTypes.Subject).Value,
CreationTime = data.CreationTime,
Expiration = data.CreationTime.AddSeconds(data.Lifetime),
Data = Serialize(data)
}
).ConfigureAwait(false);
}
public async Task<DeviceCode> FindByUserCodeAsync(string userCode)
{
Check.NotNull(userCode, nameof(userCode));
var deviceCodes = await DeviceFlowCodesRepository
.FindByUserCodeAsync(userCode)
.ConfigureAwait(false);
if (deviceCodes == null)
{
return null;
}
return DeserializeToDeviceCode(deviceCodes.Data);
}
public async Task<DeviceCode> FindByDeviceCodeAsync(string deviceCode)
{
Check.NotNull(deviceCode, nameof(deviceCode));
var deviceCodes = await DeviceFlowCodesRepository
.FindByDeviceCodeAsync(deviceCode)
.ConfigureAwait(false);
if (deviceCodes == null)
{
return null;
}
return DeserializeToDeviceCode(deviceCodes.Data);
}
public async Task UpdateByUserCodeAsync(string userCode, DeviceCode data)
{
Check.NotNull(userCode, nameof(userCode));
Check.NotNull(data, nameof(data));
var deviceCodes = await DeviceFlowCodesRepository
.FindByUserCodeAsync(userCode)
.ConfigureAwait(false);
if (deviceCodes == null)
{
throw new InvalidOperationException($"Could not update device code by the given userCode: {userCode}");
}
deviceCodes.SubjectId = data.Subject?.FindFirst(JwtClaimTypes.Subject).Value;
deviceCodes.Data = Serialize(data);
await DeviceFlowCodesRepository
.UpdateAsync(deviceCodes, autoSave: true)
.ConfigureAwait(false);
}
public async Task RemoveByDeviceCodeAsync(string deviceCode)
{
Check.NotNull(deviceCode, nameof(deviceCode));
var deviceCodes = await DeviceFlowCodesRepository
.FindByDeviceCodeAsync(deviceCode)
.ConfigureAwait(false);
if (deviceCodes == null)
{
return;
}
await DeviceFlowCodesRepository
.DeleteAsync(deviceCodes, autoSave: true)
.ConfigureAwait(false);
}
private string Serialize([CanBeNull] DeviceCode deviceCode)
{
if (deviceCode == null)
{
return null;
}
return PersistentGrantSerializer.Serialize(deviceCode);
}
protected virtual DeviceCode DeserializeToDeviceCode([CanBeNull] string data)
{
if (data == null)
{
return null;
}
return PersistentGrantSerializer.Deserialize<DeviceCode>(data);
}
}
}

27
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Devices/IDeviceFlowCodesRepository.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.IdentityServer.Devices
{
public interface IDeviceFlowCodesRepository : IBasicRepository<DeviceFlowCodes, Guid>
{
Task<DeviceFlowCodes> FindByUserCodeAsync(
string userCode,
CancellationToken cancellationToken = default
);
Task<DeviceFlowCodes> FindByDeviceCodeAsync(
string deviceCode,
CancellationToken cancellationToken = default
);
Task<List<DeviceFlowCodes>> GetListByExpirationAsync(
DateTime maxExpirationDate,
int maxResultCount,
CancellationToken cancellationToken = default
);
}
}

6
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Grants/IPersistentGrantRepository.cs

@ -18,6 +18,12 @@ namespace Volo.Abp.IdentityServer.Grants
CancellationToken cancellationToken = default
);
Task<List<PersistedGrant>> GetListByExpirationAsync(
DateTime maxExpirationDate,
int maxResultCount,
CancellationToken cancellationToken = default
);
Task DeleteAsync(
string subjectId,
string clientId,

7
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs → modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerAutoMapperProfile.cs

@ -2,14 +2,15 @@
using System.Security.Claims;
using AutoMapper;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
namespace Volo.Abp.IdentityServer.Clients
namespace Volo.Abp.IdentityServer
{
public class ClientAutoMapperProfile : Profile
public class IdentityServerAutoMapperProfile : Profile
{
public ClientAutoMapperProfile()
public IdentityServerAutoMapperProfile()
{
//TODO: Reverse maps will not used probably. Remove those will not used

2
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/IdentityServerBuilderExtensions.cs

@ -1,6 +1,7 @@
using IdentityServer4.Stores;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
namespace Volo.Abp.IdentityServer
@ -10,6 +11,7 @@ namespace Volo.Abp.IdentityServer
public static IIdentityServerBuilder AddAbpStores(this IIdentityServerBuilder builder)
{
builder.Services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();
builder.Services.AddTransient<IDeviceFlowStore, DeviceFlowStore>();
return builder
.AddClientStore<ClientStore>()

34
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupBackgroundWorker.cs

@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Threading;
namespace Volo.Abp.IdentityServer.Tokens
{
public class TokenCleanupBackgroundWorker : AsyncPeriodicBackgroundWorkerBase
{
protected TokenCleanupOptions Options { get; }
public TokenCleanupBackgroundWorker(
AbpTimer timer,
IServiceScopeFactory serviceScopeFactory,
IOptions<TokenCleanupOptions> options)
: base(
timer,
serviceScopeFactory)
{
Options = options.Value;
timer.Period = Options.CleanupPeriod;
}
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
{
await workerContext
.ServiceProvider
.GetRequiredService<TokenCleanupService>()
.CleanAsync()
.ConfigureAwait(false);
}
}
}

34
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupOptions.cs

@ -0,0 +1,34 @@
using Volo.Abp.BackgroundWorkers;
namespace Volo.Abp.IdentityServer.Tokens
{
public class TokenCleanupOptions
{
/// <summary>
/// Default: 3,600,000 ms.
/// </summary>
public int CleanupPeriod { get; set; } = 3_600_000;
/// <summary>
/// Default value: 100.
/// </summary>
public int CleanupBatchSize { get; set; } = 100;
/// <summary>
/// The number of loop if there are
/// more than <see cref="CleanupBatchSize"/> tokens in the database.
/// So, if <see cref="CleanupLoopCount"/> is 10 and <see cref="CleanupBatchSize"/> is 100,
/// then the cleanup worker will clean 1,000 items in one <see cref="CleanupPeriod"/> at max.
///
/// Default value: 10.
/// </summary>
public int CleanupLoopCount { get; set; } = 10;
/// <summary>
/// Default value: true.
/// If <see cref="AbpBackgroundWorkerOptions.IsEnabled"/> is false,
/// this property is ignored and the cleanup worker doesn't work for this application instance.
/// </summary>
public bool IsCleanupEnabled { get; set; } = true;
}
}

88
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Tokens/TokenCleanupService.cs

@ -0,0 +1,88 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace Volo.Abp.IdentityServer.Tokens
{
public class TokenCleanupService : ITransientDependency
{
protected IPersistentGrantRepository PersistentGrantRepository { get; }
protected IDeviceFlowCodesRepository DeviceFlowCodesRepository { get; }
protected IClock Clock { get; }
protected TokenCleanupOptions Options { get; }
public TokenCleanupService(
IPersistentGrantRepository persistentGrantRepository,
IDeviceFlowCodesRepository deviceFlowCodesRepository,
IClock clock,
IOptions<TokenCleanupOptions> options)
{
PersistentGrantRepository = persistentGrantRepository;
DeviceFlowCodesRepository = deviceFlowCodesRepository;
Clock = clock;
Options = options.Value;
}
public virtual async Task CleanAsync()
{
await RemoveGrantsAsync()
.ConfigureAwait(false);
await RemoveDeviceCodesAsync()
.ConfigureAwait(false);
}
[UnitOfWork]
protected virtual async Task RemoveGrantsAsync()
{
for (int i = 0; i < Options.CleanupLoopCount; i++)
{
var persistentGrants = await PersistentGrantRepository
.GetListByExpirationAsync(Clock.Now, Options.CleanupBatchSize)
.ConfigureAwait(false);
//TODO: Can be optimized if the repository implements the batch deletion
foreach (var persistentGrant in persistentGrants)
{
await PersistentGrantRepository
.DeleteAsync(persistentGrant)
.ConfigureAwait(false);
}
//No need to continue to query if it gets more than max items.
if (persistentGrants.Count < Options.CleanupBatchSize)
{
break;
}
}
}
protected virtual async Task RemoveDeviceCodesAsync()
{
for (int i = 0; i < Options.CleanupLoopCount; i++)
{
var deviceFlowCodeses = await DeviceFlowCodesRepository
.GetListByExpirationAsync(Clock.Now, Options.CleanupBatchSize)
.ConfigureAwait(false);
//TODO: Can be optimized if the repository implements the batch deletion
foreach (var deviceFlowCodes in deviceFlowCodeses)
{
await DeviceFlowCodesRepository
.DeleteAsync(deviceFlowCodes)
.ConfigureAwait(false);
}
//No need to continue to query if it gets more than max items.
if (deviceFlowCodeses.Count < Options.CleanupBatchSize)
{
break;
}
}
}
}
}

51
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Devices/DeviceFlowCodesRepository.cs

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.IdentityServer.EntityFrameworkCore;
namespace Volo.Abp.IdentityServer.Devices
{
public class DeviceFlowCodesRepository : EfCoreRepository<IIdentityServerDbContext, DeviceFlowCodes, Guid>,
IDeviceFlowCodesRepository
{
public DeviceFlowCodesRepository(IDbContextProvider<IIdentityServerDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<DeviceFlowCodes> FindByUserCodeAsync(
string userCode,
CancellationToken cancellationToken = default)
{
return await DbSet
.FirstOrDefaultAsync(d => d.UserCode == userCode, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task<DeviceFlowCodes> FindByDeviceCodeAsync(
string deviceCode,
CancellationToken cancellationToken = default)
{
return await DbSet
.FirstOrDefaultAsync(d => d.DeviceCode == deviceCode, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task<List<DeviceFlowCodes>> GetListByExpirationAsync(DateTime maxExpirationDate, int maxResultCount,
CancellationToken cancellationToken = default)
{
return await DbSet
.Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
.OrderBy(x => x.ClientId)
.Take(maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
}
}

2
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/AbpIdentityServerEntityFrameworkCoreModule.cs

@ -2,6 +2,7 @@
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
using Volo.Abp.Modularity;
@ -34,6 +35,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
options.AddRepository<ApiResource, ApiResourceRepository>();
options.AddRepository<IdentityResource, IdentityResourceRepository>();
options.AddRepository<PersistedGrant, PersistentGrantRepository>();
options.AddRepository<DeviceFlowCodes, DeviceFlowCodesRepository>();
});
}
}

3
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IIdentityServerDbContext.cs

@ -3,6 +3,7 @@ using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
@ -46,5 +47,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
DbSet<ClientProperty> ClientProperties { get; set; }
DbSet<PersistedGrant> PersistedGrants { get; set; }
DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
}
}

4
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContext.cs

@ -3,6 +3,7 @@ using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
@ -47,6 +48,8 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
public DbSet<PersistedGrant> PersistedGrants { get; set; }
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
public IdentityServerDbContext(DbContextOptions<IdentityServerDbContext> options)
: base(options)
{
@ -56,6 +59,7 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureIdentityServer();
}
}

19
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/EntityFrameworkCore/IdentityServerDbContextModelCreatingExtensions.cs

@ -6,6 +6,7 @@ using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
@ -289,6 +290,24 @@ namespace Volo.Abp.IdentityServer.EntityFrameworkCore
apiScopeClaim.Property(x => x.Type).HasMaxLength(UserClaimConsts.TypeMaxLength).IsRequired();
apiScopeClaim.Property(x => x.Name).HasMaxLength(ApiScopeConsts.NameMaxLength).IsRequired();
});
builder.Entity<DeviceFlowCodes>(b =>
{
b.ToTable(options.TablePrefix + "DeviceFlowCodes", options.Schema);
b.ConfigureByConvention();
b.Property(x => x.DeviceCode).HasMaxLength(200).IsRequired();
b.Property(x => x.UserCode).HasMaxLength(200).IsRequired();
b.Property(x => x.SubjectId).HasMaxLength(200);
b.Property(x => x.ClientId).HasMaxLength(200).IsRequired();
b.Property(x => x.Expiration).IsRequired();
b.Property(x => x.Data).HasMaxLength(50000).IsRequired();
b.HasIndex(x => new { x.UserCode }).IsUnique();
b.HasIndex(x => x.DeviceCode).IsUnique();
b.HasIndex(x => x.Expiration);
});
}
}
}

30
modules/identityserver/src/Volo.Abp.IdentityServer.EntityFrameworkCore/Volo/Abp/IdentityServer/Grants/PersistedGrantRepository.cs

@ -12,26 +12,42 @@ namespace Volo.Abp.IdentityServer.Grants
{
public class PersistentGrantRepository : EfCoreRepository<IIdentityServerDbContext, PersistedGrant, Guid>, IPersistentGrantRepository
{
public PersistentGrantRepository(IDbContextProvider<IIdentityServerDbContext> dbContextProvider) : base(dbContextProvider)
public PersistentGrantRepository(IDbContextProvider<IIdentityServerDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public Task<PersistedGrant> FindByKeyAsync(
public async Task<PersistedGrant> FindByKeyAsync(
string key,
CancellationToken cancellationToken = default)
{
return DbSet
.FirstOrDefaultAsync(x => x.Key == key, GetCancellationToken(cancellationToken));
return await DbSet
.FirstOrDefaultAsync(x => x.Key == key, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public Task<List<PersistedGrant>> GetListBySubjectIdAsync(
public async Task<List<PersistedGrant>> GetListBySubjectIdAsync(
string subjectId,
CancellationToken cancellationToken = default)
{
return DbSet
return await DbSet
.Where(x => x.SubjectId == subjectId)
.ToListAsync(GetCancellationToken(cancellationToken));
.ToListAsync(GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task<List<PersistedGrant>> GetListByExpirationAsync(
DateTime maxExpirationDate,
int maxResultCount,
CancellationToken cancellationToken = default)
{
return await DbSet
.Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
.OrderBy(x => x.ClientId)
.Take(maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task DeleteAsync(

3
modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/AbpIdentityServerMongoDbContext.cs

@ -2,6 +2,7 @@
using Volo.Abp.Data;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.MongoDB;
using IdentityResource = Volo.Abp.IdentityServer.IdentityResources.IdentityResource;
@ -19,6 +20,8 @@ namespace Volo.Abp.IdentityServer.MongoDB
public IMongoCollection<PersistedGrant> PersistedGrants => Collection<PersistedGrant>();
public IMongoCollection<DeviceFlowCodes> DeviceFlowCodes => Collection<DeviceFlowCodes>();
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);

6
modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/AbpIdentityServerMongoDbContextExtensions.cs

@ -1,6 +1,7 @@
using System;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
using Volo.Abp.MongoDB;
@ -39,6 +40,11 @@ namespace Volo.Abp.IdentityServer.MongoDB
{
b.CollectionName = options.CollectionPrefix + "PersistedGrants";
});
builder.Entity<DeviceFlowCodes>(b =>
{
b.CollectionName = options.CollectionPrefix + "DeviceFlowCodes";
});
}
}
}

2
modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/AbpIdentityServerMongoDbModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.Modularity;
using Volo.Abp.MongoDB;
@ -32,6 +33,7 @@ namespace Volo.Abp.IdentityServer.MongoDB
options.AddRepository<IdentityResource, MongoIdentityResourceRepository>();
options.AddRepository<Client, MongoClientRepository>();
options.AddRepository<PersistedGrant, MongoPersistentGrantRepository>();
options.AddRepository<DeviceFlowCodes, MongoDeviceFlowCodesRepository>();
});
}
}

3
modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/IAbpIdentityServerMongoDbContext.cs

@ -1,6 +1,7 @@
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
using Volo.Abp.MongoDB;
@ -18,5 +19,7 @@ namespace Volo.Abp.IdentityServer.MongoDB
IMongoCollection<IdentityResource> IdentityResources { get; }
IMongoCollection<PersistedGrant> PersistedGrants { get; }
IMongoCollection<DeviceFlowCodes> DeviceFlowCodes { get; }
}
}

51
modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoDeviceFlowCodesRepository.cs

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.MongoDB;
namespace Volo.Abp.IdentityServer.MongoDB
{
public class MongoDeviceFlowCodesRepository :
MongoDbRepository<IAbpIdentityServerMongoDbContext, DeviceFlowCodes, Guid>, IDeviceFlowCodesRepository
{
public MongoDeviceFlowCodesRepository(
IMongoDbContextProvider<IAbpIdentityServerMongoDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public async Task<DeviceFlowCodes> FindByUserCodeAsync(
string userCode,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.FirstOrDefaultAsync(d => d.UserCode == userCode, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task<DeviceFlowCodes> FindByDeviceCodeAsync(string deviceCode, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.FirstOrDefaultAsync(d => d.DeviceCode == deviceCode, GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task<List<DeviceFlowCodes>> GetListByExpirationAsync(
DateTime maxExpirationDate,
int maxResultCount,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
.OrderBy(x => x.ClientId)
.Take(maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
}
}

14
modules/identityserver/src/Volo.Abp.IdentityServer.MongoDB/Volo/Abp/IdentityServer/MongoDB/MongoPersistedGrantRepository.cs

@ -27,7 +27,19 @@ namespace Volo.Abp.IdentityServer.MongoDB
{
return await GetMongoQueryable()
.Where(x => x.SubjectId == subjectId)
.ToListAsync(GetCancellationToken(cancellationToken)).ConfigureAwait(false);
.ToListAsync(GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task<List<PersistedGrant>> GetListByExpirationAsync(DateTime maxExpirationDate, int maxResultCount,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(x => x.Expiration != null && x.Expiration < maxExpirationDate)
.OrderBy(x => x.ClientId)
.Take(maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken))
.ConfigureAwait(false);
}
public async Task DeleteAsync(string subjectId, string clientId, CancellationToken cancellationToken = default)

95
modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/Devices/DeviceFlowStore_Tests.cs

@ -0,0 +1,95 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using Shouldly;
using Xunit;
namespace Volo.Abp.IdentityServer.Devices
{
public class DeviceFlowStore_Tests : AbpIdentityServerTestBase
{
private readonly IDeviceFlowStore _deviceFlowStore;
public DeviceFlowStore_Tests()
{
_deviceFlowStore = GetRequiredService<IDeviceFlowStore>();
}
[Fact]
public async Task StoreDeviceAuthorizationAsync()
{
await _deviceFlowStore.StoreDeviceAuthorizationAsync(
"DeviceCode-Test1",
"UserCode-Test1",
new DeviceCode
{
ClientId = "ClientId1",
AuthorizedScopes = new string[] { "s1", "s2" },
IsAuthorized = true,
Lifetime = 42,
RequestedScopes = new string[] { "rs1" },
Subject = new ClaimsPrincipal(
new[]
{
new ClaimsIdentity(new[]
{
new Claim(JwtClaimTypes.Subject, "sid1")
})
}
)
}
);
void Check(DeviceCode deviceCode)
{
deviceCode.ClientId.ShouldBe("ClientId1");
deviceCode.AuthorizedScopes.ShouldNotBeNull();
deviceCode.AuthorizedScopes.Count().ShouldBe(2);
deviceCode.AuthorizedScopes.ShouldContain("s1");
deviceCode.AuthorizedScopes.ShouldContain("s2");
deviceCode.IsAuthorized.ShouldBeTrue();
deviceCode.Lifetime.ShouldBe(42);
deviceCode.RequestedScopes.ShouldNotBeNull();
deviceCode.RequestedScopes.Count().ShouldBe(1);
deviceCode.RequestedScopes.ShouldContain("rs1");
deviceCode.Subject.ShouldNotBeNull();
deviceCode.Subject.Claims.ShouldContain(x => x.Type == JwtClaimTypes.Subject && x.Value == "sid1");
}
Check(await _deviceFlowStore.FindByUserCodeAsync("UserCode-Test1"));
Check(await _deviceFlowStore.FindByDeviceCodeAsync("DeviceCode-Test1"));
}
[Fact]
public async Task RemoveByDeviceCodeAsync()
{
(await _deviceFlowStore.FindByDeviceCodeAsync("DeviceCode1")).ShouldNotBeNull();
await _deviceFlowStore.RemoveByDeviceCodeAsync("DeviceCode1");
(await _deviceFlowStore.FindByDeviceCodeAsync("DeviceCode1")).ShouldBeNull();
}
[Fact]
public async Task UpdateByDeviceCodeAsync()
{
var deviceCode = await _deviceFlowStore.FindByDeviceCodeAsync("DeviceCode1");
deviceCode.ShouldNotBeNull();
deviceCode.Lifetime.ShouldBe(42);
await _deviceFlowStore.UpdateByUserCodeAsync(
"DeviceFlowCodesUserCode1",
new DeviceCode
{
Lifetime = 43
}
);
deviceCode = await _deviceFlowStore.FindByDeviceCodeAsync("DeviceCode1");
deviceCode.Lifetime.ShouldBe(43);
}
}
}

37
modules/identityserver/test/Volo.Abp.IdentityServer.Domain.Tests/Volo/Abp/IdentityServer/Tokens/TokenCleanupService_Tests.cs

@ -0,0 +1,37 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Xunit;
namespace Volo.Abp.IdentityServer.Tokens
{
public class TokenCleanupService_Tests : AbpIdentityServerTestBase
{
private readonly IPersistentGrantRepository _persistentGrantRepository;
private readonly IDeviceFlowCodesRepository _deviceFlowCodesRepository;
private readonly TokenCleanupService _tokenCleanupService;
public TokenCleanupService_Tests()
{
_persistentGrantRepository = GetRequiredService<IPersistentGrantRepository>();
_deviceFlowCodesRepository = GetRequiredService<IDeviceFlowCodesRepository>();
_tokenCleanupService = GetRequiredService<TokenCleanupService>();
}
[Fact]
public async Task Should_Clear_Expired_Tokens()
{
var persistentGrantCount = await _persistentGrantRepository.GetCountAsync();
var deviceFlowCodesCount = await _deviceFlowCodesRepository.GetCountAsync();
await _tokenCleanupService.CleanAsync();
(await _persistentGrantRepository.GetCountAsync())
.ShouldBe(persistentGrantCount - 1);
(await _deviceFlowCodesRepository.GetCountAsync())
.ShouldBe(deviceFlowCodesCount - 1);
}
}
}

52
modules/identityserver/test/Volo.Abp.IdentityServer.TestBase/Volo/Abp/IdentityServer/AbpIdentityServerTestDataBuilder.cs

@ -6,8 +6,10 @@ using Volo.Abp.Guids;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.ApiResources;
using Volo.Abp.IdentityServer.Clients;
using Volo.Abp.IdentityServer.Devices;
using Volo.Abp.IdentityServer.Grants;
using Volo.Abp.IdentityServer.IdentityResources;
using Volo.Abp.Timing;
namespace Volo.Abp.IdentityServer
{
@ -19,7 +21,9 @@ namespace Volo.Abp.IdentityServer
private readonly IIdentityResourceRepository _identityResourceRepository;
private readonly IIdentityClaimTypeRepository _identityClaimTypeRepository;
private readonly IPersistentGrantRepository _persistentGrantRepository;
private readonly IDeviceFlowCodesRepository _deviceFlowCodesRepository;
private readonly AbpIdentityServerTestData _testData;
private readonly IClock _clock;
public AbpIdentityServerTestDataBuilder(
IGuidGenerator guidGenerator,
@ -28,7 +32,9 @@ namespace Volo.Abp.IdentityServer
IIdentityResourceRepository identityResourceRepository,
IIdentityClaimTypeRepository identityClaimTypeRepository,
AbpIdentityServerTestData testData,
IPersistentGrantRepository persistentGrantRepository)
IPersistentGrantRepository persistentGrantRepository,
IDeviceFlowCodesRepository deviceFlowCodesRepository,
IClock clock)
{
_testData = testData;
_guidGenerator = guidGenerator;
@ -37,10 +43,13 @@ namespace Volo.Abp.IdentityServer
_identityResourceRepository = identityResourceRepository;
_identityClaimTypeRepository = identityClaimTypeRepository;
_persistentGrantRepository = persistentGrantRepository;
_clock = clock;
_deviceFlowCodesRepository = deviceFlowCodesRepository;
}
public async Task BuildAsync()
{
await AddDeviceFlowCodes().ConfigureAwait(false);
await AddPersistedGrants().ConfigureAwait(false);
await AddIdentityResources().ConfigureAwait(false);
await AddApiResources().ConfigureAwait(false);
@ -48,6 +57,34 @@ namespace Volo.Abp.IdentityServer
await AddClaimTypes().ConfigureAwait(false);
}
private async Task AddDeviceFlowCodes()
{
await _deviceFlowCodesRepository.InsertAsync(
new DeviceFlowCodes(_guidGenerator.Create())
{
ClientId = "c1",
DeviceCode = "DeviceCode1",
Expiration = _clock.Now.AddDays(1),
Data = "{\"Lifetime\":\"42\"}",
UserCode = "DeviceFlowCodesUserCode1",
SubjectId = "DeviceFlowCodesSubjectId1"
}
);
await _deviceFlowCodesRepository.InsertAsync(
new DeviceFlowCodes(_guidGenerator.Create())
{
ClientId = "c1",
DeviceCode = "DeviceCode2",
Expiration = _clock.Now.AddDays(-1),
Data = "",
UserCode = "DeviceFlowCodesUserCode2",
SubjectId = "DeviceFlowCodesSubjectId2"
}
);
}
private async Task AddPersistedGrants()
{
await _persistentGrantRepository.InsertAsync(new PersistedGrant(_guidGenerator.Create())
@ -74,7 +111,18 @@ namespace Volo.Abp.IdentityServer
SubjectId = "PersistedGrantSubjectId3",
ClientId = "c1",
Type = "c1type",
Data = ""
Data = "",
Expiration = _clock.Now.AddDays(1),
}).ConfigureAwait(false);
await _persistentGrantRepository.InsertAsync(new PersistedGrant(_guidGenerator.Create())
{
Key = "PersistedGrantKey_Expired1",
SubjectId = "PersistedGrantSubjectId_Expired1",
ClientId = "c1",
Type = "c1type",
Data = "",
Expiration = _clock.Now.AddDays(-1)
}).ConfigureAwait(false);
}

Loading…
Cancel
Save