Browse Source

Introduce NHibernate 5.x stores

pull/758/head
Kévin Chalet 7 years ago
committed by GitHub
parent
commit
14c229d2ce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      OpenIddict.sln
  2. 1
      build/dependencies.props
  3. 15
      src/OpenIddict.NHibernate.Models/OpenIddict.NHibernate.Models.csproj
  4. 114
      src/OpenIddict.NHibernate.Models/OpenIddictApplication.cs
  5. 87
      src/OpenIddict.NHibernate.Models/OpenIddictAuthorization.cs
  6. 71
      src/OpenIddict.NHibernate.Models/OpenIddictScope.cs
  7. 107
      src/OpenIddict.NHibernate.Models/OpenIddictToken.cs
  8. 27
      src/OpenIddict.NHibernate/IOpenIddictNHibernateContext.cs
  9. 101
      src/OpenIddict.NHibernate/Mappings/OpenIddictApplicationMapping.cs
  10. 84
      src/OpenIddict.NHibernate/Mappings/OpenIddictAuthorizationMapping.cs
  11. 63
      src/OpenIddict.NHibernate/Mappings/OpenIddictScopeMapping.cs
  12. 85
      src/OpenIddict.NHibernate/Mappings/OpenIddictTokenMapping.cs
  13. 29
      src/OpenIddict.NHibernate/OpenIddict.NHibernate.csproj
  14. 109
      src/OpenIddict.NHibernate/OpenIddictNHibernateBuilder.cs
  15. 125
      src/OpenIddict.NHibernate/OpenIddictNHibernateContext.cs
  16. 86
      src/OpenIddict.NHibernate/OpenIddictNHibernateExtensions.cs
  17. 74
      src/OpenIddict.NHibernate/OpenIddictNHibernateHelpers.cs
  18. 22
      src/OpenIddict.NHibernate/OpenIddictNHibernateOptions.cs
  19. 67
      src/OpenIddict.NHibernate/Resolvers/OpenIddictApplicationStoreResolver.cs
  20. 67
      src/OpenIddict.NHibernate/Resolvers/OpenIddictAuthorizationStoreResolver.cs
  21. 65
      src/OpenIddict.NHibernate/Resolvers/OpenIddictScopeStoreResolver.cs
  22. 67
      src/OpenIddict.NHibernate/Resolvers/OpenIddictTokenStoreResolver.cs
  23. 982
      src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs
  24. 935
      src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs
  25. 712
      src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs
  26. 1111
      src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs
  27. 26
      test/OpenIddict.NHibernate.Tests/OpenIddict.NHibernate.Tests.csproj
  28. 122
      test/OpenIddict.NHibernate.Tests/OpenIddictNHibernateBuilderTests.cs
  29. 240
      test/OpenIddict.NHibernate.Tests/OpenIddictNHibernateContextTests.cs
  30. 102
      test/OpenIddict.NHibernate.Tests/OpenIddictNHibernateExtensionsTests.cs
  31. 83
      test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs
  32. 83
      test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs
  33. 83
      test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs
  34. 83
      test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs

21
OpenIddict.sln

