diff --git a/OpenIddict.sln b/OpenIddict.sln
index 786458c7..998f73a6 100644
--- a/OpenIddict.sln
+++ b/OpenIddict.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26228.4
+VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D544447C-D701-46BB-9A5B-C76C612A596B}"
EndProject
@@ -31,6 +31,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Tests", "test\Op
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Models", "src\OpenIddict.Models\OpenIddict.Models.csproj", "{0102A6CC-41A6-4B34-B49E-65AFE95882BB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.EntityFramework", "src\OpenIddict.EntityFramework\OpenIddict.EntityFramework.csproj", "{BF42CC6C-0B56-4F66-9866-18B8393F3C06}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.EntityFramework.Tests", "test\OpenIddict.EntityFramework.Tests\OpenIddict.EntityFramework.Tests.csproj", "{96325E37-9897-43AC-8408-7B17F58E8788}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -81,6 +85,14 @@ Global
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0102A6CC-41A6-4B34-B49E-65AFE95882BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF42CC6C-0B56-4F66-9866-18B8393F3C06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF42CC6C-0B56-4F66-9866-18B8393F3C06}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF42CC6C-0B56-4F66-9866-18B8393F3C06}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF42CC6C-0B56-4F66-9866-18B8393F3C06}.Release|Any CPU.Build.0 = Release|Any CPU
+ {96325E37-9897-43AC-8408-7B17F58E8788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {96325E37-9897-43AC-8408-7B17F58E8788}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {96325E37-9897-43AC-8408-7B17F58E8788}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {96325E37-9897-43AC-8408-7B17F58E8788}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -97,5 +109,10 @@ Global
{8B4B0CCC-711B-4F9D-9DE6-DD32BDD3BCCA} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{3E2FBDB3-DC82-4E97-8EBC-CC8B279110FF} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{0102A6CC-41A6-4B34-B49E-65AFE95882BB} = {D544447C-D701-46BB-9A5B-C76C612A596B}
+ {BF42CC6C-0B56-4F66-9866-18B8393F3C06} = {D544447C-D701-46BB-9A5B-C76C612A596B}
+ {96325E37-9897-43AC-8408-7B17F58E8788} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616}
EndGlobalSection
EndGlobal
diff --git a/build/dependencies.props b/build/dependencies.props
index 303cd30f..7b443b02 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -5,6 +5,7 @@
1.0.0
1.0.1
2.0.0
+ 6.1.3
10.3.0
1.6.0
4.7.63
diff --git a/build/repo.props b/build/repo.props
new file mode 100644
index 00000000..b8e3b559
--- /dev/null
+++ b/build/repo.props
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj
new file mode 100644
index 00000000..2d72dcc4
--- /dev/null
+++ b/src/OpenIddict.EntityFramework/OpenIddict.EntityFramework.csproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+ net461
+
+
+
+ Entity Framework 6.x stores for OpenIddict.
+ Kévin Chalet
+ aspnetcore;authentication;jwt;openidconnect;openiddict;security
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs
new file mode 100644
index 00000000..da835d5f
--- /dev/null
+++ b/src/OpenIddict.EntityFramework/OpenIddictExtensions.cs
@@ -0,0 +1,264 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Data.Entity;
+using System.Data.Entity.Infrastructure.Annotations;
+using System.Diagnostics;
+using System.Reflection;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using OpenIddict.Core;
+using OpenIddict.EntityFramework;
+using OpenIddict.Models;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class OpenIddictExtensions
+ {
+ ///
+ /// Registers the Entity Framework 6.x stores. Note: when using the Entity Framework stores,
+ /// the application MUST be manually registered in the DI container and
+ /// the entities MUST be derived from the models contained in the OpenIddict.Models package.
+ ///
+ /// The services builder used by OpenIddict to register new services.
+ /// The .
+ public static OpenIddictBuilder AddEntityFrameworkStores([NotNull] this OpenIddictBuilder builder)
+ where TContext : DbContext
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ Debug.Assert(builder.ApplicationType != null &&
+ builder.AuthorizationType != null &&
+ builder.ScopeType != null &&
+ builder.TokenType != null, "The entity types exposed by OpenIddictBuilder shouldn't be null.");
+
+ var application = FindGenericBaseType(builder.ApplicationType, typeof(OpenIddictApplication<,,>));
+ if (application == null)
+ {
+ throw new InvalidOperationException("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictApplication entity.");
+ }
+
+ var authorization = FindGenericBaseType(builder.AuthorizationType, typeof(OpenIddictAuthorization<,,>));
+ if (authorization == null)
+ {
+ throw new InvalidOperationException("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictAuthorization entity.");
+ }
+
+ var scope = FindGenericBaseType(builder.ScopeType, typeof(OpenIddictScope<>));
+ if (scope == null)
+ {
+ throw new InvalidOperationException("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictScope entity.");
+ }
+
+ var token = FindGenericBaseType(builder.TokenType, typeof(OpenIddictToken<,,>));
+ if (token == null)
+ {
+ throw new InvalidOperationException("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictToken entity.");
+ }
+
+ var converter = TypeDescriptor.GetConverter(application.GenericTypeArguments[0]);
+ if (converter == null || !converter.CanConvertFrom(typeof(string)) ||
+ !converter.CanConvertTo(typeof(string)))
+ {
+ throw new InvalidOperationException("The specified entity key type is not supported.");
+ }
+
+ // Register the application store in the DI container.
+ builder.Services.TryAddScoped(
+ typeof(IOpenIddictApplicationStore<>).MakeGenericType(builder.ApplicationType),
+ typeof(OpenIddictApplicationStore<,,,,>).MakeGenericType(
+ /* TApplication: */ builder.ApplicationType,
+ /* TAuthorization: */ builder.AuthorizationType,
+ /* TToken: */ builder.TokenType,
+ /* TContext: */ typeof(TContext),
+ /* TKey: */ application.GenericTypeArguments[0]));
+
+ // Register the authorization store in the DI container.
+ builder.Services.TryAddScoped(
+ typeof(IOpenIddictAuthorizationStore<>).MakeGenericType(builder.AuthorizationType),
+ typeof(OpenIddictAuthorizationStore<,,,,>).MakeGenericType(
+ /* TAuthorization: */ builder.AuthorizationType,
+ /* TApplication: */ builder.ApplicationType,
+ /* TToken: */ builder.TokenType,
+ /* TContext: */ typeof(TContext),
+ /* TKey: */ authorization.GenericTypeArguments[0]));
+
+ // Register the scope store in the DI container.
+ builder.Services.TryAddScoped(
+ typeof(IOpenIddictScopeStore<>).MakeGenericType(builder.ScopeType),
+ typeof(OpenIddictScopeStore<,,>).MakeGenericType(
+ /* TScope: */ builder.ScopeType,
+ /* TContext: */ typeof(TContext),
+ /* TKey: */ scope.GenericTypeArguments[0]));
+
+ // Register the token store in the DI container.
+ builder.Services.TryAddScoped(
+ typeof(IOpenIddictTokenStore<>).MakeGenericType(builder.TokenType),
+ typeof(OpenIddictTokenStore<,,,,>).MakeGenericType(
+ /* TToken: */ builder.TokenType,
+ /* TApplication: */ builder.ApplicationType,
+ /* TAuthorization: */ builder.AuthorizationType,
+ /* TContext: */ typeof(TContext),
+ /* TKey: */ token.GenericTypeArguments[0]));
+
+ return builder;
+ }
+
+ ///
+ /// Registers the OpenIddict entity sets in the Entity Framework context
+ /// using the default OpenIddict models and the default key type (string).
+ ///
+ /// The builder used to configure the Entity Framework context.
+ /// The Entity Framework context builder.
+ public static DbModelBuilder UseOpenIddict([NotNull] this DbModelBuilder builder)
+ {
+ return builder.UseOpenIddict();
+ }
+
+ ///
+ /// Registers the OpenIddict entity sets in the Entity Framework context
+ /// using the specified entities and the specified key type.
+ /// Note: using this method requires creating non-generic derived classes
+ /// for all the OpenIddict entities (application, authorization, scope, token).
+ ///
+ /// The builder used to configure the Entity Framework context.
+ /// The Entity Framework context builder.
+ public static DbModelBuilder UseOpenIddict([NotNull] this DbModelBuilder builder)
+ where TApplication : OpenIddictApplication, new()
+ where TAuthorization : OpenIddictAuthorization, new()
+ where TScope : OpenIddictScope, new()
+ where TToken : OpenIddictToken, new()
+ where TKey : IEquatable
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ // Note: unlike Entity Framework Core 1.x/2.x, Entity Framework 6.x
+ // always throws an exception when using generic types as entity types.
+ // To ensure a better exception is thrown, a manual check is made here.
+ if (typeof(TApplication).GetTypeInfo().IsGenericType)
+ {
+ throw new InvalidOperationException("The application entity cannot be a generic type. " +
+ "Consider creating a non-generic derived class.");
+ }
+
+ if (typeof(TAuthorization).GetTypeInfo().IsGenericType)
+ {
+ throw new InvalidOperationException("The authorization entity cannot be a generic type. " +
+ "Consider creating a non-generic derived class.");
+ }
+
+ if (typeof(TScope).GetTypeInfo().IsGenericType)
+ {
+ throw new InvalidOperationException("The scope entity cannot be a generic type. " +
+ "Consider creating a non-generic derived class.");
+ }
+
+ if (typeof(TToken).GetTypeInfo().IsGenericType)
+ {
+ throw new InvalidOperationException("The scope entity cannot be a generic type. " +
+ "Consider creating a non-generic derived class.");
+ }
+
+ // Warning: optional foreign keys MUST NOT be added as CLR properties because
+ // Entity Framework would throw an exception due to the TKey generic parameter
+ // being non-nullable when using value types like short, int, long or Guid.
+
+ // Configure the TApplication entity.
+ builder.Entity()
+ .HasKey(application => application.Id);
+
+ builder.Entity()
+ .Property(application => application.ClientId)
+ .HasMaxLength(450)
+ .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
+
+ builder.Entity()
+ .HasMany(application => application.Authorizations)
+ .WithOptional(authorization => authorization.Application)
+ .Map(association => association.MapKey("ApplicationId"));
+
+ builder.Entity()
+ .HasMany(application => application.Tokens)
+ .WithOptional(token => token.Application)
+ .Map(association => association.MapKey("ApplicationId"));
+
+ builder.Entity()
+ .ToTable("OpenIddictApplications");
+
+ // Configure the TAuthorization entity.
+ builder.Entity()
+ .HasKey(authorization => authorization.Id);
+
+ builder.Entity()
+ .HasMany(application => application.Tokens)
+ .WithOptional(token => token.Authorization)
+ .Map(association => association.MapKey("AuthorizationId"));
+
+ builder.Entity()
+ .ToTable("OpenIddictAuthorizations");
+
+ // Configure the TScope entity.
+ builder.Entity()
+ .HasKey(scope => scope.Id);
+
+ builder.Entity()
+ .ToTable("OpenIddictScopes");
+
+ // Configure the TToken entity.
+ builder.Entity()
+ .HasKey(token => token.Id);
+
+ builder.Entity()
+ .Property(token => token.Hash)
+ .HasMaxLength(450)
+ .HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
+
+ builder.Entity()
+ .ToTable("OpenIddictTokens");
+
+ return builder;
+ }
+
+ private static TypeInfo FindGenericBaseType(Type type, Type definition)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ if (definition == null)
+ {
+ throw new ArgumentNullException(nameof(definition));
+ }
+
+ for (var candidate = type.GetTypeInfo(); candidate != null; candidate = candidate.BaseType?.GetTypeInfo())
+ {
+ if (candidate.IsGenericType && candidate.GetGenericTypeDefinition() == definition)
+ {
+ return candidate;
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
new file mode 100644
index 00000000..89629b5c
--- /dev/null
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
@@ -0,0 +1,225 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Data.Entity;
+using System.Data.Entity.Infrastructure;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using OpenIddict.Core;
+using OpenIddict.Models;
+
+namespace OpenIddict.EntityFramework
+{
+ ///
+ /// Provides methods allowing to manage the applications stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ public class OpenIddictApplicationStore : OpenIddictApplicationStore
+ where TContext : DbContext
+ {
+ public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the applications stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictApplicationStore : OpenIddictApplicationStore,
+ OpenIddictAuthorization,
+ OpenIddictToken, TContext, TKey>
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictApplicationStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the applications stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Application entity.
+ /// The type of the Authorization entity.
+ /// The type of the Token entity.
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictApplicationStore :
+ OpenIddictApplicationStore
+ where TApplication : OpenIddictApplication, new()
+ where TAuthorization : OpenIddictAuthorization, new()
+ where TToken : OpenIddictToken, new()
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictApplicationStore([NotNull] TContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Context = context;
+ }
+
+ ///
+ /// Gets the database context associated with the current store.
+ ///
+ protected virtual TContext Context { get; }
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Applications => Context.Set();
+
+ ///
+ /// Creates a new application.
+ ///
+ /// The application to create.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the application.
+ ///
+ public override async Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ Applications.Add(application);
+
+ await Context.SaveChangesAsync(cancellationToken);
+
+ return application;
+ }
+
+ ///
+ /// Creates a new application.
+ ///
+ /// The application descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the application.
+ ///
+ public override Task CreateAsync([NotNull] OpenIddictApplicationDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ var application = new TApplication
+ {
+ ClientId = descriptor.ClientId,
+ ClientSecret = descriptor.ClientSecret,
+ DisplayName = descriptor.DisplayName,
+ LogoutRedirectUri = descriptor.LogoutRedirectUri,
+ RedirectUri = descriptor.RedirectUri,
+ Type = descriptor.Type
+ };
+
+ return CreateAsync(application, cancellationToken);
+ }
+
+ ///
+ /// Removes an existing application.
+ ///
+ /// The application to delete.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ Applications.Remove(application);
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (DbUpdateConcurrencyException) { }
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the single element returned when executing the specified query.
+ ///
+ public override Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Applications).SingleOrDefaultAsync(cancellationToken);
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the elements returned when executing the specified query.
+ ///
+ public override Task ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Applications).ToArrayAsync(cancellationToken);
+ }
+
+ ///
+ /// Updates an existing application.
+ ///
+ /// The application to update.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public override async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ Applications.Attach(application);
+ Context.Entry(application).State = EntityState.Modified;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (DbUpdateConcurrencyException) { }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
new file mode 100644
index 00000000..af65eca1
--- /dev/null
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
@@ -0,0 +1,211 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Data.Entity;
+using System.Data.Entity.Infrastructure;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using OpenIddict.Core;
+using OpenIddict.Models;
+
+namespace OpenIddict.EntityFramework
+{
+ ///
+ /// Provides methods allowing to manage the authorizations stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ public class OpenIddictAuthorizationStore : OpenIddictAuthorizationStore
+ where TContext : DbContext
+ {
+ public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the authorizations stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictAuthorizationStore : OpenIddictAuthorizationStore,
+ OpenIddictApplication,
+ OpenIddictToken, TContext, TKey>
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictAuthorizationStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the authorizations stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Authorization entity.
+ /// The type of the Application entity.
+ /// The type of the Token entity.
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictAuthorizationStore :
+ OpenIddictAuthorizationStore
+ where TAuthorization : OpenIddictAuthorization, new()
+ where TApplication : OpenIddictApplication, new()
+ where TToken : OpenIddictToken, new()
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictAuthorizationStore([NotNull] TContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Context = context;
+ }
+
+ ///
+ /// Gets the database context associated with the current store.
+ ///
+ protected virtual TContext Context { get; }
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Applications => Context.Set();
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Authorizations => Context.Set();
+
+ ///
+ /// Creates a new authorization.
+ ///
+ /// The authorization to create.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
+ ///
+ public override async Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
+ {
+ if (authorization == null)
+ {
+ throw new ArgumentNullException(nameof(authorization));
+ }
+
+ Authorizations.Add(authorization);
+
+ await Context.SaveChangesAsync(cancellationToken);
+
+ return authorization;
+ }
+
+ ///
+ /// Creates a new authorization.
+ ///
+ /// The authorization descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
+ ///
+ public override async Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ var authorization = new TAuthorization
+ {
+ Scope = string.Join(" ", descriptor.Scopes),
+ Subject = descriptor.Subject
+ };
+
+ // Bind the authorization to the specified application, if applicable.
+ if (!string.IsNullOrEmpty(descriptor.ApplicationId))
+ {
+ var key = ConvertIdentifierFromString(descriptor.ApplicationId);
+
+ var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ if (application == null)
+ {
+ throw new InvalidOperationException("The application associated with the authorization cannot be found.");
+ }
+
+ authorization.Application = application;
+ }
+
+ return await CreateAsync(authorization, cancellationToken);
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the single element returned when executing the specified query.
+ ///
+ public override Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Authorizations).SingleOrDefaultAsync(cancellationToken);
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the elements returned when executing the specified query.
+ ///
+ public override Task ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Authorizations).ToArrayAsync(cancellationToken);
+ }
+
+ ///
+ /// Updates an existing authorization.
+ ///
+ /// The authorization to update.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public override async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
+ {
+
+ Authorizations.Attach(authorization);
+ Context.Entry(authorization).State = EntityState.Modified;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (DbUpdateConcurrencyException) { }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
new file mode 100644
index 00000000..b960fba4
--- /dev/null
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
@@ -0,0 +1,113 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Data.Entity;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using OpenIddict.Models;
+
+namespace OpenIddict.EntityFramework
+{
+ ///
+ /// Provides methods allowing to manage the scopes stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ public class OpenIddictScopeStore : OpenIddictScopeStore
+ where TContext : DbContext
+ {
+ public OpenIddictScopeStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the scopes stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictScopeStore : OpenIddictScopeStore, TContext, TKey>
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictScopeStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the scopes stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Scope entity.
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictScopeStore : Core.OpenIddictScopeStore
+ where TScope : OpenIddictScope, new()
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictScopeStore([NotNull] TContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Context = context;
+ }
+
+ ///
+ /// Gets the database context associated with the current store.
+ ///
+ protected virtual TContext Context { get; }
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Scopes => Context.Set();
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the single element returned when executing the specified query.
+ ///
+ public override Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Scopes).SingleOrDefaultAsync(cancellationToken);
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the elements returned when executing the specified query.
+ ///
+ public override Task ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Scopes).ToArrayAsync(cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
new file mode 100644
index 00000000..886200b7
--- /dev/null
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
@@ -0,0 +1,347 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Data.Entity;
+using System.Data.Entity.Infrastructure;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using OpenIddict.Core;
+using OpenIddict.Models;
+
+namespace OpenIddict.EntityFramework
+{
+ ///
+ /// Provides methods allowing to manage the tokens stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ public class OpenIddictTokenStore : OpenIddictTokenStore
+ where TContext : DbContext
+ {
+ public OpenIddictTokenStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the tokens stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictTokenStore : OpenIddictTokenStore,
+ OpenIddictApplication,
+ OpenIddictAuthorization, TContext, TKey>
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictTokenStore([NotNull] TContext context) : base(context) { }
+ }
+
+ ///
+ /// Provides methods allowing to manage the tokens stored in a database.
+ /// Note: this class can only be used with the default OpenIddict entities.
+ ///
+ /// The type of the Token entity.
+ /// The type of the Application entity.
+ /// The type of the Authorization entity.
+ /// The type of the Entity Framework database context.
+ /// The type of the entity primary keys.
+ public class OpenIddictTokenStore :
+ OpenIddictTokenStore
+ where TToken : OpenIddictToken, new()
+ where TApplication : OpenIddictApplication, new()
+ where TAuthorization : OpenIddictAuthorization, new()
+ where TContext : DbContext
+ where TKey : IEquatable
+ {
+ public OpenIddictTokenStore([NotNull] TContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Context = context;
+ }
+
+ ///
+ /// Gets the database context associated with the current store.
+ ///
+ protected virtual TContext Context { get; }
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Applications => Context.Set();
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Authorizations => Context.Set();
+
+ ///
+ /// Gets the database set corresponding to the entity.
+ ///
+ protected DbSet Tokens => Context.Set();
+
+ ///
+ /// Creates a new token.
+ ///
+ /// The token to create.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the token.
+ ///
+ public override async Task CreateAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ Tokens.Add(token);
+
+ await Context.SaveChangesAsync(cancellationToken);
+
+ return token;
+ }
+
+ ///
+ /// Creates a new token, which is associated with a particular subject.
+ ///
+ /// The token descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation, whose result returns the token.
+ ///
+ public override async Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ var token = new TToken
+ {
+ Ciphertext = descriptor.Ciphertext,
+ CreationDate = descriptor.CreationDate,
+ ExpirationDate = descriptor.ExpirationDate,
+ Hash = descriptor.Hash,
+ Subject = descriptor.Subject,
+ Type = descriptor.Type
+ };
+
+ // Bind the token to the specified client application, if applicable.
+ if (!string.IsNullOrEmpty(descriptor.ApplicationId))
+ {
+ var key = ConvertIdentifierFromString(descriptor.ApplicationId);
+
+ var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ if (application == null)
+ {
+ throw new InvalidOperationException("The application associated with the token cannot be found.");
+ }
+
+ token.Application = application;
+ }
+
+ // Bind the token to the specified authorization, if applicable.
+ if (!string.IsNullOrEmpty(descriptor.AuthorizationId))
+ {
+ var key = ConvertIdentifierFromString(descriptor.AuthorizationId);
+
+ var authorization = await Authorizations.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ if (authorization == null)
+ {
+ throw new InvalidOperationException("The authorization associated with the token cannot be found.");
+ }
+
+ token.Authorization = authorization;
+ }
+
+ return await CreateAsync(token, cancellationToken);
+ }
+
+ ///
+ /// Removes a token.
+ ///
+ /// The token to delete.
+ /// The that can be used to abort the operation.
+ /// A that can be used to monitor the asynchronous operation.
+ public override async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ Tokens.Remove(token);
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (DbUpdateConcurrencyException) { }
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the single element returned when executing the specified query.
+ ///
+ public override Task GetAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Tokens).SingleOrDefaultAsync(cancellationToken);
+ }
+
+ ///
+ /// Executes the specified query.
+ ///
+ /// The result type.
+ /// The query to execute.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns all the elements returned when executing the specified query.
+ ///
+ public override Task ListAsync([NotNull] Func, IQueryable> query, CancellationToken cancellationToken)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
+
+ return query.Invoke(Tokens).ToArrayAsync(cancellationToken);
+ }
+
+ ///
+ /// Sets the authorization associated with a token.
+ ///
+ /// The token.
+ /// The unique identifier associated with the authorization.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public override async Task SetAuthorizationAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ if (!string.IsNullOrEmpty(identifier))
+ {
+ var key = ConvertIdentifierFromString(identifier);
+
+ var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Id.Equals(key));
+ if (authorization == null)
+ {
+ throw new InvalidOperationException("The authorization associated with the token cannot be found.");
+ }
+
+ authorization.Tokens.Add(token);
+ }
+
+ else
+ {
+ var key = await GetIdAsync(token, cancellationToken);
+
+ // Try to retrieve the authorization associated with the token.
+ // If none can be found, assume that no authorization is attached.
+ var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
+ if (authorization != null)
+ {
+ authorization.Tokens.Remove(token);
+ }
+ }
+ }
+
+ ///
+ /// Sets the client application associated with a token.
+ ///
+ /// The token.
+ /// The unique identifier associated with the client application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public override async Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ if (!string.IsNullOrEmpty(identifier))
+ {
+ var key = ConvertIdentifierFromString(identifier);
+
+ var application = await Applications.SingleOrDefaultAsync(element => element.Id.Equals(key));
+ if (application == null)
+ {
+ throw new InvalidOperationException("The application associated with the token cannot be found.");
+ }
+
+ application.Tokens.Add(token);
+ }
+
+ else
+ {
+ var key = await GetIdAsync(token, cancellationToken);
+
+ // Try to retrieve the application associated with the token.
+ // If none can be found, assume that no application is attached.
+ var application = await Applications.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
+ if (application != null)
+ {
+ application.Tokens.Remove(token);
+ }
+ }
+ }
+
+ ///
+ /// Updates an existing token.
+ ///
+ /// The token to update.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public override async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ Tokens.Attach(token);
+ Context.Entry(token).State = EntityState.Modified;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (DbUpdateConcurrencyException) { }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs b/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs
index fd9fd1cb..6ef5830b 100644
--- a/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs
+++ b/test/OpenIddict.Core.Tests/OpenIddictBuilderTests.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
diff --git a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs
index b740f2ff..edb2ccfa 100644
--- a/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Core.Tests/OpenIddictExtensionsTests.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
using Xunit;
diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddict.EntityFramework.Tests.csproj b/test/OpenIddict.EntityFramework.Tests/OpenIddict.EntityFramework.Tests.csproj
new file mode 100644
index 00000000..34576c2c
--- /dev/null
+++ b/test/OpenIddict.EntityFramework.Tests/OpenIddict.EntityFramework.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+ net461
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs
new file mode 100644
index 00000000..45d9007c
--- /dev/null
+++ b/test/OpenIddict.EntityFramework.Tests/OpenIddictExtensionsTests.cs
@@ -0,0 +1,167 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.Data.Entity;
+using Microsoft.Extensions.DependencyInjection;
+using OpenIddict.EntityFramework;
+using OpenIddict.Models;
+using Xunit;
+
+namespace OpenIddict.EntityFrameworkCore.Tests
+{
+ public class OpenIddictExtensionsTests
+ {
+ [Fact]
+ public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidApplicationEntity()
+ {
+ // Arrange
+ var builder = new OpenIddictBuilder(new ServiceCollection())
+ {
+ ApplicationType = typeof(object)
+ };
+
+ // Act and assert
+ var exception = Assert.Throws(delegate
+ {
+ builder.AddEntityFrameworkStores();
+ });
+
+ Assert.Equal("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictApplication entity.", exception.Message);
+ }
+
+ [Fact]
+ public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidAuthorizationEntity()
+ {
+ // Arrange
+ var builder = new OpenIddictBuilder(new ServiceCollection())
+ {
+ AuthorizationType = typeof(object)
+ };
+
+ // Act and assert
+ var exception = Assert.Throws(delegate
+ {
+ builder.AddEntityFrameworkStores();
+ });
+
+ Assert.Equal("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictAuthorization entity.", exception.Message);
+ }
+
+ [Fact]
+ public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidScopeEntity()
+ {
+ // Arrange
+ var builder = new OpenIddictBuilder(new ServiceCollection())
+ {
+ ScopeType = typeof(object)
+ };
+
+ // Act and assert
+ var exception = Assert.Throws(delegate
+ {
+ builder.AddEntityFrameworkStores();
+ });
+
+ Assert.Equal("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictScope entity.", exception.Message);
+ }
+
+ [Fact]
+ public void AddEntityFrameworkStores_ThrowsAnExceptionForInvalidTokenEntity()
+ {
+ // Arrange
+ var builder = new OpenIddictBuilder(new ServiceCollection())
+ {
+ TokenType = typeof(object)
+ };
+
+ // Act and assert
+ var exception = Assert.Throws(delegate
+ {
+ builder.AddEntityFrameworkStores();
+ });
+
+ Assert.Equal("The Entity Framework stores can only be used " +
+ "with the built-in OpenIddictToken entity.", exception.Message);
+ }
+
+ [Theory]
+ [InlineData(typeof(OpenIddictApplicationStore))]
+ [InlineData(typeof(OpenIddictAuthorizationStore))]
+ [InlineData(typeof(OpenIddictScopeStore))]
+ [InlineData(typeof(OpenIddictTokenStore))]
+ public void AddEntityFrameworkStores_RegistersEntityFrameworkStores(Type type)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var builder = new OpenIddictBuilder(services);
+
+ // Act
+ builder.AddEntityFrameworkStores();
+
+ // Assert
+ Assert.Contains(services, service => service.ImplementationType == type);
+ }
+
+ [Theory]
+ [InlineData(typeof(OpenIddictApplicationStore, OpenIddictAuthorization, OpenIddictToken, DbContext, Guid>))]
+ [InlineData(typeof(OpenIddictAuthorizationStore, OpenIddictApplication, OpenIddictToken, DbContext, Guid>))]
+ [InlineData(typeof(OpenIddictScopeStore, DbContext, Guid>))]
+ [InlineData(typeof(OpenIddictTokenStore, OpenIddictApplication, OpenIddictAuthorization, DbContext, Guid>))]
+ public void AddEntityFrameworkStores_KeyTypeIsInferredFromEntities(Type type)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+
+ var builder = new OpenIddictBuilder(services)
+ {
+ ApplicationType = typeof(OpenIddictApplication),
+ AuthorizationType = typeof(OpenIddictAuthorization),
+ ScopeType = typeof(OpenIddictScope),
+ TokenType = typeof(OpenIddictToken)
+ };
+
+ // Act
+ builder.AddEntityFrameworkStores();
+
+ // Assert
+ Assert.Contains(services, service => service.ImplementationType == type);
+ }
+
+ [Theory]
+ [InlineData(typeof(OpenIddictApplicationStore))]
+ [InlineData(typeof(OpenIddictAuthorizationStore))]
+ [InlineData(typeof(OpenIddictScopeStore))]
+ [InlineData(typeof(OpenIddictTokenStore))]
+ public void AddEntityFrameworkStores_DefaultEntitiesCanBeReplaced(Type type)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+
+ var builder = new OpenIddictBuilder(services)
+ {
+ ApplicationType = typeof(CustomApplication),
+ AuthorizationType = typeof(CustomAuthorization),
+ ScopeType = typeof(CustomScope),
+ TokenType = typeof(CustomToken)
+ };
+
+ // Act
+ builder.AddEntityFrameworkStores();
+
+ // Assert
+ Assert.Contains(services, service => service.ImplementationType == type);
+ }
+
+ public class CustomApplication : OpenIddictApplication { }
+ public class CustomAuthorization : OpenIddictAuthorization { }
+ public class CustomScope : OpenIddictScope { }
+ public class CustomToken : OpenIddictToken { }
+ }
+}
diff --git a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs
index 7a59825f..00b9f939 100644
--- a/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.EntityFrameworkCore.Tests/OpenIddictExtensionsTests.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Models;
diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs
index 618711c2..e6674752 100644
--- a/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Mvc.Tests/OpenIddictExtensionsTests.cs
@@ -1,4 +1,10 @@
-using Microsoft.AspNetCore.Mvc;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs
index 77964abc..42f4182d 100644
--- a/test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs
+++ b/test/OpenIddict.Mvc.Tests/OpenIddictModelBinderTests.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Primitives;
diff --git a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
index df3e6319..d727beab 100644
--- a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using AspNet.Security.OpenIdConnect.Primitives;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
index a7cb5962..6424327d 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
@@ -1,6 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
using System.IO;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs
index 962bf052..9be4ff26 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Discovery.cs
@@ -1,4 +1,9 @@
-using System.Linq;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
@@ -6,6 +11,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Facebook;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using OpenIddict.Core;
using Xunit;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
index dacb7958..ac1313cc 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
@@ -1,4 +1,9 @@
-using System.Collections.Generic;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
index dcc6374f..77de1e21 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
@@ -1,4 +1,9 @@
-using System.Linq;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs
index da6a3d46..84846303 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Revocation.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
index fba55324..76cfac78 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
@@ -1,6 +1,10 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
index c4c288d6..94972245 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
@@ -1,4 +1,9 @@
-using System.Linq;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
@@ -8,7 +13,6 @@ using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
-using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Tests
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs
index be80b2a3..883f383d 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Userinfo.cs
@@ -1,4 +1,10 @@
-using System.Threading.Tasks;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Primitives;
using Xunit;
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.cs
index 153ca18a..38e0b7e7 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.cs
@@ -1,4 +1,10 @@
-using System;
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;