diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs index 46656cc9..d0e61def 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 375f186a..21e90944 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -38,6 +38,11 @@ namespace OpenIddict.Core /// /// Provides methods allowing to manage the applications stored in the store. /// + /// + /// Applications that do not want to depend on a specific entity type can use the non-generic + /// instead, for which the actual entity type + /// is resolved at runtime based on the default entity type registered in the core options. + /// /// The type of the Application entity. public class OpenIddictApplicationManager : IOpenIddictApplicationManager where TApplication : class { diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index f8f8bf93..8903999c 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -26,6 +26,11 @@ namespace OpenIddict.Core /// /// Provides methods allowing to manage the authorizations stored in the store. /// + /// + /// Applications that do not want to depend on a specific entity type can use the non-generic + /// instead, for which the actual entity type + /// is resolved at runtime based on the default entity type registered in the core options. + /// /// The type of the Authorization entity. public class OpenIddictAuthorizationManager : IOpenIddictAuthorizationManager where TAuthorization : class { diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index dd2f8d11..baa43048 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -24,6 +24,11 @@ namespace OpenIddict.Core /// /// Provides methods allowing to manage the scopes stored in the store. /// + /// + /// Applications that do not want to depend on a specific entity type can use the non-generic + /// instead, for which the actual entity type + /// is resolved at runtime based on the default entity type registered in the core options. + /// /// The type of the Scope entity. public class OpenIddictScopeManager : IOpenIddictScopeManager where TScope : class { diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 0000bc8b..63258748 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -26,6 +26,11 @@ namespace OpenIddict.Core /// /// Provides methods allowing to manage the tokens stored in the store. /// + /// + /// Applications that do not want to depend on a specific entity type can use the non-generic + /// instead, for which the actual entity type + /// is resolved at runtime based on the default entity type registered in the core options. + /// /// The type of the Token entity. public class OpenIddictTokenManager : IOpenIddictTokenManager where TToken : class { diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs index ce1a896a..1961adfa 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbBuilder.cs @@ -50,14 +50,6 @@ namespace Microsoft.Extensions.DependencyInjection return this; } - /// - /// Disables initialization so that the MongoDB indexes used by OpenIddict - /// are not automatically created the first time the stores are invoked. - /// - /// The . - public OpenIddictMongoDbBuilder DisableInitialization() - => Configure(options => options.DisableInitialization = true); - /// /// Configures OpenIddict to use the specified entity as the default application entity. /// @@ -136,15 +128,6 @@ namespace Microsoft.Extensions.DependencyInjection return Configure(options => options.AuthorizationsCollectionName = name); } - /// - /// Sets the maximal duration given to the MongoDB client to initialize - /// the database and register the indexes used by the OpenIddict entities. - /// - /// The timeout. - /// The . - public OpenIddictMongoDbBuilder SetInitializationTimeout(TimeSpan timeout) - => Configure(options => options.InitializationTimeout = timeout); - /// /// Replaces the default scopes collection name (by default, openiddict.scopes). /// diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs index 2cb654df..5914cfa2 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbContext.cs @@ -12,19 +12,16 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using MongoDB.Driver; -using OpenIddict.MongoDb.Models; namespace OpenIddict.MongoDb { /// /// Exposes the MongoDB database used by the OpenIddict stores. /// - public class OpenIddictMongoDbContext : IOpenIddictMongoDbContext, IDisposable + public class OpenIddictMongoDbContext : IOpenIddictMongoDbContext { private readonly IOptionsMonitor _options; private readonly IServiceProvider _provider; - private readonly SemaphoreSlim _semaphore; - private IMongoDatabase _database; public OpenIddictMongoDbContext( [NotNull] IOptionsMonitor options, @@ -32,14 +29,8 @@ namespace OpenIddict.MongoDb { _options = options; _provider = provider; - _semaphore = new SemaphoreSlim(1); } - /// - /// Disposes the semaphore held by this instance. - /// - public void Dispose() => _semaphore.Dispose(); - /// /// Gets the . /// @@ -49,135 +40,29 @@ namespace OpenIddict.MongoDb /// public ValueTask GetDatabaseAsync(CancellationToken cancellationToken) { - if (_database != null) - { - return new ValueTask(_database); - } - if (cancellationToken.IsCancellationRequested) { return new ValueTask(Task.FromCanceled(cancellationToken)); } - async Task ExecuteAsync() + var database = _options.CurrentValue.Database; + if (database == null) { - var options = _options.CurrentValue; - if (options == null) - { - throw new InvalidOperationException("The OpenIddict MongoDB options cannot be retrieved."); - } - - if (!await _semaphore.WaitAsync(options.InitializationTimeout, cancellationToken)) - { - throw new InvalidOperationException(new StringBuilder() - .AppendLine("The MongoDB database couldn't be initialized within a reasonable timeframe.") - .Append("Make sure that the MongoDB server is ready and accepts connections from this machine or use ") - .Append("'services.AddOpenIddict().AddCore().UseMongoDb().SetInitializationTimeout()' to adjust the timeout.") - .ToString()); - } - - try - { - var database = options.Database; - if (database == null) - { - database = _provider.GetService(); - } - - if (database == null) - { - throw new InvalidOperationException(new StringBuilder() - .AppendLine("No suitable MongoDB database service can be found.") - .Append("To configure the OpenIddict MongoDB stores to use a specific database, use ") - .Append("'services.AddOpenIddict().AddCore().UseMongoDb().UseDatabase()' or register an ") - .Append("'IMongoDatabase' in the dependency injection container in 'ConfigureServices()'.") - .ToString()); - } - - if (!options.DisableInitialization) - { - // Note: the cancellation token passed as a parameter is deliberately not used here to ensure - // the cancellation of a single store operation doesn't prevent the indexes from being created. - var applications = database.GetCollection(options.ApplicationsCollectionName); - await applications.Indexes.CreateManyAsync(new[] - { - new CreateIndexModel( - Builders.IndexKeys.Ascending(application => application.ClientId), - new CreateIndexOptions - { - Unique = true - }), - - new CreateIndexModel( - Builders.IndexKeys.Ascending(application => application.PostLogoutRedirectUris), - new CreateIndexOptions - { - Background = true - }), - - new CreateIndexModel( - Builders.IndexKeys.Ascending(application => application.RedirectUris), - new CreateIndexOptions - { - Background = true - }) - }); - - var authorizations = database.GetCollection(options.AuthorizationsCollectionName); - await authorizations.Indexes.CreateOneAsync(new CreateIndexModel( - Builders.IndexKeys - .Ascending(authorization => authorization.ApplicationId) - .Ascending(authorization => authorization.Scopes) - .Ascending(authorization => authorization.Status) - .Ascending(authorization => authorization.Subject) - .Ascending(authorization => authorization.Type), - new CreateIndexOptions - { - Background = true - })); - - var scopes = database.GetCollection(options.ScopesCollectionName); - await scopes.Indexes.CreateOneAsync(new CreateIndexModel( - Builders.IndexKeys.Ascending(scope => scope.Name), - new CreateIndexOptions - { - Unique = true - })); - - var tokens = database.GetCollection(options.TokensCollectionName); - await tokens.Indexes.CreateManyAsync(new[] - { - new CreateIndexModel( - Builders.IndexKeys.Ascending(token => token.ReferenceId), - new CreateIndexOptions - { - PartialFilterExpression = Builders.Filter.Exists(token => token.ReferenceId), - Unique = true - }), - - new CreateIndexModel( - Builders.IndexKeys - .Ascending(token => token.ApplicationId) - .Ascending(token => token.Status) - .Ascending(token => token.Subject) - .Ascending(token => token.Type), - new CreateIndexOptions - { - Background = true - }) - }); - } - - return _database = database; - } + database = _provider.GetService(); + } - finally - { - _semaphore.Release(); - } + if (database == null) + { + return new ValueTask(Task.FromException( + new InvalidOperationException(new StringBuilder() + .AppendLine("No suitable MongoDB database service can be found.") + .Append("To configure the OpenIddict MongoDB stores to use a specific database, use ") + .Append("'services.AddOpenIddict().AddCore().UseMongoDb().UseDatabase()' or register an ") + .Append("'IMongoDatabase' in the dependency injection container in 'ConfigureServices()'.") + .ToString()))); } - return new ValueTask(ExecuteAsync()); + return new ValueTask(database); } } } diff --git a/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs b/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs index 3ae3c5aa..7191d9fc 100644 --- a/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs +++ b/src/OpenIddict.MongoDb/OpenIddictMongoDbOptions.cs @@ -30,17 +30,6 @@ namespace OpenIddict.MongoDb /// public IMongoDatabase Database { get; set; } - /// - /// Gets or sets a boolean indicating whether automatic initialization should be disabled. - /// - public bool DisableInitialization { get; set; } - - /// - /// Gets or sets the maximal duration given to the MongoDB client to initialize - /// the database and register the indexes used by the OpenIddict entities. - /// - public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(30); - /// /// Gets or sets the name of the scopes collection (by default, openiddict.scopes). /// diff --git a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs index 124161e5..fe970838 100644 --- a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs +++ b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbBuilderTests.cs @@ -29,23 +29,6 @@ namespace OpenIddict.MongoDb.Tests Assert.Equal("services", exception.ParamName); } - [Fact] - public void DisableInitialization_InitializationIsCorrectlyDisabled() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.DisableInitialization(); - - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; - - Assert.True(options.DisableInitialization); - } - [Fact] public void ReplaceDefaultApplicationEntity_EntityIsCorrectlySet() { @@ -213,23 +196,6 @@ namespace OpenIddict.MongoDb.Tests Assert.Equal("custom_collection", options.ScopesCollectionName); } - [Fact] - public void SetInitializationTimeout_TimeoutIsCorrectlySet() - { - // Arrange - var services = CreateServices(); - var builder = CreateBuilder(services); - - // Act - builder.SetInitializationTimeout(TimeSpan.FromDays(42)); - - // Assert - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>().CurrentValue; - - Assert.Equal(TimeSpan.FromDays(42), options.InitializationTimeout); - } - [Theory] [InlineData(null)] [InlineData("")] diff --git a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs index 82e5ac48..8e3d5d09 100644 --- a/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs +++ b/test/OpenIddict.MongoDb.Tests/OpenIddictMongoDbContextTests.cs @@ -5,7 +5,6 @@ */ using System; -using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,7 +12,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using MongoDB.Driver; using Moq; -using OpenIddict.MongoDb.Models; using Xunit; namespace OpenIddict.MongoDb.Tests @@ -30,7 +28,7 @@ namespace OpenIddict.MongoDb.Tests var options = Mock.Of>(); var token = new CancellationToken(canceled: true); - using var context = new OpenIddictMongoDbContext(options, provider); + var context = new OpenIddictMongoDbContext(options, provider); // Act and assert var exception = await Assert.ThrowsAsync(async delegate @@ -41,76 +39,6 @@ namespace OpenIddict.MongoDb.Tests Assert.Equal(token, exception.CancellationToken); } - [Fact] - public async Task GetDatabaseAsync_ThrowsAnExceptionForNullOptions() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - - var database = GetDatabase(); - var options = Mock.Of>(); - - using var context = new OpenIddictMongoDbContext(options, provider); - - // Act and assert - var exception = await Assert.ThrowsAsync(async delegate - { - await context.GetDatabaseAsync(CancellationToken.None); - }); - - Assert.Equal("The OpenIddict MongoDB options cannot be retrieved.", exception.Message); - } - - [Fact] - public async Task GetDatabaseAsync_ThrowsAnExceptionForConcurrentCallsWhenInitializationTimesOut() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - - var manager = new Mock>(); - manager.Setup(mock => mock.CreateManyAsync(It.IsAny>>(), It.IsAny())) - .Returns(async delegate - { - await Task.Delay(TimeSpan.FromMilliseconds(1000)); - return new[] { string.Empty }; - }); - - var collection = new Mock>(); - collection.SetupGet(mock => mock.Indexes) - .Returns(manager.Object); - - var database = GetDatabase(); - database.Setup(mock => mock.GetCollection(It.IsAny(), It.IsAny())) - .Returns(collection.Object); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = database.Object, - InitializationTimeout = TimeSpan.FromMilliseconds(50) - }); - - using var context = new OpenIddictMongoDbContext(options, provider); - - // Act and assert - var exception = await Assert.ThrowsAsync(delegate - { - return Task.WhenAll( - context.GetDatabaseAsync(CancellationToken.None).AsTask(), - context.GetDatabaseAsync(CancellationToken.None).AsTask(), - context.GetDatabaseAsync(CancellationToken.None).AsTask(), - context.GetDatabaseAsync(CancellationToken.None).AsTask()); - }); - - Assert.Equal(new StringBuilder() - .AppendLine("The MongoDB database couldn't be initialized within a reasonable timeframe.") - .Append("Make sure that the MongoDB server is ready and accepts connections from this machine or use ") - .Append("'services.AddOpenIddict().AddCore().UseMongoDb().SetInitializationTimeout()' to adjust the timeout.") - .ToString(), exception.Message); - } - [Fact] public async Task GetDatabaseAsync_PrefersDatabaseRegisteredInOptionsToDatabaseRegisteredInDependencyInjectionContainer() { @@ -120,17 +48,17 @@ namespace OpenIddict.MongoDb.Tests var provider = services.BuildServiceProvider(); - var database = GetDatabase(); + var database = Mock.Of(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictMongoDbOptions { - Database = database.Object + Database = database }); - using var context = new OpenIddictMongoDbContext(options, provider); + var context = new OpenIddictMongoDbContext(options, provider); // Act and assert - Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None)); + Assert.Same(database, await context.GetDatabaseAsync(CancellationToken.None)); } [Fact] @@ -140,14 +68,13 @@ namespace OpenIddict.MongoDb.Tests var services = new ServiceCollection(); var provider = services.BuildServiceProvider(); - var database = GetDatabase(); var options = Mock.Of>( mock => mock.CurrentValue == new OpenIddictMongoDbOptions { Database = null }); - using var context = new OpenIddictMongoDbContext(options, provider); + var context = new OpenIddictMongoDbContext(options, provider); // Act and assert var exception = await Assert.ThrowsAsync(async delegate @@ -170,8 +97,8 @@ namespace OpenIddict.MongoDb.Tests var services = new ServiceCollection(); services.AddSingleton(Mock.Of()); - var database = GetDatabase(); - services.AddSingleton(database.Object); + var database = Mock.Of(); + services.AddSingleton(database); var provider = services.BuildServiceProvider(); @@ -181,138 +108,10 @@ namespace OpenIddict.MongoDb.Tests Database = null }); - using var context = new OpenIddictMongoDbContext(options, provider); + var context = new OpenIddictMongoDbContext(options, provider); // Act and assert - Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None)); - } - - [Fact] - public async Task GetDatabaseAsync_SkipsInitializationWhenDisabled() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - - var database = GetDatabase(); - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = database.Object, - DisableInitialization = true - }); - - using var context = new OpenIddictMongoDbContext(options, provider); - - // Act - await context.GetDatabaseAsync(CancellationToken.None); - - // Assert - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Never()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Never()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Never()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Never()); - } - - [Fact] - public async Task GetDatabaseAsync_ReturnsCachedDatabase() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - - var database = GetDatabase(); - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = database.Object - }); - - using var context = new OpenIddictMongoDbContext(options, provider); - - // Act and assert - Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None)); - Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None)); - - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - } - - [Fact] - public async Task GetDatabaseAsync_FailedInvocationDoesNotPreventFutureInvocations() - { - // Arrange - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - - var count = 0; - - var collection = new Mock>(); - collection.SetupGet(mock => mock.Indexes) - .Returns(Mock.Of>()); - - var database = GetDatabase(); - database.Setup(mock => mock.GetCollection(It.IsAny(), It.IsAny())) - .Callback(() => count++) - .Returns(delegate - { - if (count == 1) - { - throw new Exception(); - } - - return collection.Object; - }); - - var options = Mock.Of>( - mock => mock.CurrentValue == new OpenIddictMongoDbOptions - { - Database = database.Object - }); - - using var context = new OpenIddictMongoDbContext(options, provider); - - // Act and assert - await Assert.ThrowsAsync(async () => await context.GetDatabaseAsync(CancellationToken.None)); - Assert.Same(database.Object, await context.GetDatabaseAsync(CancellationToken.None)); - - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Exactly(2)); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - database.Verify(mock => mock.GetCollection(It.IsAny(), It.IsAny()), Times.Once()); - } - - private static Mock GetDatabase() - { - var applications = new Mock>(); - applications.SetupGet(mock => mock.Indexes) - .Returns(Mock.Of>()); - - var authorizations = new Mock>(); - authorizations.SetupGet(mock => mock.Indexes) - .Returns(Mock.Of>()); - - var scopes = new Mock>(); - scopes.SetupGet(mock => mock.Indexes) - .Returns(Mock.Of>()); - - var tokens = new Mock>(); - tokens.SetupGet(mock => mock.Indexes) - .Returns(Mock.Of>()); - - var database = new Mock(); - database.Setup(mock => mock.GetCollection(It.IsAny(), It.IsAny())) - .Returns(applications.Object); - database.Setup(mock => mock.GetCollection(It.IsAny(), It.IsAny())) - .Returns(authorizations.Object); - database.Setup(mock => mock.GetCollection(It.IsAny(), It.IsAny())) - .Returns(scopes.Object); - database.Setup(mock => mock.GetCollection(It.IsAny(), It.IsAny())) - .Returns(tokens.Object); - - return database; + Assert.Same(database, await context.GetDatabaseAsync(CancellationToken.None)); } } }