@ -67,6 +67,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{D8075F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.Extensions", "shared\OpenIddict.Extensions\OpenIddict.Extensions.csproj", "{B90761B9-7582-44CB-AB0D-3C4058693227}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.NHibernate", "src\OpenIddict.NHibernate\OpenIddict.NHibernate.csproj", "{17BFF448-F11F-40D6-B658-BD81B306D2CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenIddict.NHibernate.Models", "src\OpenIddict.NHibernate.Models\OpenIddict.NHibernate.Models.csproj", "{22882DA6-6A5F-4E48-8BDC-7248B1DE5D14}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIddict.NHibernate.Tests", "test\OpenIddict.NHibernate.Tests\OpenIddict.NHibernate.Tests.csproj", "{B99BCBEC-9771-4C68-96E2-1A54E9BC432D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -165,6 +171,18 @@ Global
{B90761B9-7582-44CB-AB0D-3C4058693227}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B90761B9-7582-44CB-AB0D-3C4058693227}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B90761B9-7582-44CB-AB0D-3C4058693227}.Release|Any CPU.Build.0 = Release|Any CPU
{17BFF448-F11F-40D6-B658-BD81B306D2CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17BFF448-F11F-40D6-B658-BD81B306D2CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17BFF448-F11F-40D6-B658-BD81B306D2CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17BFF448-F11F-40D6-B658-BD81B306D2CA}.Release|Any CPU.Build.0 = Release|Any CPU
{22882DA6-6A5F-4E48-8BDC-7248B1DE5D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22882DA6-6A5F-4E48-8BDC-7248B1DE5D14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22882DA6-6A5F-4E48-8BDC-7248B1DE5D14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22882DA6-6A5F-4E48-8BDC-7248B1DE5D14}.Release|Any CPU.Build.0 = Release|Any CPU
{B99BCBEC-9771-4C68-96E2-1A54E9BC432D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B99BCBEC-9771-4C68-96E2-1A54E9BC432D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B99BCBEC-9771-4C68-96E2-1A54E9BC432D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B99BCBEC-9771-4C68-96E2-1A54E9BC432D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -193,6 +211,9 @@ Global
{8FACE85E-EF8F-4AB1-85DD-4010D5E2165D} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{27F603EF-D335-445B-9443-6B5A6CA3C110} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
{B90761B9-7582-44CB-AB0D-3C4058693227} = {D8075F1F-6257-463B-B481-BDC7C5ABA292}
{17BFF448-F11F-40D6-B658-BD81B306D2CA} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{22882DA6-6A5F-4E48-8BDC-7248B1DE5D14} = {D544447C-D701-46BB-9A5B-C76C612A596B}
{B99BCBEC-9771-4C68-96E2-1A54E9BC432D} = {5FC71D6A-A994-4F62-977F-88A7D25379D7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A710059F-0466-4D48-9B3A-0EF4F840B616}

1
build/dependencies.props

@ -16,6 +16,7 @@
<MoqVersion>4.7.63</MoqVersion>
<NetStandardImplicitPackageVersion>2.0.0</NetStandardImplicitPackageVersion>
<NetStandardLibraryNetFrameworkVersion>2.0.0</NetStandardLibraryNetFrameworkVersion>
<NHibernateVersion>5.2.2</NHibernateVersion>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<TasksExtensionsVersion>4.4.0</TasksExtensionsVersion>
<TestSdkVersion>15.3.0</TestSdkVersion>

15
src/OpenIddict.NHibernate.Models/OpenIddict.NHibernate.Models.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\packages.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Description>Relational entities for the NHibernate 5.x stores.</Description>
<Authors>Kévin Chalet</Authors>
<PackageTags>aspnetcore;authentication;jwt;openidconnect;openiddict;security</PackageTags>
</PropertyGroup>
</Project>

114
src/OpenIddict.NHibernate.Models/OpenIddictApplication.cs

@ -0,0 +1,114 @@
/*
* 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.Diagnostics;
namespace OpenIddict.NHibernate.Models
{
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication : OpenIddictApplication<string, OpenIddictAuthorization, OpenIddictToken>
{
public OpenIddictApplication()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
public class OpenIddictApplication<TKey> : OpenIddictApplication<TKey, OpenIddictAuthorization<TKey>, OpenIddictToken<TKey>>
where TKey : IEquatable<TKey>
{ }
/// <summary>
/// Represents an OpenIddict application.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; ClientId = {ClientId,nq} ; Type = {Type,nq}")]
public class OpenIddictApplication<TKey, TAuthorization, TToken> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the list of the authorizations associated with this application.
/// </summary>
public virtual IList<TAuthorization> Authorizations { get; set; } = new List<TAuthorization>();
/// <summary>
/// Gets or sets the client identifier
/// associated with the current application.
/// </summary>
public virtual string ClientId { get; set; }
/// <summary>
/// Gets or sets the client secret associated with the current application.
/// Note: depending on the application manager used to create this instance,
/// this property may be hashed or encrypted for security reasons.
/// </summary>
public virtual string ClientSecret { get; set; }
/// <summary>
/// Gets or sets the consent type
/// associated with the current application.
/// </summary>
public virtual string ConsentType { get; set; }
/// <summary>
/// Gets or sets the display name
/// associated with the current application.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current application.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the permissions associated with the
/// current application, serialized as a JSON array.
/// </summary>
public virtual string Permissions { get; set; }
/// <summary>
/// Gets or sets the logout callback URLs associated with
/// the current application, serialized as a JSON array.
/// </summary>
public virtual string PostLogoutRedirectUris { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current application.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the callback URLs associated with the
/// current application, serialized as a JSON array.
/// </summary>
public virtual string RedirectUris { get; set; }
/// <summary>
/// Gets or sets the list of the tokens associated with this application.
/// </summary>
public virtual IList<TToken> Tokens { get; set; } = new List<TToken>();
/// <summary>
/// Gets or sets the application type
/// associated with the current application.
/// </summary>
public virtual string Type { get; set; }
/// <summary>
/// Gets or sets the entity version, used as a concurrency token.
/// </summary>
public virtual int Version { get; set; }
}
}

87
src/OpenIddict.NHibernate.Models/OpenIddictAuthorization.cs

@ -0,0 +1,87 @@
/*
* 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.Diagnostics;
namespace OpenIddict.NHibernate.Models
{
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization : OpenIddictAuthorization<string, OpenIddictApplication, OpenIddictToken>
{
public OpenIddictAuthorization()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
public class OpenIddictAuthorization<TKey> : OpenIddictAuthorization<TKey, OpenIddictApplication<TKey>, OpenIddictToken<TKey>>
where TKey : IEquatable<TKey>
{ }
/// <summary>
/// Represents an OpenIddict authorization.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")]
public class OpenIddictAuthorization<TKey, TApplication, TToken> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the application associated with the current authorization.
/// </summary>
public virtual TApplication Application { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current authorization.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current authorization.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the scopes associated with the current
/// authorization, serialized as a JSON array.
/// </summary>
public virtual string Scopes { get; set; }
/// <summary>
/// Gets or sets the status of the current authorization.
/// </summary>
public virtual string Status { get; set; }
/// <summary>
/// Gets or sets the subject associated with the current authorization.
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the list of tokens
/// associated with the current authorization.
/// </summary>
public virtual IList<TToken> Tokens { get; set; } = new List<TToken>();
/// <summary>
/// Gets or sets the type of the current authorization.
/// </summary>
public virtual string Type { get; set; }
/// <summary>
/// Gets or sets the entity version, used as a concurrency token.
/// </summary>
public virtual int Version { get; set; }
}
}

71
src/OpenIddict.NHibernate.Models/OpenIddictScope.cs

@ -0,0 +1,71 @@
/*
* 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.Diagnostics;
namespace OpenIddict.NHibernate.Models
{
/// <summary>
/// Represents an OpenIddict scope.
/// </summary>
public class OpenIddictScope : OpenIddictScope<string>
{
public OpenIddictScope()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict scope.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Name = {Name,nq}")]
public class OpenIddictScope<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the public description
/// associated with the current scope.
/// </summary>
public virtual string Description { get; set; }
/// <summary>
/// Gets or sets the display name
/// associated with the current scope.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current scope.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the unique name
/// associated with the current scope.
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current scope.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the resources associated with the
/// current scope, serialized as a JSON array.
/// </summary>
public virtual string Resources { get; set; }
/// <summary>
/// Gets or sets the entity version, used as a concurrency token.
/// </summary>
public virtual int Version { get; set; }
}
}

107
src/OpenIddict.NHibernate.Models/OpenIddictToken.cs

@ -0,0 +1,107 @@
/*
* 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.Diagnostics;
namespace OpenIddict.NHibernate.Models
{
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken : OpenIddictToken<string, OpenIddictApplication, OpenIddictAuthorization>
{
public OpenIddictToken()
{
// Generate a new string identifier.
Id = Guid.NewGuid().ToString();
}
}
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
public class OpenIddictToken<TKey> : OpenIddictToken<TKey, OpenIddictApplication<TKey>, OpenIddictAuthorization<TKey>>
where TKey : IEquatable<TKey>
{
}
/// <summary>
/// Represents an OpenIddict token.
/// </summary>
[DebuggerDisplay("Id = {Id.ToString(),nq} ; Subject = {Subject,nq} ; Type = {Type,nq} ; Status = {Status,nq}")]
public class OpenIddictToken<TKey, TApplication, TAuthorization> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the application associated with the current token.
/// </summary>
public virtual TApplication Application { get; set; }
/// <summary>
/// Gets or sets the authorization associated with the current token.
/// </summary>
public virtual TAuthorization Authorization { get; set; }
/// <summary>
/// Gets or sets the date on which the token
/// will start to be considered valid.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the date on which the token
/// will no longer be considered valid.
/// </summary>
public virtual DateTimeOffset? ExpirationDate { get; set; }
/// <summary>
/// Gets or sets the unique identifier
/// associated with the current token.
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the payload of the current token, if applicable.
/// Note: this property is only used for reference tokens
/// and may be encrypted for security reasons.
/// </summary>
public virtual string Payload { get; set; }
/// <summary>
/// Gets or sets the additional properties serialized as a JSON object,
/// or <c>null</c> if no bag was associated with the current token.
/// </summary>
public virtual string Properties { get; set; }
/// <summary>
/// Gets or sets the reference identifier associated
/// with the current token, if applicable.
/// Note: this property is only used for reference tokens
/// and may be hashed or encrypted for security reasons.
/// </summary>
public virtual string ReferenceId { get; set; }
/// <summary>
/// Gets or sets the status of the current token.
/// </summary>
public virtual string Status { get; set; }
/// <summary>
/// Gets or sets the subject associated with the current token.
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the type of the current token.
/// </summary>
public virtual string Type { get; set; }
/// <summary>
/// Gets or sets the entity version, used as a concurrency token.
/// </summary>
public virtual int Version { get; set; }
}
}

27
src/OpenIddict.NHibernate/IOpenIddictNHibernateContext.cs

@ -0,0 +1,27 @@
/*
* 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 NHibernate;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Exposes the NHibernate session used by the OpenIddict stores.
/// </summary>
public interface IOpenIddictNHibernateContext
{
/// <summary>
/// Gets the <see cref="ISession"/>.
/// </summary>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the
/// asynchronous operation, whose result returns the NHibernate session.
/// </returns>
ValueTask<ISession> GetSessionAsync(CancellationToken cancellationToken);
}
}

101
src/OpenIddict.NHibernate/Mappings/OpenIddictApplicationMapping.cs

@ -0,0 +1,101 @@
/*
* 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 NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Defines a relational mapping for the Application entity.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public class OpenIddictApplicationMapping<TApplication, TAuthorization, TToken, TKey> : ClassMapping<TApplication>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationMapping()
{
Id(application => application.Id, map =>
{
map.Generator(Generators.Identity);
});
Version(application => application.Version, map =>
{
map.Insert(true);
});
Property(application => application.ClientId, map =>
{
map.NotNullable(true);
map.Unique(true);
});
Property(application => application.ClientSecret);
Property(application => application.ConsentType);
Property(application => application.DisplayName);
Property(application => application.Permissions, map =>
{
map.Length(10000);
});
Property(application => application.PostLogoutRedirectUris, map =>
{
map.Length(10000);
});
Property(application => application.Properties, map =>
{
map.Length(10000);
});
Property(application => application.RedirectUris, map =>
{
map.Length(10000);
});
Property(application => application.Type, map =>
{
map.NotNullable(true);
});
Bag(application => application.Authorizations,
map =>
{
map.Key(key => key.Column("ApplicationId"));
},
map =>
{
map.OneToMany();
});
Bag(application => application.Tokens,
map =>
{
map.Key(key => key.Column("ApplicationId"));
},
map =>
{
map.OneToMany();
});
Table("OpenIddictApplications");
}
}
}

84
src/OpenIddict.NHibernate/Mappings/OpenIddictAuthorizationMapping.cs

@ -0,0 +1,84 @@
/*
* 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 NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Defines a relational mapping for the Authorization entity.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public class OpenIddictAuthorizationMapping<TAuthorization, TApplication, TToken, TKey> : ClassMapping<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationMapping()
{
Id(authorization => authorization.Id, map =>
{
map.Generator(Generators.Identity);
});
Version(authorization => authorization.Version, map =>
{
map.Insert(true);
});
Property(authorization => authorization.Properties, map =>
{
map.Length(10000);
});
Property(authorization => authorization.Scopes, map =>
{
map.Length(10000);
});
Property(authorization => authorization.Status, map =>
{
map.NotNullable(true);
});
Property(authorization => authorization.Subject, map =>
{
map.NotNullable(true);
});
Property(authorization => authorization.Type, map =>
{
map.NotNullable(true);
});
ManyToOne(authorization => authorization.Application, map =>
{
map.ForeignKey("ApplicationId");
});
Bag(authorization => authorization.Tokens,
map =>
{
map.Key(key => key.Column("AuthorizationId"));
},
map =>
{
map.OneToMany();
});
Table("OpenIddictAuthorizations");
}
}
}

63
src/OpenIddict.NHibernate/Mappings/OpenIddictScopeMapping.cs

@ -0,0 +1,63 @@
/*
* 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 NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Defines a relational mapping for the Scope entity.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public class OpenIddictScopeMapping<TScope, TKey> : ClassMapping<TScope>
where TScope : OpenIddictScope<TKey>
where TKey : IEquatable<TKey>
{
public OpenIddictScopeMapping()
{
Id(scope => scope.Id, map =>
{
map.Generator(Generators.Identity);
});
Version(scope => scope.Version, map =>
{
map.Insert(true);
});
Property(scope => scope.Description, map =>
{
map.Length(10000);
});
Property(scope => scope.DisplayName);
Property(scope => scope.Name, map =>
{
map.NotNullable(true);
map.Unique(true);
});
Property(scope => scope.Properties, map =>
{
map.Length(10000);
});
Property(scope => scope.Resources, map =>
{
map.Length(10000);
});
Table("OpenIddictScopes");
}
}
}

85
src/OpenIddict.NHibernate/Mappings/OpenIddictTokenMapping.cs

@ -0,0 +1,85 @@
/*
* 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 NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Defines a relational mapping for the Token entity.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TKey">The type of the Key entity.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public class OpenIddictTokenMapping<TToken, TApplication, TAuthorization, TKey> : ClassMapping<TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TKey : IEquatable<TKey>
{
public OpenIddictTokenMapping()
{
Id(token => token.Id, map =>
{
map.Generator(Generators.Identity);
});
Version(token => token.Version, map =>
{
map.Insert(true);
});
Property(token => token.CreationDate);
Property(token => token.ExpirationDate);
Property(token => token.Payload, map =>
{
map.Length(10000);
});
Property(token => token.Properties, map =>
{
map.Length(10000);
});
Property(token => token.ReferenceId);
Property(token => token.Status, map =>
{
map.NotNullable(true);
});
Property(token => token.Subject, map =>
{
map.NotNullable(true);
});
Property(token => token.Type, map =>
{
map.NotNullable(true);
});
ManyToOne(token => token.Application, map =>
{
map.Column("ApplicationId");
});
ManyToOne(token => token.Authorization, map =>
{
map.Column("AuthorizationId");
});
Table("OpenIddictTokens");
}
}
}

29
src/OpenIddict.NHibernate/OpenIddict.NHibernate.csproj

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\packages.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Description>NHibernate 5.x stores for OpenIddict.</Description>
<Authors>Kévin Chalet</Authors>
<PackageTags>aspnetcore;authentication;jwt;openidconnect;openiddict;security</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OpenIddict.Core\OpenIddict.Core.csproj" />
<ProjectReference Include="..\OpenIddict.NHibernate.Models\OpenIddict.NHibernate.Models.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" />
<PackageReference Include="NHibernate" Version="$(NHibernateVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" />
</ItemGroup>
</Project>

109
src/OpenIddict.NHibernate/OpenIddictNHibernateBuilder.cs

@ -0,0 +1,109 @@
/*
* 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 JetBrains.Annotations;
using NHibernate;
using OpenIddict.Core;
using OpenIddict.NHibernate;
using OpenIddict.NHibernate.Models;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Exposes the necessary methods required to configure the OpenIddict NHibernate services.
/// </summary>
public class OpenIddictNHibernateBuilder
{
/// <summary>
/// Initializes a new instance of <see cref="OpenIddictNHibernateBuilder"/>.
/// </summary>
/// <param name="services">The services collection.</param>
public OpenIddictNHibernateBuilder([NotNull] IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
Services = services;
}
/// <summary>
/// Gets the services collection.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IServiceCollection Services { get; }
/// <summary>
/// Amends the default OpenIddict NHibernate configuration.
/// </summary>
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictNHibernateBuilder"/>.</returns>
public OpenIddictNHibernateBuilder Configure([NotNull] Action<OpenIddictNHibernateOptions> configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
Services.Configure(configuration);
return this;
}
/// <summary>
/// Configures the NHibernate stores to use the specified session factory
/// instead of retrieving it from the dependency injection container.
/// </summary>
/// <param name="factory">The <see cref="ISessionFactory"/>.</param>
/// <returns>The <see cref="OpenIddictNHibernateBuilder"/>.</returns>
public OpenIddictNHibernateBuilder UseSessionFactory([NotNull] ISessionFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return Configure(options => options.SessionFactory = factory);
}
/// <summary>
/// Configures OpenIddict to use the default OpenIddict Entity Framework entities, with the specified key type.
/// </summary>
/// <returns>The <see cref="OpenIddictNHibernateBuilder"/>.</returns>
public OpenIddictNHibernateBuilder ReplaceDefaultEntities<TKey>()
where TKey : IEquatable<TKey>
=> ReplaceDefaultEntities<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TKey>();
/// <summary>
/// Configures OpenIddict to use the specified entities, derived from the default OpenIddict Entity Framework entities.
/// </summary>
/// <returns>The <see cref="OpenIddictNHibernateBuilder"/>.</returns>
public OpenIddictNHibernateBuilder ReplaceDefaultEntities<TApplication, TAuthorization, TScope, TToken, TKey>()
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TKey : IEquatable<TKey>
{
Services.Configure<OpenIddictCoreOptions>(options =>
{
options.DefaultApplicationType = typeof(TApplication);
options.DefaultAuthorizationType = typeof(TAuthorization);
options.DefaultScopeType = typeof(TScope);
options.DefaultTokenType = typeof(TToken);
});
return this;
}
}
}

125
src/OpenIddict.NHibernate/OpenIddictNHibernateContext.cs

@ -0,0 +1,125 @@
/*
* 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.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NHibernate;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Exposes the NHibernate session used by the OpenIddict stores.
/// </summary>
public class OpenIddictNHibernateContext : IOpenIddictNHibernateContext, IDisposable
{
private readonly IOptionsMonitor<OpenIddictNHibernateOptions> _options;
private readonly IServiceProvider _provider;
private ISession _session;
public OpenIddictNHibernateContext(
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options,
[NotNull] IServiceProvider provider)
{
_options = options;
_provider = provider;
}
/// <summary>
/// Disposes the session held by this instance, if applicable.
/// </summary>
public void Dispose() => _session?.Dispose();
/// <summary>
/// Gets the <see cref="ISession"/>.
/// </summary>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the
/// asynchronous operation, whose result returns the NHibernate session.
/// </returns>
/// <remarks>
/// If a session factory was explicitly set in the OpenIddict NHibernate options,
/// a new session, specific to the OpenIddict stores is automatically opened
/// and disposed when the ambient scope is collected. If no session factory
/// was set, the session is retrieved from the dependency injection container
/// and a derived instance disabling automatic flush is managed by the context.
/// </remarks>
public ValueTask<ISession> GetSessionAsync(CancellationToken cancellationToken)
{
if (_session != null)
{
return new ValueTask<ISession>(_session);
}
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask<ISession>(Task.FromCanceled<ISession>(cancellationToken));
}
var options = _options.CurrentValue;
if (options == null)
{
throw new InvalidOperationException("The OpenIddict NHibernate options cannot be retrieved.");
}
// Note: by default, NHibernate is natively configured to perform automatic flushes
// on queries when it determines stale data may be returned during their execution.
// Combined with implicit entity updates, this feature is inconvenient for OpenIddict
// as it may result in updated entities being persisted before they are explicitly
// validated by the core managers and marked as updated by the NHibernate stores.
// To ensure this doesn't interfere with OpenIddict, automatic flush is disabled.
var factory = options.SessionFactory;
if (factory == null)
{
var session = _provider.GetService<ISession>();
if (session != null)
{
// If the flush mode is already set to manual, avoid creating a sub-session.
// If the session must be derived, all the parameters are inherited from
// the original session (except the flush mode, explicitly set to manual).
if (session.FlushMode != FlushMode.Manual)
{
session = _session = session.SessionWithOptions()
.AutoClose()
.AutoJoinTransaction()
.Connection()
.ConnectionReleaseMode()
.FlushMode(FlushMode.Manual)
.Interceptor()
.OpenSession();
}
return new ValueTask<ISession>(session);
}
factory = _provider.GetService<ISessionFactory>();
}
if (factory == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("No suitable NHibernate session or session factory can be found.")
.Append("To configure the OpenIddict NHibernate stores to use a specific factory, use ")
.Append("'services.AddOpenIddict().AddCore().UseNHibernate().UseSessionFactory()' or register an ")
.Append("'ISession'/'ISessionFactory' in the dependency injection container in 'ConfigureServices()'.")
.ToString());
}
else
{
var session = factory.OpenSession();
session.FlushMode = FlushMode.Manual;
return new ValueTask<ISession>(_session = session);
}
}
}
}

86
src/OpenIddict.NHibernate/OpenIddictNHibernateExtensions.cs

@ -0,0 +1,86 @@
/*
* 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 JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OpenIddict.NHibernate;
using OpenIddict.NHibernate.Models;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Exposes extensions allowing to register the OpenIddict NHibernate services.
/// </summary>
public static class OpenIddictNHibernateExtensions
{
/// <summary>
/// Registers the NHibernate stores services in the DI container and
/// configures OpenIddict to use the NHibernate entities by default.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictNHibernateBuilder"/>.</returns>
public static OpenIddictNHibernateBuilder UseNHibernate([NotNull] this OpenIddictCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
// Since NHibernate may be used with databases performing case-insensitive or
// culture-sensitive comparisons, ensure the additional filtering logic is enforced
// in case case-sensitive stores were registered before this extension was called.
builder.Configure(options => options.DisableAdditionalFiltering = false);
builder.SetDefaultApplicationEntity<OpenIddictApplication>()
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>()
.SetDefaultScopeEntity<OpenIddictScope>()
.SetDefaultTokenEntity<OpenIddictToken>();
builder.ReplaceApplicationStoreResolver<OpenIddictApplicationStoreResolver>()
.ReplaceAuthorizationStoreResolver<OpenIddictAuthorizationStoreResolver>()
.ReplaceScopeStoreResolver<OpenIddictScopeStoreResolver>()
.ReplaceTokenStoreResolver<OpenIddictTokenStoreResolver>();
builder.Services.TryAddScoped(typeof(OpenIddictApplicationStore<,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictAuthorizationStore<,,,>));
builder.Services.TryAddScoped(typeof(OpenIddictScopeStore<,>));
builder.Services.TryAddScoped(typeof(OpenIddictTokenStore<,,,>));
builder.Services.TryAddScoped<IOpenIddictNHibernateContext, OpenIddictNHibernateContext>();
return new OpenIddictNHibernateBuilder(builder.Services);
}
/// <summary>
/// Registers the NHibernate stores services in the DI container and
/// configures OpenIddict to use the NHibernate entities by default.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="configuration">The configuration delegate used to configure the NHibernate services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks>
/// <returns>The <see cref="OpenIddictCoreBuilder"/>.</returns>
public static OpenIddictCoreBuilder UseNHibernate(
[NotNull] this OpenIddictCoreBuilder builder,
[NotNull] Action<OpenIddictNHibernateBuilder> configuration)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
configuration(builder.UseNHibernate());
return builder;
}
}
}

74
src/OpenIddict.NHibernate/OpenIddictNHibernateHelpers.cs

@ -0,0 +1,74 @@
/*
* 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 JetBrains.Annotations;
using NHibernate.Mapping.ByCode;
using OpenIddict.NHibernate;
using OpenIddict.NHibernate.Models;
namespace NHibernate.Cfg
{
/// <summary>
/// Exposes extensions allowing to register the OpenIddict NHibernate mappings.
/// </summary>
public static class OpenIddictNHibernateHelpers
{
/// <summary>
/// Registers the OpenIddict entity mappings in the NHibernate
/// configuration using the default entities and the default key type.
/// </summary>
/// <param name="configuration">The NHibernate configuration builder.</param>
/// <returns>The <see cref="Configuration"/>.</returns>
public static Configuration UseOpenIddict([NotNull] this Configuration configuration)
=> configuration.UseOpenIddict<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictScope,
OpenIddictToken, string>();
/// <summary>
/// Registers the OpenIddict entity mappings in the NHibernate
/// configuration using the default entities and the specified key type.
/// </summary>
/// <param name="configuration">The NHibernate configuration builder.</param>
/// <returns>The <see cref="Configuration"/>.</returns>
public static Configuration UseOpenIddict<TKey>([NotNull] this Configuration configuration)
where TKey : IEquatable<TKey>
=> configuration.UseOpenIddict<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictScope<TKey>,
OpenIddictToken<TKey>, TKey>();
/// <summary>
/// Registers the OpenIddict entity mappings in the NHibernate
/// configuration using the specified entities and the specified key type.
/// </summary>
/// <param name="configuration">The NHibernate configuration builder.</param>
/// <returns>The <see cref="Configuration"/>.</returns>
public static Configuration UseOpenIddict<TApplication, TAuthorization, TScope, TToken, TKey>([NotNull] this Configuration configuration)
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TScope : OpenIddictScope<TKey>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TKey : IEquatable<TKey>
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var mapper = new ModelMapper();
mapper.AddMapping<OpenIddictApplicationMapping<TApplication, TAuthorization, TToken, TKey>>();
mapper.AddMapping<OpenIddictAuthorizationMapping<TAuthorization, TApplication, TToken, TKey>>();
mapper.AddMapping<OpenIddictScopeMapping<TScope, TKey>>();
mapper.AddMapping<OpenIddictTokenMapping<TToken, TApplication, TAuthorization, TKey>>();
configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
return configuration;
}
}
}

22
src/OpenIddict.NHibernate/OpenIddictNHibernateOptions.cs

@ -0,0 +1,22 @@
/*
* 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 NHibernate;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Provides various settings needed to configure the OpenIddict NHibernate integration.
/// </summary>
public class OpenIddictNHibernateOptions
{
/// <summary>
/// Gets or sets the session factory used by the OpenIddict NHibernate stores.
/// If none is explicitly set, the session factory is resolved from the DI container.
/// </summary>
public ISessionFactory SessionFactory { get; set; }
}
}

67
src/OpenIddict.NHibernate/Resolvers/OpenIddictApplicationStoreResolver.cs

@ -0,0 +1,67 @@
/*
* 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.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Extensions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Exposes a method allowing to resolve an application store.
/// </summary>
public class OpenIddictApplicationStoreResolver : IOpenIddictApplicationStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IServiceProvider _provider;
public OpenIddictApplicationStoreResolver([NotNull] IServiceProvider provider)
=> _provider = provider;
/// <summary>
/// Returns an application store compatible with the specified application type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <returns>An <see cref="IOpenIddictApplicationStore{TApplication}"/>.</returns>
public IOpenIddictApplicationStore<TApplication> Get<TApplication>() where TApplication : class
{
var store = _provider.GetService<IOpenIddictApplicationStore<TApplication>>();
if (store != null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TApplication), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictApplication<,,>));
if (root == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified application type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictApplication' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictApplication' entity.")
.ToString());
}
return typeof(OpenIddictApplicationStore<,,,>).MakeGenericType(
/* TApplication: */ key,
/* TAuthorization: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictApplicationStore<TApplication>) _provider.GetRequiredService(type);
}
}
}

67
src/OpenIddict.NHibernate/Resolvers/OpenIddictAuthorizationStoreResolver.cs

@ -0,0 +1,67 @@
/*
* 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.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Extensions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Exposes a method allowing to resolve an authorization store.
/// </summary>
public class OpenIddictAuthorizationStoreResolver : IOpenIddictAuthorizationStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IServiceProvider _provider;
public OpenIddictAuthorizationStoreResolver([NotNull] IServiceProvider provider)
=> _provider = provider;
/// <summary>
/// Returns an authorization store compatible with the specified authorization type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <returns>An <see cref="IOpenIddictAuthorizationStore{TAuthorization}"/>.</returns>
public IOpenIddictAuthorizationStore<TAuthorization> Get<TAuthorization>() where TAuthorization : class
{
var store = _provider.GetService<IOpenIddictAuthorizationStore<TAuthorization>>();
if (store != null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TAuthorization), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictAuthorization<,,>));
if (root == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified authorization type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictAuthorization' entity.")
.ToString());
}
return typeof(OpenIddictAuthorizationStore<,,,>).MakeGenericType(
/* TAuthorization: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TToken: */ root.GenericTypeArguments[2],
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictAuthorizationStore<TAuthorization>) _provider.GetRequiredService(type);
}
}
}

65
src/OpenIddict.NHibernate/Resolvers/OpenIddictScopeStoreResolver.cs

@ -0,0 +1,65 @@
/*
* 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.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Extensions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Exposes a method allowing to resolve a scope store.
/// </summary>
public class OpenIddictScopeStoreResolver : IOpenIddictScopeStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IServiceProvider _provider;
public OpenIddictScopeStoreResolver([NotNull] IServiceProvider provider)
=> _provider = provider;
/// <summary>
/// Returns a scope store compatible with the specified scope type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <returns>An <see cref="IOpenIddictScopeStore{TScope}"/>.</returns>
public IOpenIddictScopeStore<TScope> Get<TScope>() where TScope : class
{
var store = _provider.GetService<IOpenIddictScopeStore<TScope>>();
if (store != null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TScope), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictScope<>));
if (root == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified scope type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictScope' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictScope' entity.")
.ToString());
}
return typeof(OpenIddictScopeStore<,>).MakeGenericType(
/* TScope: */ key,
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictScopeStore<TScope>) _provider.GetRequiredService(type);
}
}
}

67
src/OpenIddict.NHibernate/Resolvers/OpenIddictTokenStoreResolver.cs

@ -0,0 +1,67 @@
/*
* 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.Concurrent;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.Extensions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Exposes a method allowing to resolve a token store.
/// </summary>
public class OpenIddictTokenStoreResolver : IOpenIddictTokenStoreResolver
{
private static readonly ConcurrentDictionary<Type, Type> _cache = new ConcurrentDictionary<Type, Type>();
private readonly IServiceProvider _provider;
public OpenIddictTokenStoreResolver([NotNull] IServiceProvider provider)
=> _provider = provider;
/// <summary>
/// Returns a token store compatible with the specified token type or throws an
/// <see cref="InvalidOperationException"/> if no store can be built using the specified type.
/// </summary>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <returns>An <see cref="IOpenIddictTokenStore{TToken}"/>.</returns>
public IOpenIddictTokenStore<TToken> Get<TToken>() where TToken : class
{
var store = _provider.GetService<IOpenIddictTokenStore<TToken>>();
if (store != null)
{
return store;
}
var type = _cache.GetOrAdd(typeof(TToken), key =>
{
var root = OpenIddictHelpers.FindGenericBaseType(key, typeof(OpenIddictToken<,,>));
if (root == null)
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("The specified token type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictToken' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictToken' entity.")
.ToString());
}
return typeof(OpenIddictTokenStore<,,,>).MakeGenericType(
/* TToken: */ key,
/* TApplication: */ root.GenericTypeArguments[1],
/* TAuthorization: */ root.GenericTypeArguments[2],
/* TKey: */ root.GenericTypeArguments[0]);
});
return (IOpenIddictTokenStore<TToken>) _provider.GetRequiredService(type);
}
}
}

982
src/OpenIddict.NHibernate/Stores/OpenIddictApplicationStore.cs

@ -0,0 +1,982 @@
/*
* 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.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Linq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
public class OpenIddictApplicationStore : OpenIddictApplicationStore<OpenIddictApplication,
OpenIddictAuthorization,
OpenIddictToken, string>
{
public OpenIddictApplicationStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictApplicationStore<TKey> : OpenIddictApplicationStore<OpenIddictApplication<TKey>,
OpenIddictAuthorization<TKey>,
OpenIddictToken<TKey>, TKey>
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the applications stored in a database.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictApplicationStore<TApplication, TAuthorization, TToken, TKey> : IOpenIddictApplicationStore<TApplication>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TKey : IEquatable<TKey>
{
public OpenIddictApplicationStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected IOpenIddictNHibernateContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictNHibernateOptions> Options { get; }
/// <summary>
/// Determines the number of applications that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications in the database.
/// </returns>
public virtual async Task<long> CountAsync(CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
return await session.Query<TApplication>().LongCountAsync(cancellationToken);
}
/// <summary>
/// Determines the number of applications that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of applications that match the specified query.
/// </returns>
public virtual async Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await query(session.Query<TApplication>()).LongCountAsync(cancellationToken);
}
/// <summary>
/// Creates a new application.
/// </summary>
/// <param name="application">The application to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task CreateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var session = await Context.GetSessionAsync(cancellationToken);
await session.PersistAsync(application, cancellationToken);
await session.FlushAsync(cancellationToken);
}
/// <summary>
/// Removes an existing application.
/// </summary>
/// <param name="application">The application to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var session = await Context.GetSessionAsync(cancellationToken);
try
{
// Delete all the tokens associated with the application.
await (from authorization in session.Query<TAuthorization>()
where authorization.Application.Id.Equals(application.Id)
select authorization).DeleteAsync(cancellationToken);
// Delete all the tokens associated with the application.
await (from token in session.Query<TToken>()
where token.Application.Id.Equals(application.Id)
select token).DeleteAsync(cancellationToken);
await session.DeleteAsync(application, cancellationToken);
await session.FlushAsync(cancellationToken);
}
catch (StaleObjectStateException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The application was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the application from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Retrieves an application using its client identifier.
/// </summary>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual async Task<TApplication> FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await (from application in session.Query<TApplication>()
where application.ClientId == identifier
select application).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual async Task<TApplication> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await session.GetAsync<TApplication>(ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
/// </summary>
/// <param name="address">The post_logout_redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException("The address cannot be null or empty.", nameof(address));
}
var session = await Context.GetSessionAsync(cancellationToken);
// To optimize the efficiency of the query a bit, only applications whose stringified
// PostLogoutRedirectUris contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var applications = await (from application in session.Query<TApplication>()
where application.PostLogoutRedirectUris.Contains(address)
select application).ToListAsync(cancellationToken);
var builder = ImmutableArray.CreateBuilder<TApplication>();
foreach (var application in applications)
{
foreach (var uri in await GetPostLogoutRedirectUrisAsync(application, cancellationToken))
{
// Note: the post_logout_redirect_uri must be compared
// using case-sensitive "Simple String Comparison".
if (string.Equals(uri, address, StringComparison.Ordinal))
{
builder.Add(application);
break;
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves all the applications associated with the specified redirect_uri.
/// </summary>
/// <param name="address">The redirect_uri associated with the applications.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(address))
{
throw new ArgumentException("The address cannot be null or empty.", nameof(address));
}
var session = await Context.GetSessionAsync(cancellationToken);
// To optimize the efficiency of the query a bit, only applications whose stringified
// RedirectUris property contains the specified URL are returned. Once the applications
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var applications = await (from application in session.Query<TApplication>()
where application.RedirectUris.Contains(address)
select application).ToListAsync(cancellationToken);
var builder = ImmutableArray.CreateBuilder<TApplication>();
foreach (var application in applications)
{
foreach (var uri in await GetRedirectUrisAsync(application, cancellationToken))
{
// Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
if (string.Equals(uri, address, StringComparison.Ordinal))
{
builder.Add(application);
break;
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public virtual async Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await query(session.Query<TApplication>(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientId);
}
/// <summary>
/// Retrieves the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client secret associated with the application.
/// </returns>
public virtual ValueTask<string> GetClientSecretAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ClientSecret);
}
/// <summary>
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual ValueTask<string> GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.Type);
}
/// <summary>
/// Retrieves the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the consent type of the application (by default, "explicit").
/// </returns>
public virtual ValueTask<string> GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.ConsentType);
}
/// <summary>
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(application.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return new ValueTask<string>(ConvertIdentifierToString(application.Id));
}
/// <summary>
/// Retrieves the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the permissions associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPermissionsAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Permissions))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified permissions is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions);
var permissions = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.Permissions)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(permissions);
}
/// <summary>
/// Retrieves the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the post_logout_redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetPostLogoutRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.PostLogoutRedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.PostLogoutRedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Retrieves the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the application.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(application.Properties));
}
/// <summary>
/// Retrieves the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the redirect_uri associated with the application.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (string.IsNullOrEmpty(application.RedirectUris))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(application.RedirectUris)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(addresses);
}
/// <summary>
/// Instantiates a new application.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated application, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TApplication> InstantiateAsync(CancellationToken cancellationToken)
{
try
{
return new ValueTask<TApplication>(Activator.CreateInstance<TApplication>());
}
catch (MemberAccessException exception)
{
return new ValueTask<TApplication>(Task.FromException<TApplication>(
new InvalidOperationException(new StringBuilder()
.AppendLine("An error occurred while trying to create a new application instance.")
.Append("Make sure that the application entity is not abstract and has a public parameterless constructor ")
.Append("or create a custom application store that overrides 'InstantiateAsync()' to use a custom factory.")
.ToString(), exception)));
}
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TApplication>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
var query = session.Query<TApplication>()
.OrderBy(application => application.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return ImmutableArray.CreateRange(await query(session.Query<TApplication>(), state).ToListAsync(cancellationToken));
}
/// <summary>
/// Sets the client identifier associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="identifier">The client identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientIdAsync([NotNull] TApplication application,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientId = identifier;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client secret associated with an application.
/// Note: depending on the manager used to create the application,
/// the client secret may be hashed for security reasons.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The client secret associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientSecretAsync([NotNull] TApplication application,
[CanBeNull] string secret, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ClientSecret = secret;
return Task.CompletedTask;
}
/// <summary>
/// Sets the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The client type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetClientTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the consent type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="type">The consent type associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetConsentTypeAsync([NotNull] TApplication application,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.ConsentType = type;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="name">The display name associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TApplication application,
[CanBeNull] string name, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
application.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the permissions associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="permissions">The permissions associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPermissionsAsync([NotNull] TApplication application, ImmutableArray<string> permissions, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (permissions.IsDefaultOrEmpty)
{
application.Permissions = null;
return Task.CompletedTask;
}
application.Permissions = new JArray(permissions.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the logout callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The logout callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPostLogoutRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.PostLogoutRedirectUris = null;
return Task.CompletedTask;
}
application.PostLogoutRedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="properties">The additional properties associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TApplication application, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (properties == null)
{
application.Properties = null;
return Task.CompletedTask;
}
application.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the callback addresses associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="addresses">The callback addresses associated with the application </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetRedirectUrisAsync([NotNull] TApplication application,
ImmutableArray<string> addresses, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (addresses.IsDefaultOrEmpty)
{
application.RedirectUris = null;
return Task.CompletedTask;
}
application.RedirectUris = new JArray(addresses.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing application.
/// </summary>
/// <param name="application">The application to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var session = await Context.GetSessionAsync(cancellationToken);
try
{
await session.UpdateAsync(application, cancellationToken);
await session.FlushAsync(cancellationToken);
}
catch (StaleObjectStateException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The application was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the application from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

935
src/OpenIddict.NHibernate/Stores/OpenIddictAuthorizationStore.cs

@ -0,0 +1,935 @@
/*
* 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.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Linq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
public class OpenIddictAuthorizationStore : OpenIddictAuthorizationStore<OpenIddictAuthorization,
OpenIddictApplication,
OpenIddictToken, string>
{
public OpenIddictAuthorizationStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictAuthorizationStore<TKey> : OpenIddictAuthorizationStore<OpenIddictAuthorization<TKey>,
OpenIddictApplication<TKey>,
OpenIddictToken<TKey>, TKey>
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the authorizations stored in a database.
/// </summary>
/// <typeparam name="TAuthorization">The type of the Authorization entity.</typeparam>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <typeparam name="TToken">The type of the Token entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictAuthorizationStore<TAuthorization, TApplication, TToken, TKey> : IOpenIddictAuthorizationStore<TAuthorization>
where TAuthorization : OpenIddictAuthorization<TKey, TApplication, TToken>
where TApplication : OpenIddictApplication<TKey, TAuthorization, TToken>
where TToken : OpenIddictToken<TKey, TApplication, TAuthorization>
where TKey : IEquatable<TKey>
{
public OpenIddictAuthorizationStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected IOpenIddictNHibernateContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictNHibernateOptions> Options { get; }
/// <summary>
/// Determines the number of authorizations that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations in the database.
/// </returns>
public virtual async Task<long> CountAsync(CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
return await session.Query<TAuthorization>().LongCountAsync(cancellationToken);
}
/// <summary>
/// Determines the number of authorizations that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of authorizations that match the specified query.
/// </returns>
public virtual async Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TAuthorization>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await query(session.Query<TAuthorization>()).LongCountAsync(cancellationToken);
}
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="authorization">The authorization to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
var session = await Context.GetSessionAsync(cancellationToken);
await session.SaveAsync(authorization, cancellationToken);
await session.FlushAsync(cancellationToken);
}
/// <summary>
/// Removes an existing authorization.
/// </summary>
/// <param name="authorization">The authorization to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
var session = await Context.GetSessionAsync(cancellationToken);
try
{
// Delete all the tokens associated with the authorization.
await (from token in session.Query<TToken>()
where token.Authorization.Id.Equals(authorization.Id)
select token).DeleteAsync(cancellationToken);
await session.DeleteAsync(authorization, cancellationToken);
await session.FlushAsync(cancellationToken);
}
catch (StaleObjectStateException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The authorization was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the authorization from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Retrieves the authorizations corresponding to the specified
/// subject and associated with the application identifier.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the subject/client.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
var session = await Context.GetSessionAsync(cancellationToken);
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from authorization in session.Query<TAuthorization>().Fetch(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == subject
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
var session = await Context.GetSessionAsync(cancellationToken);
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from authorization in session.Query<TAuthorization>().Fetch(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == subject &&
authorization.Status == status
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException("The status cannot be null or empty.", nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The type cannot be null or empty.", nameof(type));
}
var session = await Context.GetSessionAsync(cancellationToken);
var key = ConvertIdentifierFromString(client);
return ImmutableArray.CreateRange(
await (from authorization in session.Query<TAuthorization>().Fetch(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key) &&
authorization.Subject == subject &&
authorization.Status == status &&
authorization.Type == type
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="status">The authorization status.</param>
/// <param name="type">The authorization type.</param>
/// <param name="scopes">The minimal scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the criteria.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] string status, [NotNull] string type,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
var authorizations = await FindAsync(subject, client, status, type, cancellationToken);
if (authorizations.IsEmpty)
{
return ImmutableArray.Create<TAuthorization>();
}
var builder = ImmutableArray.CreateBuilder<TAuthorization>(authorizations.Length);
foreach (var authorization in authorizations)
{
async Task<bool> HasScopesAsync()
=> (await GetScopesAsync(authorization, cancellationToken))
.ToImmutableHashSet(StringComparer.Ordinal)
.IsSupersetOf(scopes);
if (await HasScopesAsync())
{
builder.Add(authorization);
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary>
/// Retrieves the list of authorizations corresponding to the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the authorizations.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the specified application.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindByApplicationIdAsync(
[NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var session = await Context.GetSessionAsync(cancellationToken);
var key = ConvertIdentifierFromString(identifier);
return ImmutableArray.CreateRange(
await (from authorization in session.Query<TAuthorization>().Fetch(authorization => authorization.Application)
where authorization.Application != null &&
authorization.Application.Id.Equals(key)
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves an authorization using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
/// </returns>
public virtual async Task<TAuthorization> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await session.GetAsync<TAuthorization>(ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves all the authorizations corresponding to the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the authorizations corresponding to the specified subject.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> FindBySubjectAsync(
[NotNull] string subject, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
var session = await Context.GetSessionAsync(cancellationToken);
return ImmutableArray.CreateRange(
await (from authorization in session.Query<TAuthorization>().Fetch(authorization => authorization.Application)
where authorization.Subject == subject
select authorization).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves the optional application identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the application identifier associated with the authorization.
/// </returns>
public virtual ValueTask<string> GetApplicationIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (authorization.Application == null)
{
return new ValueTask<string>(result: null);
}
return new ValueTask<string>(ConvertIdentifierToString(authorization.Application.Id));
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public virtual async Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await query(session.Query<TAuthorization>()
.Fetch(authorization => authorization.Application), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the unique identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the authorization.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(ConvertIdentifierToString(authorization.Id));
}
/// <summary>
/// Retrieves the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the authorization.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(authorization.Properties));
}
/// <summary>
/// Retrieves the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified authorization.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetScopesAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (string.IsNullOrEmpty(authorization.Scopes))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
return new ValueTask<ImmutableArray<string>>(JArray.Parse(authorization.Scopes).Select(element => (string) element).ToImmutableArray());
}
/// <summary>
/// Retrieves the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the status associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetStatusAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Status);
}
/// <summary>
/// Retrieves the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the subject associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Subject);
}
/// <summary>
/// Retrieves the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the type associated with the specified authorization.
/// </returns>
public virtual ValueTask<string> GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return new ValueTask<string>(authorization.Type);
}
/// <summary>
/// Instantiates a new authorization.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated authorization, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TAuthorization> InstantiateAsync(CancellationToken cancellationToken)
{
try
{
return new ValueTask<TAuthorization>(Activator.CreateInstance<TAuthorization>());
}
catch (MemberAccessException exception)
{
return new ValueTask<TAuthorization>(Task.FromException<TAuthorization>(
new InvalidOperationException(new StringBuilder()
.AppendLine("An error occurred while trying to create a new authorization instance.")
.Append("Make sure that the authorization entity is not abstract and has a public parameterless constructor ")
.Append("or create a custom authorization store that overrides 'InstantiateAsync()' to use a custom factory.")
.ToString(), exception)));
}
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TAuthorization>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
var query = session.Query<TAuthorization>()
.Fetch(authorization => authorization.Application)
.OrderBy(authorization => authorization.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TAuthorization>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return ImmutableArray.CreateRange(await query(
session.Query<TAuthorization>().Fetch(authorization => authorization.Application), state).ToListAsync(cancellationToken));
}
/// <summary>
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task PruneAsync(CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
await (from token in session.Query<TToken>()
where token.Status != OpenIddictConstants.Statuses.Valid ||
token.ExpirationDate < DateTimeOffset.UtcNow
select token).DeleteAsync(cancellationToken);
await (from authorization in session.Query<TAuthorization>()
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc && !authorization.Tokens.Any())
select authorization).DeleteAsync(cancellationToken);
await session.FlushAsync(cancellationToken);
}
/// <summary>
/// Sets the application identifier associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="identifier">The unique identifier associated with the client application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task SetApplicationIdAsync([NotNull] TAuthorization authorization,
[CanBeNull] string identifier, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
var session = await Context.GetSessionAsync(cancellationToken);
if (!string.IsNullOrEmpty(identifier))
{
authorization.Application = await session.LoadAsync<TApplication>(ConvertIdentifierFromString(identifier), cancellationToken);
}
else
{
authorization.Application = null;
}
}
/// <summary>
/// Sets the additional properties associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="properties">The additional properties associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TAuthorization authorization, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (properties == null)
{
authorization.Properties = null;
return Task.CompletedTask;
}
authorization.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the scopes associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetScopesAsync([NotNull] TAuthorization authorization,
ImmutableArray<string> scopes, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
if (scopes.IsDefaultOrEmpty)
{
authorization.Scopes = null;
return Task.CompletedTask;
}
authorization.Scopes = new JArray(scopes.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="status">The status associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetStatusAsync([NotNull] TAuthorization authorization,
[CanBeNull] string status, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Status = status;
return Task.CompletedTask;
}
/// <summary>
/// Sets the subject associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetSubjectAsync([NotNull] TAuthorization authorization,
[CanBeNull] string subject, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Subject = subject;
return Task.CompletedTask;
}
/// <summary>
/// Sets the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="type">The type associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetTypeAsync([NotNull] TAuthorization authorization,
[CanBeNull] string type, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Type = type;
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing authorization.
/// </summary>
/// <param name="authorization">The authorization to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
var session = await Context.GetSessionAsync(cancellationToken);
try
{
await session.UpdateAsync(authorization, cancellationToken);
await session.FlushAsync(cancellationToken);
}
catch (StaleObjectStateException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The authorization was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the authorization from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

712
src/OpenIddict.NHibernate/Stores/OpenIddictScopeStore.cs

@ -0,0 +1,712 @@
/*
* 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.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NHibernate;
using NHibernate.Linq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
namespace OpenIddict.NHibernate
{
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
public class OpenIddictScopeStore : OpenIddictScopeStore<OpenIddictScope, string>
{
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictScopeStore<TKey> : OpenIddictScopeStore<OpenIddictScope<TKey>, TKey>
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
: base(cache, context, options)
{
}
}
/// <summary>
/// Provides methods allowing to manage the scopes stored in a database.
/// </summary>
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
/// <typeparam name="TKey">The type of the entity primary keys.</typeparam>
public class OpenIddictScopeStore<TScope, TKey> : IOpenIddictScopeStore<TScope>
where TScope : OpenIddictScope<TKey>
where TKey : IEquatable<TKey>
{
public OpenIddictScopeStore(
[NotNull] IMemoryCache cache,
[NotNull] IOpenIddictNHibernateContext context,
[NotNull] IOptionsMonitor<OpenIddictNHibernateOptions> options)
{
Cache = cache;
Context = context;
Options = options;
}
/// <summary>
/// Gets the memory cache associated with the current store.
/// </summary>
protected IMemoryCache Cache { get; }
/// <summary>
/// Gets the database context associated with the current store.
/// </summary>
protected IOpenIddictNHibernateContext Context { get; }
/// <summary>
/// Gets the options associated with the current store.
/// </summary>
protected IOptionsMonitor<OpenIddictNHibernateOptions> Options { get; }
/// <summary>
/// Determines the number of scopes that exist in the database.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes in the database.
/// </returns>
public virtual async Task<long> CountAsync(CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
return await session.Query<TScope>().LongCountAsync(cancellationToken);
}
/// <summary>
/// Determines the number of scopes that match the specified query.
/// </summary>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the number of scopes that match the specified query.
/// </returns>
public virtual async Task<long> CountAsync<TResult>([NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await query(session.Query<TScope>()).LongCountAsync(cancellationToken);
}
/// <summary>
/// Creates a new scope.
/// </summary>
/// <param name="scope">The scope to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
var session = await Context.GetSessionAsync(cancellationToken);
await session.SaveAsync(scope, cancellationToken);
await session.FlushAsync(cancellationToken);
}
/// <summary>
/// Removes an existing scope.
/// </summary>
/// <param name="scope">The scope to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
var session = await Context.GetSessionAsync(cancellationToken);
try
{
await session.DeleteAsync(scope, cancellationToken);
await session.FlushAsync(cancellationToken);
}
catch (StaleObjectStateException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the scope from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Retrieves a scope using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the identifier.
/// </returns>
public virtual async Task<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await session.GetAsync<TScope>(ConvertIdentifierFromString(identifier), cancellationToken);
}
/// <summary>
/// Retrieves a scope using its name.
/// </summary>
/// <param name="name">The name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scope corresponding to the specified name.
/// </returns>
public virtual async Task<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await (from scope in session.Query<TScope>()
where scope.Name == name
select scope).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves a list of scopes using their name.
/// </summary>
/// <param name="names">The names associated with the scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes corresponding to the specified names.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> FindByNamesAsync(
ImmutableArray<string> names, CancellationToken cancellationToken)
{
if (names.Any(name => string.IsNullOrEmpty(name)))
{
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
}
var session = await Context.GetSessionAsync(cancellationToken);
return ImmutableArray.CreateRange(
await (from scope in session.Query<TScope>()
where names.Contains(scope.Name)
select scope).ToListAsync(cancellationToken));
}
/// <summary>
/// Retrieves all the scopes that contain the specified resource.
/// </summary>
/// <param name="resource">The resource associated with the scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the scopes associated with the specified resource.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> FindByResourceAsync(
[NotNull] string resource, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
}
var session = await Context.GetSessionAsync(cancellationToken);
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var scopes = await (from scope in session.Query<TScope>()
where scope.Resources.Contains(resource)
select scope).ToListAsync(cancellationToken);
var builder = ImmutableArray.CreateBuilder<TScope>();
foreach (var scope in scopes)
{
var resources = await GetResourcesAsync(scope, cancellationToken);
if (resources.Contains(resource, StringComparer.OrdinalIgnoreCase))
{
builder.Add(scope);
}
}
return builder.ToImmutable();
}
/// <summary>
/// Executes the specified query and returns the first element.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the first element returned when executing the query.
/// </returns>
public virtual async Task<TResult> GetAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return await query(session.Query<TScope>(), state).FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
/// Retrieves the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the description associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Description);
}
/// <summary>
/// Retrieves the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the scope.
/// </returns>
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.DisplayName);
}
/// <summary>
/// Retrieves the unique identifier associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the scope.
/// </returns>
public virtual ValueTask<string> GetIdAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(ConvertIdentifierToString(scope.Id));
}
/// <summary>
/// Retrieves the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the name associated with the specified scope.
/// </returns>
public virtual ValueTask<string> GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return new ValueTask<string>(scope.Name);
}
/// <summary>
/// Retrieves the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the additional properties associated with the scope.
/// </returns>
public virtual ValueTask<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Properties))
{
return new ValueTask<JObject>(new JObject());
}
return new ValueTask<JObject>(JObject.Parse(scope.Properties));
}
/// <summary>
/// Retrieves the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the resources associated with the scope.
/// </returns>
public virtual ValueTask<ImmutableArray<string>> GetResourcesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (string.IsNullOrEmpty(scope.Resources))
{
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
}
// Note: parsing the stringified resources is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache.
var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources);
var resources = Cache.GetOrCreate(key, entry =>
{
entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1));
return JArray.Parse(scope.Resources)
.Select(element => (string) element)
.ToImmutableArray();
});
return new ValueTask<ImmutableArray<string>>(resources);
}
/// <summary>
/// Instantiates a new scope.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
/// whose result returns the instantiated scope, that can be persisted in the database.
/// </returns>
public virtual ValueTask<TScope> InstantiateAsync(CancellationToken cancellationToken)
{
try
{
return new ValueTask<TScope>(Activator.CreateInstance<TScope>());
}
catch (MemberAccessException exception)
{
return new ValueTask<TScope>(Task.FromException<TScope>(
new InvalidOperationException(new StringBuilder()
.AppendLine("An error occurred while trying to create a new scope instance.")
.Append("Make sure that the scope entity is not abstract and has a public parameterless constructor ")
.Append("or create a custom scope store that overrides 'InstantiateAsync()' to use a custom factory.")
.ToString(), exception)));
}
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <param name="count">The number of results to return.</param>
/// <param name="offset">The number of results to skip.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TScope>> ListAsync(
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
{
var session = await Context.GetSessionAsync(cancellationToken);
var query = session.Query<TScope>()
.OrderBy(scope => scope.Id)
.AsQueryable();
if (offset.HasValue)
{
query = query.Skip(offset.Value);
}
if (count.HasValue)
{
query = query.Take(count.Value);
}
return ImmutableArray.CreateRange(await query.ToListAsync(cancellationToken));
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
/// <typeparam name="TState">The state type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <param name="query">The query to execute.</param>
/// <param name="state">The optional state.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns all the elements returned when executing the specified query.
/// </returns>
public virtual async Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
[CanBeNull] TState state, CancellationToken cancellationToken)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var session = await Context.GetSessionAsync(cancellationToken);
return ImmutableArray.CreateRange(await query(session.Query<TScope>(), state).ToListAsync(cancellationToken));
}
/// <summary>
/// Sets the description associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="description">The description associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Description = description;
return Task.CompletedTask;
}
/// <summary>
/// Sets the display name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The display name associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.DisplayName = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the name associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="name">The name associated with the authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
scope.Name = name;
return Task.CompletedTask;
}
/// <summary>
/// Sets the additional properties associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="properties">The additional properties associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (properties == null)
{
scope.Properties = null;
return Task.CompletedTask;
}
scope.Properties = properties.ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Sets the resources associated with a scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="resources">The resources associated with the scope.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetResourcesAsync([NotNull] TScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
if (resources.IsDefaultOrEmpty)
{
scope.Resources = null;
return Task.CompletedTask;
}
scope.Resources = new JArray(resources.ToArray()).ToString(Formatting.None);
return Task.CompletedTask;
}
/// <summary>
/// Updates an existing scope.
/// </summary>
/// <param name="scope">The scope to update.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
var session = await Context.GetSessionAsync(cancellationToken);
try
{
await session.UpdateAsync(scope, cancellationToken);
await session.FlushAsync(cancellationToken);
}
catch (StaleObjectStateException exception)
{
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
.Append("Reload the scope from the database and retry the operation.")
.ToString(), exception);
}
}
/// <summary>
/// Converts the provided identifier to a strongly typed key object.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>An instance of <typeparamref name="TKey"/> representing the provided identifier.</returns>
public virtual TKey ConvertIdentifierFromString([CanBeNull] string identifier)
{
if (string.IsNullOrEmpty(identifier))
{
return default;
}
return (TKey) TypeDescriptor.GetConverter(typeof(TKey)).ConvertFromInvariantString(identifier);
}
/// <summary>
/// Converts the provided identifier to its string representation.
/// </summary>
/// <param name="identifier">The identifier to convert.</param>
/// <returns>A <see cref="string"/> representation of the provided identifier.</returns>
public virtual string ConvertIdentifierToString([CanBeNull] TKey identifier)
{
if (Equals(identifier, default(TKey)))
{
return null;
}
return TypeDescriptor.GetConverter(typeof(TKey)).ConvertToInvariantString(identifier);
}
}
}

1111
src/OpenIddict.NHibernate/Stores/OpenIddictTokenStore.cs

File diff suppressed because it is too large

26
test/OpenIddict.NHibernate.Tests/OpenIddict.NHibernate.Tests.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\OpenIddict.NHibernate\OpenIddict.NHibernate.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

122
test/OpenIddict.NHibernate.Tests/OpenIddictNHibernateBuilderTests.cs

@ -0,0 +1,122 @@
/*
* 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.Options;
using Moq;
using NHibernate;
using OpenIddict.Core;
using OpenIddict.NHibernate.Models;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictNHibernateBuilderTests
{
[Fact]
public void Constructor_ThrowsAnExceptionForNullServices()
{
// Arrange
var services = (IServiceCollection) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => new OpenIddictNHibernateBuilder(services));
Assert.Equal("services", exception.ParamName);
}
[Fact]
public void ReplaceDefaultEntities_EntitiesAreCorrectlyReplaced()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceDefaultEntities<CustomApplication, CustomAuthorization, CustomScope, CustomToken, long>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(CustomApplication), options.DefaultApplicationType);
Assert.Equal(typeof(CustomAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(CustomScope), options.DefaultScopeType);
Assert.Equal(typeof(CustomToken), options.DefaultTokenType);
}
[Fact]
public void ReplaceDefaultEntities_AllowsSpecifyingCustomKeyType()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.ReplaceDefaultEntities<long>();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(OpenIddictApplication<long>), options.DefaultApplicationType);
Assert.Equal(typeof(OpenIddictAuthorization<long>), options.DefaultAuthorizationType);
Assert.Equal(typeof(OpenIddictScope<long>), options.DefaultScopeType);
Assert.Equal(typeof(OpenIddictToken<long>), options.DefaultTokenType);
}
[Fact]
public void UseSessionFactory_ThrowsAnExceptionForNullFactory()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(delegate
{
return builder.UseSessionFactory(factory: null);
});
Assert.Equal("factory", exception.ParamName);
}
[Fact]
public void UseSessionFactory_SetsDbContextTypeInOptions()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
var factory = Mock.Of<ISessionFactory>();
// Act
builder.UseSessionFactory(factory);
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictNHibernateOptions>>().CurrentValue;
Assert.Same(factory, options.SessionFactory);
}
private static OpenIddictNHibernateBuilder CreateBuilder(IServiceCollection services)
=> services.AddOpenIddict().AddCore().UseNHibernate();
private static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
services.AddOptions();
return services;
}
public class CustomApplication : OpenIddictApplication<long, CustomAuthorization, CustomToken> { }
public class CustomAuthorization : OpenIddictAuthorization<long, CustomApplication, CustomToken> { }
public class CustomScope : OpenIddictScope<long> { }
public class CustomToken : OpenIddictToken<long, CustomApplication, CustomAuthorization> { }
}
}

240
test/OpenIddict.NHibernate.Tests/OpenIddictNHibernateContextTests.cs

@ -0,0 +1,240 @@
/*
* 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.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using NHibernate;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictNHibernateContextTests
{
[Fact]
public async Task GetSessionAsync_ThrowsAnExceptionForCanceledToken()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>();
var token = new CancellationToken(canceled: true);
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
var exception = await Assert.ThrowsAsync<TaskCanceledException>(async delegate
{
await context.GetSessionAsync(token);
});
Assert.Equal(token, exception.CancellationToken);
}
[Fact]
public async Task GetSessionAsync_UsesSessionRegisteredInDependencyInjectionContainer()
{
// Arrange
var services = new ServiceCollection();
var session = new Mock<ISession>();
var factory = new Mock<ISessionFactory>();
services.AddSingleton(session.Object);
services.AddSingleton(factory.Object);
var provider = services.BuildServiceProvider();
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>(
mock => mock.CurrentValue == new OpenIddictNHibernateOptions
{
SessionFactory = null
});
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
Assert.Same(session.Object, await context.GetSessionAsync(CancellationToken.None));
factory.Verify(mock => mock.OpenSession(), Times.Never());
}
[Theory]
[InlineData(FlushMode.Always)]
[InlineData(FlushMode.Auto)]
[InlineData(FlushMode.Commit)]
[InlineData(FlushMode.Unspecified)]
public async Task GetSessionAsync_CreatesSubSessionWhenFlushModeIsNotManual(FlushMode mode)
{
// Arrange
var services = new ServiceCollection();
var session = new Mock<ISession>();
session.SetupProperty(mock => mock.FlushMode, mode);
var builder = new Mock<ISharedSessionBuilder>();
builder.Setup(mock => mock.AutoClose())
.Returns(builder.Object);
builder.Setup(mock => mock.AutoJoinTransaction())
.Returns(builder.Object);
builder.Setup(mock => mock.Connection())
.Returns(builder.Object);
builder.Setup(mock => mock.ConnectionReleaseMode())
.Returns(builder.Object);
builder.Setup(mock => mock.FlushMode(FlushMode.Manual))
.Returns(builder.Object);
builder.Setup(mock => mock.Interceptor())
.Returns(builder.Object);
builder.Setup(mock => mock.OpenSession())
.Returns(session.Object);
session.Setup(mock => mock.SessionWithOptions())
.Returns(builder.Object);
var factory = new Mock<ISessionFactory>();
services.AddSingleton(session.Object);
services.AddSingleton(factory.Object);
var provider = services.BuildServiceProvider();
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>(
mock => mock.CurrentValue == new OpenIddictNHibernateOptions
{
SessionFactory = null
});
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
Assert.Same(session.Object, await context.GetSessionAsync(CancellationToken.None));
builder.Verify(mock => mock.AutoClose(), Times.Once());
builder.Verify(mock => mock.AutoJoinTransaction(), Times.Once());
builder.Verify(mock => mock.Connection(), Times.Once());
builder.Verify(mock => mock.ConnectionReleaseMode(), Times.Once());
builder.Verify(mock => mock.FlushMode(FlushMode.Manual), Times.Once());
builder.Verify(mock => mock.Interceptor(), Times.Once());
builder.Verify(mock => mock.OpenSession(), Times.Once());
factory.Verify(mock => mock.OpenSession(), Times.Never());
}
[Fact]
public async Task GetSessionAsync_UsesSessionFactoryRegisteredInDependencyInjectionContainer()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<ISessionFactory>());
var session = new Mock<ISession>();
var factory = new Mock<ISessionFactory>();
factory.Setup(mock => mock.OpenSession())
.Returns(session.Object);
var provider = services.BuildServiceProvider();
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>(
mock => mock.CurrentValue == new OpenIddictNHibernateOptions
{
SessionFactory = factory.Object
});
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
Assert.Same(session.Object, await context.GetSessionAsync(CancellationToken.None));
factory.Verify(mock => mock.OpenSession(), Times.Once());
session.VerifySet(mock => mock.FlushMode = FlushMode.Manual, Times.Once());
}
[Fact]
public async Task GetSessionAsync_ThrowsAnExceptionWhenSessionFactoryCannotBeFound()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>(
mock => mock.CurrentValue == new OpenIddictNHibernateOptions
{
SessionFactory = null
});
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async delegate
{
await context.GetSessionAsync(CancellationToken.None);
});
Assert.Equal(new StringBuilder()
.AppendLine("No suitable NHibernate session or session factory can be found.")
.Append("To configure the OpenIddict NHibernate stores to use a specific factory, use ")
.Append("'services.AddOpenIddict().AddCore().UseNHibernate().UseSessionFactory()' or register an ")
.Append("'ISession'/'ISessionFactory' in the dependency injection container in 'ConfigureServices()'.")
.ToString(), exception.Message);
}
[Fact]
public async Task GetSessionAsync_PrefersSessionFactoryRegisteredInOptionsToSessionRegisteredInDependencyInjectionContainer()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<ISessionFactory>());
var session = new Mock<ISession>();
var factory = new Mock<ISessionFactory>();
factory.Setup(mock => mock.OpenSession())
.Returns(session.Object);
var provider = services.BuildServiceProvider();
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>(
mock => mock.CurrentValue == new OpenIddictNHibernateOptions
{
SessionFactory = factory.Object
});
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
Assert.Same(session.Object, await context.GetSessionAsync(CancellationToken.None));
factory.Verify(mock => mock.OpenSession(), Times.Once());
session.VerifySet(mock => mock.FlushMode = FlushMode.Manual, Times.Once());
}
[Fact]
public async Task GetSessionAsync_ReturnsCachedSession()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var factory = new Mock<ISessionFactory>();
factory.Setup(mock => mock.OpenSession())
.Returns(() => Mock.Of<ISession>());
var options = Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>(
mock => mock.CurrentValue == new OpenIddictNHibernateOptions
{
SessionFactory = factory.Object
});
var context = new OpenIddictNHibernateContext(options, provider);
// Act and assert
Assert.Same(
await context.GetSessionAsync(CancellationToken.None),
await context.GetSessionAsync(CancellationToken.None));
factory.Verify(mock => mock.OpenSession(), Times.Once());
}
}
}

102
test/OpenIddict.NHibernate.Tests/OpenIddictNHibernateExtensionsTests.cs

@ -0,0 +1,102 @@
/*
* 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.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.NHibernate.Models;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictNHibernateExtensionsTests
{
[Fact]
public void UseNHibernate_ThrowsAnExceptionForNullBuilder()
{
// Arrange
var builder = (OpenIddictCoreBuilder) null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.UseNHibernate());
Assert.Equal("builder", exception.ParamName);
}
[Fact]
public void UseNHibernate_ThrowsAnExceptionForNullConfiguration()
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.UseNHibernate(configuration: null));
Assert.Equal("configuration", exception.ParamName);
}
[Fact]
public void UseNHibernate_RegistersDefaultEntities()
{
// Arrange
var services = new ServiceCollection().AddOptions();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseNHibernate();
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictCoreOptions>>().CurrentValue;
Assert.Equal(typeof(OpenIddictApplication), options.DefaultApplicationType);
Assert.Equal(typeof(OpenIddictAuthorization), options.DefaultAuthorizationType);
Assert.Equal(typeof(OpenIddictScope), options.DefaultScopeType);
Assert.Equal(typeof(OpenIddictToken), options.DefaultTokenType);
}
[Theory]
[InlineData(typeof(IOpenIddictApplicationStoreResolver), typeof(OpenIddictApplicationStoreResolver))]
[InlineData(typeof(IOpenIddictAuthorizationStoreResolver), typeof(OpenIddictAuthorizationStoreResolver))]
[InlineData(typeof(IOpenIddictScopeStoreResolver), typeof(OpenIddictScopeStoreResolver))]
[InlineData(typeof(IOpenIddictTokenStoreResolver), typeof(OpenIddictTokenStoreResolver))]
public void UseNHibernate_RegistersNHibernateStoreResolvers(Type serviceType, Type implementationType)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseNHibernate();
// Assert
Assert.Contains(services, service => service.ServiceType == serviceType &&
service.ImplementationType == implementationType);
}
[Theory]
[InlineData(typeof(OpenIddictApplicationStore<,,,>))]
[InlineData(typeof(OpenIddictAuthorizationStore<,,,>))]
[InlineData(typeof(OpenIddictScopeStore<,>))]
[InlineData(typeof(OpenIddictTokenStore<,,,>))]
public void UseNHibernate_RegistersNHibernateStore(Type type)
{
// Arrange
var services = new ServiceCollection();
var builder = new OpenIddictCoreBuilder(services);
// Act
builder.UseNHibernate();
// Assert
Assert.Contains(services, service => service.ServiceType == type && service.ImplementationType == type);
}
}
}

83
test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictApplicationStoreResolverTests.cs

@ -0,0 +1,83 @@
/*
* 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.Text;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictApplicationStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictApplicationStore<CustomApplication>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictApplicationStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomApplication>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictApplicationStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => resolver.Get<CustomApplication>());
Assert.Equal(new StringBuilder()
.AppendLine("The specified application type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictApplication' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictApplication' entity.")
.ToString(), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictApplicationStore<CustomApplication>>());
services.AddSingleton(CreateStore());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictApplicationStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<MyApplication>());
}
private static OpenIddictApplicationStore<MyApplication, MyAuthorization, MyToken, long> CreateStore()
=> new Mock<OpenIddictApplicationStore<MyApplication, MyAuthorization, MyToken, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<IOpenIddictNHibernateContext>(),
Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>()).Object;
public class CustomApplication { }
public class MyApplication : OpenIddictApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictScope<long> { }
public class MyToken : OpenIddictToken<long, MyApplication, MyAuthorization> { }
}
}

83
test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictAuthorizationStoreResolverTests.cs

@ -0,0 +1,83 @@
/*
* 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.Text;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictAuthorizationStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictAuthorizationStore<CustomAuthorization>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictAuthorizationStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomAuthorization>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictAuthorizationStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => resolver.Get<CustomAuthorization>());
Assert.Equal(new StringBuilder()
.AppendLine("The specified authorization type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictAuthorization' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictAuthorization' entity.")
.ToString(), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictAuthorizationStore<CustomAuthorization>>());
services.AddSingleton(CreateStore());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictAuthorizationStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<MyAuthorization>());
}
private static OpenIddictAuthorizationStore<MyAuthorization, MyApplication, MyToken, long> CreateStore()
=> new Mock<OpenIddictAuthorizationStore<MyAuthorization, MyApplication, MyToken, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<IOpenIddictNHibernateContext>(),
Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>()).Object;
public class CustomAuthorization { }
public class MyApplication : OpenIddictApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictScope<long> { }
public class MyToken : OpenIddictToken<long, MyApplication, MyAuthorization> { }
}
}

83
test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictScopeStoreResolverTests.cs

@ -0,0 +1,83 @@
/*
* 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.Text;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictScopeStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictScopeStore<CustomScope>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictScopeStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomScope>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictScopeStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => resolver.Get<CustomScope>());
Assert.Equal(new StringBuilder()
.AppendLine("The specified scope type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictScope' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictScope' entity.")
.ToString(), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictScopeStore<CustomScope>>());
services.AddSingleton(CreateStore());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictScopeStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<MyScope>());
}
private static OpenIddictScopeStore<MyScope, long> CreateStore()
=> new Mock<OpenIddictScopeStore<MyScope, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<IOpenIddictNHibernateContext>(),
Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>()).Object;
public class CustomScope { }
public class MyApplication : OpenIddictApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictScope<long> { }
public class MyToken : OpenIddictToken<long, MyApplication, MyAuthorization> { }
}
}

83
test/OpenIddict.NHibernate.Tests/Resolvers/OpenIddictTokenStoreResolverTests.cs

@ -0,0 +1,83 @@
/*
* 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.Text;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using OpenIddict.Abstractions;
using OpenIddict.NHibernate.Models;
using Xunit;
namespace OpenIddict.NHibernate.Tests
{
public class OpenIddictTokenStoreResolverTests
{
[Fact]
public void Get_ReturnsCustomStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictTokenStore<CustomToken>>());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictTokenStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<CustomToken>());
}
[Fact]
public void Get_ThrowsAnExceptionForInvalidEntityType()
{
// Arrange
var services = new ServiceCollection();
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictTokenStoreResolver(provider);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => resolver.Get<CustomToken>());
Assert.Equal(new StringBuilder()
.AppendLine("The specified token type is not compatible with the NHibernate stores.")
.Append("When enabling the NHibernate stores, make sure you use the built-in ")
.Append("'OpenIddictToken' entity (from the 'OpenIddict.NHibernate.Models' package) ")
.Append("or a custom entity that inherits from the generic 'OpenIddictToken' entity.")
.ToString(), exception.Message);
}
[Fact]
public void Get_ReturnsDefaultStoreCorrespondingToTheSpecifiedTypeWhenAvailable()
{
// Arrange
var services = new ServiceCollection();
services.AddSingleton(Mock.Of<IOpenIddictTokenStore<CustomToken>>());
services.AddSingleton(CreateStore());
var provider = services.BuildServiceProvider();
var resolver = new OpenIddictTokenStoreResolver(provider);
// Act and assert
Assert.NotNull(resolver.Get<MyToken>());
}
private static OpenIddictTokenStore<MyToken, MyApplication, MyAuthorization, long> CreateStore()
=> new Mock<OpenIddictTokenStore<MyToken, MyApplication, MyAuthorization, long>>(
Mock.Of<IMemoryCache>(),
Mock.Of<IOpenIddictNHibernateContext>(),
Mock.Of<IOptionsMonitor<OpenIddictNHibernateOptions>>()).Object;
public class CustomToken { }
public class MyApplication : OpenIddictApplication<long, MyAuthorization, MyToken> { }
public class MyAuthorization : OpenIddictAuthorization<long, MyApplication, MyToken> { }
public class MyScope : OpenIddictScope<long> { }
public class MyToken : OpenIddictToken<long, MyApplication, MyAuthorization> { }
}
}
Loading…
Cancel
Save