Added validation, storage tests

This commit is contained in:
Javier Calvarro Nelson 2017-05-29 19:25:32 -07:00
parent 48f9d47e90
commit 904ff0e060
16 changed files with 3661 additions and 312 deletions

View File

@ -28,13 +28,16 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
{
private bool _disposed;
public ApplicationStore(TContext context)
public ApplicationStore(TContext context, ApplicationErrorDescriber errorDescriber)
{
Context = context;
ErrorDescriber = errorDescriber;
}
public TContext Context { get; }
public ApplicationErrorDescriber ErrorDescriber { get; }
public DbSet<TApplication> ApplicationsSet => Context.Set<TApplication>();
public DbSet<TScope> Scopes => Context.Set<TScope>();
@ -83,7 +86,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
catch (DbUpdateConcurrencyException)
{
return IdentityServiceResult.Failed(new IdentityServiceError() { Description = "Concurrency failure" });
return IdentityServiceResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityServiceResult.Success;
}
@ -104,7 +107,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
catch (DbUpdateConcurrencyException)
{
return IdentityServiceResult.Failed(new IdentityServiceError() { Description = "Concurrency failure" });
return IdentityServiceResult.Failed(ErrorDescriber.ConcurrencyFailure());
}
return IdentityServiceResult.Success;
}
@ -118,16 +121,34 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var oldQueryBehavior = Context.ChangeTracker.QueryTrackingBehavior;
return Applications.SingleOrDefaultAsync(a => a.ClientId == clientId, cancellationToken);
try
{
Context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return Applications.SingleOrDefaultAsync(a => a.ClientId == clientId, cancellationToken);
}
finally
{
Context.ChangeTracker.QueryTrackingBehavior = oldQueryBehavior;
}
}
public Task<TApplication> FindByNameAsync(string name, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
var oldQueryBehavior = Context.ChangeTracker.QueryTrackingBehavior;
return Applications.SingleOrDefaultAsync(a => a.Name == name, cancellationToken);
try
{
Context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return Applications.SingleOrDefaultAsync(a => a.Name == name, cancellationToken);
}
finally
{
Context.ChangeTracker.QueryTrackingBehavior = oldQueryBehavior;
}
}
public async Task<IEnumerable<TApplication>> FindByUserIdAsync(string userId, CancellationToken cancellationToken)
@ -195,7 +216,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
return redirectUris;
}
public async Task<IdentityServiceResult> RegisterRedirectUriAsync(TApplication app, string redirectUri, CancellationToken cancellationToken)
public Task<IdentityServiceResult> RegisterRedirectUriAsync(TApplication app, string redirectUri, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@ -209,17 +230,9 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
throw new ArgumentNullException(nameof(redirectUri));
}
var existingRedirectUri = await RedirectUris.SingleOrDefaultAsync(
ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(redirectUri) && !ru.IsLogout);
if (existingRedirectUri != null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "A route with the same value already exists." });
}
RedirectUris.Add(CreateRedirectUri(app, redirectUri, isLogout: false));
return IdentityServiceResult.Success;
return Task.FromResult(IdentityServiceResult.Success);
}
private TRedirectUri CreateRedirectUri(TApplication app, string redirectUri, bool isLogout)
@ -249,12 +262,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
var registeredUri = await RedirectUris
.SingleOrDefaultAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(redirectUri) && !ru.IsLogout);
if (registeredUri == null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "We were unable to find the redirect uri to unregister." });
}
.SingleAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(redirectUri) && !ru.IsLogout);
RedirectUris.Remove(registeredUri);
@ -285,13 +293,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
var existingRedirectUri = await RedirectUris
.SingleOrDefaultAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(oldRedirectUri) && !ru.IsLogout);
if (existingRedirectUri == null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "We were unable to find the registered redirect uri to update." });
}
.SingleAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(oldRedirectUri) && !ru.IsLogout);
existingRedirectUri.Value = newRedirectUri;
@ -315,7 +317,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
return redirectUris;
}
public async Task<IdentityServiceResult> RegisterLogoutRedirectUriAsync(TApplication app, string redirectUri, CancellationToken cancellationToken)
public Task<IdentityServiceResult> RegisterLogoutRedirectUriAsync(TApplication app, string redirectUri, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@ -329,17 +331,9 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
throw new ArgumentNullException(nameof(redirectUri));
}
var existingRedirectUri = await RedirectUris.SingleOrDefaultAsync(
ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(redirectUri) && ru.IsLogout);
if (existingRedirectUri != null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "A route with the same value already exists." });
}
RedirectUris.Add(CreateRedirectUri(app, redirectUri, isLogout: true));
return IdentityServiceResult.Success;
return Task.FromResult(IdentityServiceResult.Success);
}
public async Task<IdentityServiceResult> UnregisterLogoutRedirectUriAsync(TApplication app, string redirectUri, CancellationToken cancellationToken)
@ -357,12 +351,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
var registeredUri = await RedirectUris
.SingleOrDefaultAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(redirectUri) && ru.IsLogout);
if (registeredUri == null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "We were unable to find the redirect uri to unregister." });
}
.SingleAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(redirectUri) && ru.IsLogout);
RedirectUris.Remove(registeredUri);
@ -389,13 +378,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
var existingRedirectUri = await RedirectUris
.SingleOrDefaultAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(oldRedirectUri) && ru.IsLogout);
if (existingRedirectUri == null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "We were unable to find the registered redirect uri to update." });
}
.SingleAsync(ru => ru.ApplicationId.Equals(app.Id) && ru.Value.Equals(oldRedirectUri) && ru.IsLogout);
existingRedirectUri.Value = newRedirectUri;
@ -513,7 +496,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
return scopes;
}
public async Task<IdentityServiceResult> AddScopeAsync(TApplication application, string scope, CancellationToken cancellationToken)
public Task<IdentityServiceResult> AddScopeAsync(TApplication application, string scope, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
@ -527,17 +510,9 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
throw new ArgumentNullException(nameof(scope));
}
var existingScope = await Scopes.SingleOrDefaultAsync(
ru => ru.ApplicationId.Equals(application.Id) && ru.Value.Equals(scope));
if (existingScope != null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "A scope with the same value already exists." });
}
Scopes.Add(CreateScope(application, scope));
return IdentityServiceResult.Success;
return Task.FromResult(IdentityServiceResult.Success);
}
private TScope CreateScope(TApplication application, string scope)
@ -569,13 +544,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
var existingScope = await Scopes
.SingleOrDefaultAsync(s => s.ApplicationId.Equals(application.Id) && s.Value.Equals(oldScope));
if (existingScope == null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "We were unable to find the scope to update." });
}
.SingleAsync(s => s.ApplicationId.Equals(application.Id) && s.Value.Equals(oldScope));
existingScope.Value = newScope;
return IdentityServiceResult.Success;
@ -596,13 +565,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore
}
var existingScope = await Scopes
.SingleOrDefaultAsync(ru => ru.ApplicationId.Equals(application.Id) && ru.Value.Equals(scope));
if (existingScope == null)
{
return IdentityServiceResult.Failed(
new IdentityServiceError { Description = "We were unable to find the scope to remove." });
}
.SingleAsync(ru => ru.ApplicationId.Equals(application.Id) && ru.Value.Equals(scope));
Scopes.Remove(existingScope);
return IdentityServiceResult.Success;

View File

@ -25,7 +25,6 @@ namespace Microsoft.AspNetCore.Identity.Service
IdentityServiceRedirectUri<TApplicationKey>>
where TApplicationKey : IEquatable<TApplicationKey>
where TUserKey : IEquatable<TUserKey>
{
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.Test;
using Microsoft.Extensions.Configuration;
@ -53,6 +54,8 @@ namespace Microsoft.AspNetCore.Identity.Service.Specification.Tests
new IdentityBuilder(typeof(TestUser), typeof(TestRole), services)
.AddApplications<TUser, TApplication>(options => { });
services.AddSingleton<IApplicationValidator<TApplication>, ClaimValidator>();
AddApplicationStore(services, context);
services.AddLogging();
services.AddSingleton<ILogger<ApplicationManager<TApplication>>>(new TestLogger<ApplicationManager<TApplication>>());
@ -135,6 +138,65 @@ namespace Microsoft.AspNetCore.Identity.Service.Specification.Tests
Assert.NotNull(await manager.FindByIdAsync(await manager.GetApplicationIdAsync(application)));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanFindByName()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.NotNull(await manager.FindByNameAsync(await manager.GetApplicationNameAsync(application)));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task SetApplicationName()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var name = await manager.GetApplicationNameAsync(application);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
var newName = Guid.NewGuid().ToString();
Assert.Null(await manager.FindByNameAsync(newName));
IdentityServiceResultAssert.IsSuccess(await manager.SetApplicationNameAsync(application, newName));
Assert.Null(await manager.FindByNameAsync(name));
Assert.NotNull(await manager.FindByNameAsync(newName));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task SetApplicationName_ValidatesNewName()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var name = await manager.GetApplicationNameAsync(application);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
var newName = "";
Assert.Null(await manager.FindByNameAsync(newName));
IdentityServiceResultAssert.IsFailure(await manager.SetApplicationNameAsync(application, newName));
}
/// <summary>
/// Test.
/// </summary>
@ -175,18 +237,18 @@ namespace Microsoft.AspNetCore.Identity.Service.Specification.Tests
}
var registeredUris = await manager.FindRegisteredUrisAsync(application);
foreach (var uri in redirectUris)
foreach (var uri in registeredUris)
{
Assert.Contains(uri, registeredUris);
Assert.Contains(uri, redirectUris);
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
/// <returns></returns>
[Fact]
public async Task CanGetScopesForApplication()
public async Task RegisterRedirectUrisForApplicationValidatesUris()
{
if (ShouldSkipDbTests())
{
@ -195,26 +257,669 @@ namespace Microsoft.AspNetCore.Identity.Service.Specification.Tests
var manager = CreateManager();
var application = CreateTestApplication();
var scopes = GenerateScopes(nameof(CanGetScopesForApplication), 2);
var redirect = "";
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
foreach (var redirect in scopes)
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.RegisterRedirectUriAsync(application, redirect));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanUpdateRedirectUri()
{
if (ShouldSkipDbTests())
{
await manager.AddScopeAsync(application, redirect);
return;
}
var applicationScopes = await manager.FindScopesAsync(application);
foreach (var scope in scopes)
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("login", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.RegisterRedirectUriAsync(application, redirect[0]));
Assert.Equal(redirect[0], (await manager.FindRegisteredUrisAsync(application)).First());
IdentityServiceResultAssert.IsSuccess(await manager.UpdateRedirectUriAsync(application, redirect[0], redirect[1]));
Assert.Equal(redirect[1], (await manager.FindRegisteredUrisAsync(application)).First());
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UpdateRedirectUriValidatesRedirectUri()
{
if (ShouldSkipDbTests())
{
Assert.Contains(scope, applicationScopes);
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("login", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.RegisterRedirectUriAsync(application, redirect[0]));
Assert.Equal(redirect[0], (await manager.FindRegisteredUrisAsync(application)).First());
IdentityServiceResultAssert.IsFailure(await manager.UpdateRedirectUriAsync(application, redirect[0], ""));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UpdateRedirectUriFailsIfItDoesNotFindTheUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("login", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.UpdateRedirectUriAsync(application, redirect[0], redirect[1]));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanUnregisterRedirectUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("login", 1).Single();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.RegisterRedirectUriAsync(application, redirect));
Assert.Equal(redirect, (await manager.FindRegisteredUrisAsync(application)).Single());
IdentityServiceResultAssert.IsSuccess(await manager.UnregisterRedirectUriAsync(application, redirect));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UnregisterRedirectUriFailsIfItDoesNotFindTheUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("login", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredUrisAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.UnregisterRedirectUriAsync(application, redirect[0]));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanGetLogoutUrisForApplication()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var logoutUris = GenerateRedirectUris(nameof(CanGetLogoutUrisForApplication), 2);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
foreach (var logout in logoutUris)
{
await manager.RegisterLogoutUriAsync(application, logout);
}
var registeredLogoutUris = await manager.FindRegisteredLogoutUrisAsync(application);
foreach (var uri in registeredLogoutUris)
{
Assert.Contains(uri, logoutUris);
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task RegisterLogoutUrisForApplicationValidatesUris()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = "";
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.RegisterLogoutUriAsync(application, redirect));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanUpdateLogoutUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("logout", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.RegisterLogoutUriAsync(application, redirect[0]));
Assert.Equal(redirect[0], (await manager.FindRegisteredLogoutUrisAsync(application)).First());
IdentityServiceResultAssert.IsSuccess(await manager.UpdateLogoutUriAsync(application, redirect[0], redirect[1]));
Assert.Equal(redirect[1], (await manager.FindRegisteredLogoutUrisAsync(application)).First());
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UpdateLogoutUriValidatesRedirectUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("logout", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.RegisterLogoutUriAsync(application, redirect[0]));
Assert.Equal(redirect[0], (await manager.FindRegisteredLogoutUrisAsync(application)).First());
IdentityServiceResultAssert.IsFailure(await manager.UpdateLogoutUriAsync(application, redirect[0], ""));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UpdateLogoutUriFailsIfItDoesNotFindTheUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("logout", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.UpdateLogoutUriAsync(application, redirect[0], redirect[1]));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanUnregisterLogoutUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("logout", 1).Single();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.RegisterLogoutUriAsync(application, redirect));
Assert.Equal(redirect, (await manager.FindRegisteredLogoutUrisAsync(application)).Single());
IdentityServiceResultAssert.IsSuccess(await manager.UnregisterLogoutUriAsync(application, redirect));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UnregisterLogoutUriFailsIfItDoesNotFindTheUri()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var redirect = GenerateRedirectUris("logout", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindRegisteredLogoutUrisAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.UnregisterLogoutUriAsync(application, redirect[0]));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanGetScopes()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scopes = GenerateScopes(nameof(CanGetScopes), 2);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
foreach (var scope in scopes)
{
await manager.AddScopeAsync(application, scope);
}
var applicationScopes = await manager.FindScopesAsync(application);
foreach (var scope in applicationScopes)
{
Assert.Contains(scope, scopes);
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanAddScopes()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scope = "offline_access";
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddScopeAsync(application, scope));
Assert.NotEmpty(await manager.FindScopesAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task AddScopesValidatesScopes()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scope = "";
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.AddScopeAsync(application, scope));
Assert.Empty(await manager.FindScopesAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanUpdateScopes()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scopes = GenerateScopes("UpdateScopes", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddScopeAsync(application, scopes[0]));
Assert.Equal(scopes[0], (await manager.FindScopesAsync(application)).First());
IdentityServiceResultAssert.IsSuccess(await manager.UpdateScopeAsync(application, scopes[0], scopes[1]));
Assert.Equal(scopes[1], (await manager.FindScopesAsync(application)).First());
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UpdateScopeValidatesScope()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scopes = GenerateScopes("ValidateScope", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddScopeAsync(application, scopes[0]));
Assert.Equal(scopes[0], (await manager.FindScopesAsync(application)).First());
IdentityServiceResultAssert.IsFailure(await manager.UpdateScopeAsync(application, scopes[0], ""));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task UpdateScopeFailsIfItDoesNotFindTheScope()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scope = GenerateScopes("UpdateScopeNoScope", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.UpdateScopeAsync(application, scope[0], scope[1]));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanRemoveScope()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scope = GenerateScopes(nameof(CanRemoveScope), 1).Single();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddScopeAsync(application, scope));
Assert.Equal(scope, (await manager.FindScopesAsync(application)).Single());
IdentityServiceResultAssert.IsSuccess(await manager.RemoveScopeAsync(application, scope));
Assert.Empty(await manager.FindScopesAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task RemoveScopeFailsIfItDoesNotFindTheScope()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scope = GenerateScopes("RemoveScopeValidates", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.FindScopesAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.RemoveScopeAsync(application, scope[0]));
}
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task CanGetClaimsForApplication()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var claims = GenerateClaims(nameof(CanGetClaimsForApplication), 2);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
foreach (var claim in claims)
{
await manager.AddClaimAsync(application, claim);
}
var applicationClaims = await manager.GetClaimsAsync(application);
foreach (var claim in applicationClaims)
{
Assert.Contains(claim, claims, ClaimComparer.Instance);
}
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanAddClaims()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var claim = new Claim("type", "value");
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.GetClaimsAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddClaimAsync(application, claim));
Assert.NotEmpty(await manager.GetClaimsAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task AddClaimsValidatesClaims()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var scope = new Claim("fail", "fail");
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.GetClaimsAsync(application));
IdentityServiceResultAssert.IsFailure(await manager.AddClaimAsync(application, scope));
Assert.Empty(await manager.GetClaimsAsync(application));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanUpdateClaims()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var claims = GenerateClaims("UpdateClaims", 2).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.GetClaimsAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddClaimAsync(application, claims[0]));
Assert.Equal(claims[0], (await manager.GetClaimsAsync(application)).First(), ClaimComparer.Instance);
IdentityServiceResultAssert.IsSuccess(await manager.ReplaceClaimAsync(application, claims[0], claims[1]));
Assert.Equal(claims[1], (await manager.GetClaimsAsync(application)).First(), ClaimComparer.Instance);
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task ReplaceClaimValidatesClaim()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var claims = GenerateClaims("ValidateClaim", 1).ToArray();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.GetClaimsAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddClaimAsync(application, claims[0]));
Assert.Equal(claims[0], (await manager.GetClaimsAsync(application)).First(), ClaimComparer.Instance);
IdentityServiceResultAssert.IsFailure(await manager.ReplaceClaimAsync(application, claims[0], new Claim("fail", "fail")));
}
/// <summary>
/// Test.
/// </summary>
/// <returns></returns>
[Fact]
public async Task CanRemoveClaim()
{
if (ShouldSkipDbTests())
{
return;
}
var manager = CreateManager();
var application = CreateTestApplication();
var claim = GenerateClaims(nameof(CanRemoveClaim), 1).Single();
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
Assert.Empty(await manager.GetClaimsAsync(application));
IdentityServiceResultAssert.IsSuccess(await manager.AddClaimAsync(application, claim));
Assert.Equal(claim, (await manager.GetClaimsAsync(application)).Single(),ClaimComparer.Instance);
IdentityServiceResultAssert.IsSuccess(await manager.RemoveClaimAsync(application, claim));
Assert.Empty(await manager.GetClaimsAsync(application));
}
private IEnumerable<string> GenerateRedirectUris(string prefix, int count) =>
Enumerable.Range(0, count).Select(i => $"https://www.example.com/{prefix}/{count}");
Enumerable.Range(0, count).Select(i => $"https://www.example.com/{prefix}/{i}");
private IEnumerable<string> GenerateScopes(string prefix, int count) =>
Enumerable.Range(0, count).Select(i => $"{prefix}_{count}");
Enumerable.Range(0, count).Select(i => $"{prefix}_{i}");
private IEnumerable<Claim> GenerateClaims(string prefix, int count) =>
Enumerable.Range(0, count).Select(i => new Claim($"{prefix}_type_{i}", $"{prefix}_value_{i}"));
private class ClaimComparer : IEqualityComparer<Claim>
{
public static readonly ClaimComparer Instance = new ClaimComparer();
public bool Equals(Claim x, Claim y) => x?.Type == y?.Type && x?.Value == y?.Value;
public int GetHashCode(Claim obj)
{
throw new NotImplementedException();
}
}
private class ClaimValidator : IApplicationValidator<TApplication>
{
public Task<IdentityServiceResult> ValidateAsync(ApplicationManager<TApplication> manager, TApplication application)
{
return Task.FromResult(IdentityServiceResult.Success);
}
public Task<IdentityServiceResult> ValidateClaimAsync(ApplicationManager<TApplication> manager, TApplication application, Claim claim)
{
return Task.FromResult(claim.Type.Equals("fail") ? IdentityServiceResult.Failed(new IdentityServiceError()) : IdentityServiceResult.Success);
}
public Task<IdentityServiceResult> ValidateLogoutUriAsync(ApplicationManager<TApplication> manager, TApplication application, string logoutUri)
{
return Task.FromResult(IdentityServiceResult.Success);
}
public Task<IdentityServiceResult> ValidateRedirectUriAsync(ApplicationManager<TApplication> manager, TApplication application, string redirectUri)
{
return Task.FromResult(IdentityServiceResult.Success);
}
public Task<IdentityServiceResult> ValidateScopeAsync(ApplicationManager<TApplication> manager, TApplication application, string scope)
{
return Task.FromResult(IdentityServiceResult.Success);
}
}
private class TestUser { }
private class TestRole { }

View File

@ -0,0 +1,111 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Identity.Service
{
public class ApplicationErrorDescriber
{
public virtual IdentityServiceError InvalidApplicationName(string applicationName) => new IdentityServiceError
{
Code = nameof(InvalidApplicationName),
Description = $"The application name '{applicationName}' is not valid."
};
public virtual IdentityServiceError DuplicateApplicationName(string applicationName) => new IdentityServiceError
{
Code = nameof(DuplicateApplicationName),
Description = $"An application with name '{applicationName}' already exists."
};
public virtual IdentityServiceError InvalidApplicationClientId(string clientId) => new IdentityServiceError
{
Code = nameof(InvalidApplicationClientId),
Description = $"The application client ID '{clientId}' is not valid."
};
public virtual IdentityServiceError DuplicateApplicationClientId(string clientId) => new IdentityServiceError
{
Code = nameof(DuplicateApplicationClientId),
Description = $"An application with client ID '{clientId}' already exists."
};
public virtual IdentityServiceError DuplicateLogoutUri(string logoutUri) => new IdentityServiceError
{
Code = nameof(DuplicateLogoutUri),
Description = $"The application already contains a logout uri '{logoutUri}'."
};
public virtual IdentityServiceError InvalidLogoutUri(string logoutUri) => new IdentityServiceError
{
Code = nameof(InvalidLogoutUri),
Description = $"The logout uri '{logoutUri}' is not valid."
};
public virtual IdentityServiceError NoHttpsUri(string logoutUri) => new IdentityServiceError
{
Code = nameof(NoHttpsUri),
Description = $"The uri '{logoutUri}' must use https."
};
public virtual IdentityServiceError DifferentDomains() => new IdentityServiceError
{
Code = nameof(DifferentDomains),
Description = $"All the URIs in an application must have the same domain."
};
public virtual IdentityServiceError DuplicateRedirectUri(string redirectUri) => new IdentityServiceError
{
Code = nameof(DuplicateRedirectUri),
Description = $"The application already contains a redirect uri '{redirectUri}'."
};
public virtual IdentityServiceError InvalidRedirectUri(string redirectUri) => new IdentityServiceError
{
Code = nameof(InvalidRedirectUri),
Description = $"The redirect URI '{redirectUri}' is not valid."
};
public virtual IdentityServiceError InvalidScope(string scope) => new IdentityServiceError
{
Code = nameof(InvalidScope),
Description = $"The scope '{scope}' is not valid."
};
public virtual IdentityServiceError DuplicateScope(string scope) => new IdentityServiceError
{
Code = nameof(DuplicateScope),
Description = $"The application already contains a scope '{scope}'."
};
public virtual IdentityServiceError ApplicationAlreadyHasClientSecret() => new IdentityServiceError {
Code = nameof(ApplicationAlreadyHasClientSecret),
Description = $"The application already has a client secret."
};
public virtual IdentityServiceError RedirectUriNotFound(string redirectUri) => new IdentityServiceError
{
Code = nameof(RedirectUriNotFound),
Description = $"The redirect uri '{redirectUri}' can not be found."
};
public virtual IdentityServiceError LogoutUriNotFound(string logoutUri) => new IdentityServiceError
{
Code = nameof(LogoutUriNotFound),
Description = $"The logout uri '{logoutUri}' can not be found."
};
public virtual IdentityServiceError ConcurrencyFailure() => new IdentityServiceError
{
Code = nameof(ConcurrencyFailure),
Description = $"Optimistic concurrency failure, object has been modified."
};
public virtual IdentityServiceError ScopeNotFound(string scope) => new IdentityServiceError
{
Code = nameof(ScopeNotFound),
Description = $"The scope '{scope}' can not be found."
};
}
}

View File

@ -8,6 +8,7 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Identity.Service
{
@ -16,20 +17,26 @@ namespace Microsoft.AspNetCore.Identity.Service
private bool _disposed;
public ApplicationManager(
IOptions<ApplicationOptions> options,
IApplicationStore<TApplication> store,
IPasswordHasher<TApplication> passwordHasher,
IEnumerable<IApplicationValidator<TApplication>> applicationValidators,
ILogger<ApplicationManager<TApplication>> logger)
ILogger<ApplicationManager<TApplication>> logger,
ApplicationErrorDescriber errorDescriber)
{
Options = options.Value;
Store = store;
PasswordHasher = passwordHasher;
ApplicationValidators = applicationValidators;
ErrorDescriber = errorDescriber;
Logger = Logger;
}
public ApplicationOptions Options { get; }
public IApplicationStore<TApplication> Store { get; set; }
public IPasswordHasher<TApplication> PasswordHasher { get; set; }
public IEnumerable<IApplicationValidator<TApplication>> ApplicationValidators { get; set; }
public ApplicationErrorDescriber ErrorDescriber { get; }
public ILogger<ApplicationManager<TApplication>> Logger { get; set; }
public CancellationToken CancellationToken { get; set; }
@ -60,16 +67,51 @@ namespace Microsoft.AspNetCore.Identity.Service
return Store.FindByIdAsync(applicationId, CancellationToken);
}
public Task<string> GetApplicationIdAsync(TApplication application)
{
ThrowIfDisposed();
return Store.GetApplicationIdAsync(application, CancellationToken);
}
public Task<TApplication> FindByClientIdAsync(string clientId)
{
return Store.FindByClientIdAsync(clientId, CancellationToken.None);
}
public Task<string> GetApplicationClientIdAsync(TApplication application)
{
ThrowIfDisposed();
return Store.GetApplicationClientIdAsync(application, CancellationToken);
}
public Task<TApplication> FindByNameAsync(string name)
{
return Store.FindByNameAsync(name, CancellationToken.None);
}
public Task<string> GetApplicationNameAsync(TApplication application)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return Store.GetApplicationNameAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> SetApplicationNameAsync(TApplication application, string name)
{
ThrowIfDisposed();
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
await Store.SetApplicationNameAsync(application, name, CancellationToken);
return await UpdateAsync(application);
}
public virtual async Task<IdentityServiceResult> CreateAsync(TApplication application)
{
ThrowIfDisposed();
@ -87,17 +129,6 @@ namespace Microsoft.AspNetCore.Identity.Service
return await Store.CreateAsync(application, CancellationToken);
}
public Task<string> GetApplicationNameAsync(TApplication application)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return Store.GetApplicationNameAsync(application, CancellationToken);
}
public virtual async Task<IdentityServiceResult> DeleteAsync(TApplication application)
{
ThrowIfDisposed();
@ -126,48 +157,23 @@ namespace Microsoft.AspNetCore.Identity.Service
return await Store.UpdateAsync(application, CancellationToken);
}
private async Task<IdentityServiceResult> ValidateApplicationAsync(TApplication application)
{
var errors = new List<IdentityServiceError>();
foreach (var v in ApplicationValidators)
{
var result = await v.ValidateAsync(this, application);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
return IdentityServiceResult.Failed(errors.ToArray());
}
return IdentityServiceResult.Success;
}
public Task<string> GetApplicationIdAsync(TApplication application)
{
ThrowIfDisposed();
return Store.GetApplicationIdAsync(application, CancellationToken);
}
public Task<string> GetApplicationClientIdAsync(TApplication application)
{
ThrowIfDisposed();
return Store.GetApplicationClientIdAsync(application, CancellationToken);
}
public void Dispose()
{
_disposed = true;
}
public Task<string> GenerateClientSecretAsync()
{
return Task.FromResult(CryptographyHelpers.GenerateHighEntropyValue(byteLength: 32));
}
public Task<bool> HasClientSecretAsync(TApplication application)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var store = GetClientSecretStore();
return store.HasClientSecretAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> AddClientSecretAsync(TApplication application, string clientSecret)
{
ThrowIfDisposed();
@ -180,9 +186,9 @@ namespace Microsoft.AspNetCore.Identity.Service
var hash = await store.GetClientSecretHashAsync(application, CancellationToken);
if (hash != null)
{
Logger.LogWarning(1, "User {clientId} already has a password.", await GetApplicationClientIdAsync(application));
return IdentityServiceResult.Failed();
return IdentityServiceResult.Failed(ErrorDescriber.ApplicationAlreadyHasClientSecret());
}
var result = await UpdateClientSecretHashAsync(store, application, clientSecret);
if (!result.Succeeded)
{
@ -210,54 +216,6 @@ namespace Microsoft.AspNetCore.Identity.Service
return await UpdateAsync(application);
}
public async Task<IdentityServiceResult> RegisterLogoutUriAsync(TApplication application, string logoutUri)
{
ThrowIfDisposed();
var redirectStore = GetRedirectUriStore();
var result = await redirectStore.RegisterLogoutRedirectUriAsync(application, logoutUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UnregisterLogoutUriAsync(TApplication application, string logoutUri)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (logoutUri == null)
{
throw new ArgumentNullException(nameof(logoutUri));
}
var redirectStore = GetRedirectUriStore();
var result = await redirectStore.UnregisterLogoutRedirectUriAsync(application, logoutUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> SetApplicationNameAsync(TApplication application, string name)
{
ThrowIfDisposed();
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
await Store.SetApplicationNameAsync(application, name, CancellationToken);
return await UpdateAsync(application);
}
public async Task<IdentityServiceResult> RemoveClientSecretAsync(TApplication application)
{
ThrowIfDisposed();
@ -276,88 +234,6 @@ namespace Microsoft.AspNetCore.Identity.Service
return await UpdateAsync(application);
}
private IRedirectUriStore<TApplication> GetRedirectUriStore()
{
if (Store is IRedirectUriStore<TApplication> cast)
{
return cast;
}
throw new NotSupportedException();
}
public async Task<IdentityServiceResult> RegisterRedirectUriAsync(TApplication application, string redirectUri)
{
ThrowIfDisposed();
var redirectStore = GetRedirectUriStore();
var result = await redirectStore.RegisterRedirectUriAsync(application, redirectUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public Task<IEnumerable<string>> FindRegisteredUrisAsync(TApplication application)
{
var redirectStore = GetRedirectUriStore();
return redirectStore.FindRegisteredUrisAsync(application, CancellationToken);
}
public Task<IEnumerable<string>> FindRegisteredLogoutUrisAsync(TApplication application)
{
var redirectStore = GetRedirectUriStore();
return redirectStore.FindRegisteredLogoutUrisAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UnregisterRedirectUriAsync(TApplication application, string redirectUri)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (redirectUri == null)
{
throw new ArgumentNullException(nameof(redirectUri));
}
var redirectStore = GetRedirectUriStore();
var result = await redirectStore.UnregisterRedirectUriAsync(application, redirectUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public Task<bool> HasClientSecretAsync(TApplication application)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
var store = GetClientSecretStore();
return store.HasClientSecretAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UpdateRedirectUriAsync(TApplication application, string oldRedirectUri, string newRedirectUri)
{
var redirectStore = GetRedirectUriStore();
var result = await redirectStore.UpdateRedirectUriAsync(application, oldRedirectUri, newRedirectUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public async Task<bool> ValidateClientCredentialsAsync(string clientId, string clientSecret)
{
var application = await FindByClientIdAsync(clientId);
@ -413,14 +289,248 @@ namespace Microsoft.AspNetCore.Identity.Service
return PasswordHasher.VerifyHashedPassword(application, hash, clientSecret);
}
private IApplicationClientSecretStore<TApplication> GetClientSecretStore()
public Task<IEnumerable<string>> FindRegisteredUrisAsync(TApplication application)
{
if (Store is IApplicationClientSecretStore<TApplication> cast)
var redirectStore = GetRedirectUriStore();
return redirectStore.FindRegisteredUrisAsync(application, CancellationToken);
}
private async Task<string> FindRegisteredUriAsync(TApplication application, string redirectUri)
{
var uris = await FindRegisteredUrisAsync(application);
foreach (var uri in uris)
{
return cast;
if (string.Equals(uri, redirectUri, StringComparison.Ordinal))
{
return redirectUri;
}
}
throw new NotSupportedException();
return null;
}
public async Task<IdentityServiceResult> RegisterRedirectUriAsync(TApplication application, string redirectUri)
{
ThrowIfDisposed();
var redirectStore = GetRedirectUriStore();
var validation = await ValidateRedirectUriAsync(application, redirectUri);
if (!validation.Succeeded)
{
return validation;
}
var result = await redirectStore.RegisterRedirectUriAsync(application, redirectUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UpdateRedirectUriAsync(TApplication application, string oldRedirectUri, string newRedirectUri)
{
var redirectStore = GetRedirectUriStore();
var registeredUri = await FindRegisteredUriAsync(application, oldRedirectUri);
if (registeredUri == null)
{
return IdentityServiceResult.Failed(ErrorDescriber.RedirectUriNotFound(oldRedirectUri));
}
var validation = await ValidateRedirectUriAsync(application, newRedirectUri);
if (!validation.Succeeded)
{
return validation;
}
var result = await redirectStore.UpdateRedirectUriAsync(application, oldRedirectUri, newRedirectUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UnregisterRedirectUriAsync(TApplication application, string redirectUri)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (redirectUri == null)
{
throw new ArgumentNullException(nameof(redirectUri));
}
var registeredUri = await FindRegisteredUriAsync(application, redirectUri);
if (registeredUri == null)
{
return IdentityServiceResult.Failed(ErrorDescriber.RedirectUriNotFound(redirectUri));
}
var redirectStore = GetRedirectUriStore();
var result = await redirectStore.UnregisterRedirectUriAsync(application, redirectUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
private async Task<IdentityServiceResult> ValidateRedirectUriAsync(TApplication application, string redirectUri)
{
var errors = new List<IdentityServiceError>();
foreach (var v in ApplicationValidators)
{
var result = await v.ValidateRedirectUriAsync(this, application, redirectUri);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
return IdentityServiceResult.Failed(errors.ToArray());
}
return IdentityServiceResult.Success;
}
public Task<IEnumerable<string>> FindRegisteredLogoutUrisAsync(TApplication application)
{
var redirectStore = GetRedirectUriStore();
return redirectStore.FindRegisteredLogoutUrisAsync(application, CancellationToken);
}
private async Task<string> FindRegisteredLogoutUriAsync(TApplication application, string redirectUri)
{
var uris = await FindRegisteredLogoutUrisAsync(application);
foreach (var uri in uris)
{
if (string.Equals(uri, redirectUri, StringComparison.Ordinal))
{
return redirectUri;
}
}
return null;
}
public async Task<IdentityServiceResult> RegisterLogoutUriAsync(TApplication application, string logoutUri)
{
ThrowIfDisposed();
var redirectStore = GetRedirectUriStore();
var validation = await ValidateLogoutUriAsync(application, logoutUri);
if (!validation.Succeeded)
{
return validation;
}
var result = await redirectStore.RegisterLogoutRedirectUriAsync(application, logoutUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UpdateLogoutUriAsync(TApplication application, string oldLogoutUri, string newLogoutUri)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (oldLogoutUri == null)
{
throw new ArgumentNullException(nameof(oldLogoutUri));
}
if (newLogoutUri == null)
{
throw new ArgumentNullException(nameof(newLogoutUri));
}
var redirectUriStore = GetRedirectUriStore();
var registeredUri = await FindRegisteredLogoutUriAsync(application, oldLogoutUri);
if (registeredUri == null)
{
return IdentityServiceResult.Failed(ErrorDescriber.LogoutUriNotFound(oldLogoutUri));
}
var validation = await ValidateLogoutUriAsync(application, newLogoutUri);
if (!validation.Succeeded)
{
return validation;
}
var result = await redirectUriStore.UpdateLogoutRedirectUriAsync(application, oldLogoutUri, newLogoutUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await UpdateAsync(application);
}
public async Task<IdentityServiceResult> UnregisterLogoutUriAsync(TApplication application, string logoutUri)
{
ThrowIfDisposed();
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
if (logoutUri == null)
{
throw new ArgumentNullException(nameof(logoutUri));
}
var redirectStore = GetRedirectUriStore();
var registeredUri = await FindRegisteredLogoutUriAsync(application, logoutUri);
if (registeredUri == null)
{
return IdentityServiceResult.Failed(ErrorDescriber.LogoutUriNotFound(logoutUri));
}
var result = await redirectStore.UnregisterLogoutRedirectUriAsync(application, logoutUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await redirectStore.UpdateAsync(application, CancellationToken);
}
private async Task<IdentityServiceResult> ValidateLogoutUriAsync(TApplication application, string redirectUri)
{
var errors = new List<IdentityServiceError>();
foreach (var v in ApplicationValidators)
{
var result = await v.ValidateLogoutUriAsync(this, application, redirectUri);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
return IdentityServiceResult.Failed(errors.ToArray());
}
return IdentityServiceResult.Success;
}
public Task<IEnumerable<string>> FindScopesAsync(TApplication application)
@ -429,22 +539,31 @@ namespace Microsoft.AspNetCore.Identity.Service
return scopeStore.FindScopesAsync(application, CancellationToken);
}
private async Task<string> FindScopeAsync(TApplication application, string scope)
{
var scopes = await FindScopesAsync(application);
foreach (var foundScope in scopes)
{
if (string.Equals(scope, foundScope, StringComparison.Ordinal))
{
return foundScope;
}
}
return null;
}
public async Task<IdentityServiceResult> AddScopeAsync(TApplication application, string scope)
{
var scopeStore = GetScopeStore();
var result = await scopeStore.AddScopeAsync(application, scope, CancellationToken);
if (!result.Succeeded)
var validation = await ValidateScopeAsync(application, scope);
if (!validation.Succeeded)
{
return result;
return validation;
}
return await scopeStore.UpdateAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> RemoveScopeAsync(TApplication application, string scope)
{
var scopeStore = GetScopeStore();
var result = await scopeStore.RemoveScopeAsync(application, scope, CancellationToken);
var result = await scopeStore.AddScopeAsync(application, scope, CancellationToken);
if (!result.Succeeded)
{
return result;
@ -456,6 +575,18 @@ namespace Microsoft.AspNetCore.Identity.Service
public async Task<IdentityServiceResult> UpdateScopeAsync(TApplication application, string oldScope, string newScope)
{
var scopeStore = GetScopeStore();
var scope = await FindScopeAsync(application, oldScope);
if (scope == null)
{
return IdentityServiceResult.Failed(ErrorDescriber.ScopeNotFound(oldScope));
}
var validation = await ValidateScopeAsync(application, newScope);
if (!validation.Succeeded)
{
return validation;
}
var result = await scopeStore.UpdateScopeAsync(application, oldScope, newScope, CancellationToken);
if (!result.Succeeded)
{
@ -465,14 +596,42 @@ namespace Microsoft.AspNetCore.Identity.Service
return await scopeStore.UpdateAsync(application, CancellationToken);
}
private IApplicationScopeStore<TApplication> GetScopeStore()
public async Task<IdentityServiceResult> RemoveScopeAsync(TApplication application, string scope)
{
if (Store is IApplicationScopeStore<TApplication> cast)
var scopeStore = GetScopeStore();
var foundScope = await FindScopeAsync(application, scope);
if (foundScope == null)
{
return cast;
return IdentityServiceResult.Failed(ErrorDescriber.ScopeNotFound(scope));
}
throw new NotSupportedException();
var result = await scopeStore.RemoveScopeAsync(application, scope, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await scopeStore.UpdateAsync(application, CancellationToken);
}
private async Task<IdentityServiceResult> ValidateScopeAsync(TApplication application, string scope)
{
var errors = new List<IdentityServiceError>();
foreach (var v in ApplicationValidators)
{
var result = await v.ValidateScopeAsync(this, application, scope);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
return IdentityServiceResult.Failed(errors.ToArray());
}
return IdentityServiceResult.Success;
}
public virtual Task<IdentityServiceResult> AddClaimAsync(TApplication application, Claim claim)
@ -503,6 +662,15 @@ namespace Microsoft.AspNetCore.Identity.Service
throw new ArgumentNullException(nameof(application));
}
foreach (var claim in claims)
{
var validation = await ValidateClaimAsync(application, claim);
if (!validation.Succeeded)
{
return validation;
}
}
await claimStore.AddClaimsAsync(application, claims, CancellationToken);
return await UpdateAsync(application);
}
@ -524,6 +692,12 @@ namespace Microsoft.AspNetCore.Identity.Service
throw new ArgumentNullException(nameof(application));
}
var validation = await ValidateClaimAsync(application, newClaim);
if (!validation.Succeeded)
{
return validation;
}
await claimStore.ReplaceClaimAsync(application, claim, newClaim, CancellationToken);
return await UpdateAsync(application);
}
@ -560,6 +734,26 @@ namespace Microsoft.AspNetCore.Identity.Service
return await UpdateAsync(application);
}
private async Task<IdentityServiceResult> ValidateClaimAsync(TApplication application, Claim claim)
{
var errors = new List<IdentityServiceError>();
foreach (var v in ApplicationValidators)
{
var result = await v.ValidateClaimAsync(this, application, claim);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
return IdentityServiceResult.Failed(errors.ToArray());
}
return IdentityServiceResult.Success;
}
public virtual async Task<IList<Claim>> GetClaimsAsync(TApplication application)
{
ThrowIfDisposed();
@ -571,32 +765,34 @@ namespace Microsoft.AspNetCore.Identity.Service
return await claimStore.GetClaimsAsync(application, CancellationToken);
}
public async Task<IdentityServiceResult> UpdateLogoutUriAsync(TApplication application, string oldLogoutUri, string newLogoutUri)
private IRedirectUriStore<TApplication> GetRedirectUriStore()
{
ThrowIfDisposed();
if (application == null)
if (Store is IRedirectUriStore<TApplication> cast)
{
throw new ArgumentNullException(nameof(application));
return cast;
}
if (oldLogoutUri == null)
throw new NotSupportedException();
}
private IApplicationClientSecretStore<TApplication> GetClientSecretStore()
{
if (Store is IApplicationClientSecretStore<TApplication> cast)
{
throw new ArgumentNullException(nameof(oldLogoutUri));
return cast;
}
if (newLogoutUri == null)
throw new NotSupportedException();
}
private IApplicationScopeStore<TApplication> GetScopeStore()
{
if (Store is IApplicationScopeStore<TApplication> cast)
{
throw new ArgumentNullException(nameof(newLogoutUri));
return cast;
}
var redirectUriStore = GetRedirectUriStore();
var result = await redirectUriStore.UpdateLogoutRedirectUriAsync(application, oldLogoutUri, newLogoutUri, CancellationToken);
if (!result.Succeeded)
{
return result;
}
return await UpdateAsync(application);
throw new NotSupportedException();
}
private IApplicationClaimStore<TApplication> GetApplicationClaimStore()
@ -616,5 +812,30 @@ namespace Microsoft.AspNetCore.Identity.Service
throw new ObjectDisposedException(GetType().Name);
}
}
private async Task<IdentityServiceResult> ValidateApplicationAsync(TApplication application)
{
var errors = new List<IdentityServiceError>();
foreach (var v in ApplicationValidators)
{
var result = await v.ValidateAsync(this, application);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
return IdentityServiceResult.Failed(errors.ToArray());
}
return IdentityServiceResult.Success;
}
public void Dispose()
{
_disposed = true;
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Identity.Service
{
public class ApplicationOptions
{
public string AllowedNameCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
public int? MaxApplicationNameLength { get; set; } = 36;
public string AllowedClientIdCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
public int? MaxClientIdLength { get; set; } = 36;
public string AllowedScopeCharacters { get; set; } = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
public int? MaxScopeLength { get; set; } = 16;
public IList<string> AllowedRedirectUris { get; set; } = new List<string>
{
"urn:ietf:wg:oauth:2.0:oob"
};
public IList<string> AllowedLogoutUris { get; set; } = new List<string>
{
"urn:ietf:wg:oauth:2.0:oob"
};
}
}

View File

@ -0,0 +1,232 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Identity.Service
{
public class ApplicationValidator<TApplication> : IApplicationValidator<TApplication>
where TApplication : class
{
public ApplicationValidator(ApplicationErrorDescriber errorDescriber)
{
ErrorDescriber = errorDescriber;
}
public ApplicationErrorDescriber ErrorDescriber { get; }
public async Task<IdentityServiceResult> ValidateAsync(
ApplicationManager<TApplication> manager,
TApplication application)
{
var errors = new List<IdentityServiceError>();
await ValidateNameAsync(manager, application, errors);
await ValidateClientIdAsync(manager, application, errors);
return errors.Count > 0 ? IdentityServiceResult.Failed(errors.ToArray()) : IdentityServiceResult.Success;
}
private async Task ValidateNameAsync(
ApplicationManager<TApplication> manager,
TApplication application,
IList<IdentityServiceError> errors)
{
var applicationName = await manager.GetApplicationNameAsync(application);
if (string.IsNullOrWhiteSpace(applicationName))
{
errors.Add(ErrorDescriber.InvalidApplicationName(applicationName));
}
else if (!string.IsNullOrEmpty(manager.Options.AllowedNameCharacters) &&
applicationName.Any(c => !manager.Options.AllowedNameCharacters.Contains(c)))
{
errors.Add(ErrorDescriber.InvalidApplicationName(applicationName));
}
else if (manager.Options.MaxApplicationNameLength.HasValue &&
applicationName.Length > manager.Options.MaxApplicationNameLength)
{
errors.Add(ErrorDescriber.InvalidApplicationName(applicationName));
}
else
{
var otherApplication = await manager.FindByNameAsync(applicationName);
if (otherApplication != null &&
!string.Equals(
await manager.GetApplicationIdAsync(otherApplication),
await manager.GetApplicationIdAsync(application),
StringComparison.Ordinal))
{
errors.Add(ErrorDescriber.DuplicateApplicationName(applicationName));
}
}
}
private async Task ValidateClientIdAsync(
ApplicationManager<TApplication> manager,
TApplication application,
IList<IdentityServiceError> errors)
{
var clientId = await manager.GetApplicationClientIdAsync(application);
if (string.IsNullOrWhiteSpace(clientId))
{
errors.Add(ErrorDescriber.InvalidApplicationClientId(clientId));
}
else if (!string.IsNullOrEmpty(manager.Options.AllowedClientIdCharacters) &&
clientId.Any(c => !manager.Options.AllowedClientIdCharacters.Contains(c)))
{
errors.Add(ErrorDescriber.InvalidApplicationClientId(clientId));
}
else if (manager.Options.MaxApplicationNameLength.HasValue &&
clientId.Length > manager.Options.MaxApplicationNameLength)
{
errors.Add(ErrorDescriber.InvalidApplicationClientId(clientId));
}
else
{
var otherApplication = await manager.FindByClientIdAsync(clientId);
if (otherApplication != null &&
!string.Equals(
await manager.GetApplicationIdAsync(otherApplication),
await manager.GetApplicationIdAsync(application),
StringComparison.Ordinal))
{
errors.Add(ErrorDescriber.DuplicateApplicationClientId(clientId));
}
}
}
public async Task<IdentityServiceResult> ValidateLogoutUriAsync(
ApplicationManager<TApplication> manager,
TApplication application,
string logoutUri)
{
var errors = new List<IdentityServiceError>();
var logoutUris = await manager.FindRegisteredLogoutUrisAsync(application);
if (logoutUris.Contains(logoutUri, StringComparer.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.DuplicateLogoutUri(logoutUri));
}
if (!manager.Options.AllowedLogoutUris.Contains(logoutUri, StringComparer.OrdinalIgnoreCase))
{
if (!Uri.TryCreate(logoutUri, UriKind.Absolute, out var parsedUri))
{
errors.Add(ErrorDescriber.InvalidLogoutUri(logoutUri));
}
else
{
if (!parsedUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.NoHttpsUri(logoutUri));
}
var redirectUris = await manager.FindRegisteredUrisAsync(application);
var regularRedirectUris = redirectUris.Except(manager.Options.AllowedRedirectUris, StringComparer.Ordinal);
var regularLogoutUris = logoutUris.Except(manager.Options.AllowedLogoutUris, StringComparer.Ordinal);
var allApplicationUris = regularLogoutUris.Concat(regularRedirectUris);
foreach (var nonSpecialUri in allApplicationUris)
{
var existingUri = new Uri(nonSpecialUri, UriKind.Absolute);
if (!parsedUri.Host.Equals(existingUri.Host, StringComparison.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.DifferentDomains());
break;
}
}
}
}
return errors.Count > 0 ? IdentityServiceResult.Failed(errors.ToArray()) : IdentityServiceResult.Success;
}
public async Task<IdentityServiceResult> ValidateRedirectUriAsync(
ApplicationManager<TApplication> manager,
TApplication application,
string redirectUri)
{
var errors = new List<IdentityServiceError>();
var redirectUris = await manager.FindRegisteredUrisAsync(application);
if (redirectUris.Contains(redirectUri, StringComparer.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.DuplicateRedirectUri(redirectUri));
}
if (!manager.Options.AllowedRedirectUris.Contains(redirectUri, StringComparer.OrdinalIgnoreCase))
{
if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var parsedUri))
{
errors.Add(ErrorDescriber.InvalidRedirectUri(redirectUri));
}
else
{
if (!parsedUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.NoHttpsUri(redirectUri));
}
var logoutUris = await manager.FindRegisteredUrisAsync(application);
var regularLogoutUris = logoutUris.Except(manager.Options.AllowedLogoutUris, StringComparer.Ordinal);
var regularRedirectUris = redirectUris.Except(manager.Options.AllowedRedirectUris, StringComparer.Ordinal);
var allApplicationUris = regularRedirectUris.Concat(regularLogoutUris);
foreach (var nonSpecialUri in allApplicationUris)
{
var existingUri = new Uri(nonSpecialUri, UriKind.Absolute);
if (!parsedUri.Host.Equals(existingUri.Host, StringComparison.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.DifferentDomains());
break;
}
}
}
}
return errors.Count > 0 ? IdentityServiceResult.Failed(errors.ToArray()) : IdentityServiceResult.Success;
}
public async Task<IdentityServiceResult> ValidateScopeAsync(
ApplicationManager<TApplication> manager,
TApplication application,
string scope)
{
var errors = new List<IdentityServiceError>();
if (string.IsNullOrWhiteSpace(scope))
{
errors.Add(ErrorDescriber.InvalidScope(scope));
}
else if (!string.IsNullOrEmpty(manager.Options.AllowedScopeCharacters) &&
scope.Any(c => !manager.Options.AllowedScopeCharacters.Contains(c)))
{
errors.Add(ErrorDescriber.InvalidScope(scope));
}
else if (manager.Options.MaxScopeLength.HasValue &&
scope.Length > manager.Options.MaxScopeLength)
{
errors.Add(ErrorDescriber.InvalidScope(scope));
}
else
{
var scopes = await manager.FindScopesAsync(application);
if (scopes != null && scopes.Contains(scope, StringComparer.OrdinalIgnoreCase))
{
errors.Add(ErrorDescriber.DuplicateScope(scope));
}
}
return errors.Count > 0 ? IdentityServiceResult.Failed(errors.ToArray()) : IdentityServiceResult.Success;
}
public Task<IdentityServiceResult> ValidateClaimAsync(ApplicationManager<TApplication> manager, TApplication application, Claim claim)
{
return Task.FromResult(IdentityServiceResult.Success);
}
}
}

View File

@ -10,9 +10,9 @@ namespace Microsoft.AspNetCore.Identity.Service
{
public interface IApplicationClaimStore<TApplication> : IApplicationStore<TApplication> where TApplication : class
{
Task<IList<Claim>> GetClaimsAsync(TApplication user, CancellationToken cancellationToken);
Task AddClaimsAsync(TApplication user, IEnumerable<Claim> claims, CancellationToken cancellationToken);
Task ReplaceClaimAsync(TApplication user, Claim claim, Claim newClaim, CancellationToken cancellationToken);
Task RemoveClaimsAsync(TApplication user, IEnumerable<Claim> claims, CancellationToken cancellationToken);
Task<IList<Claim>> GetClaimsAsync(TApplication application, CancellationToken cancellationToken);
Task AddClaimsAsync(TApplication application, IEnumerable<Claim> claims, CancellationToken cancellationToken);
Task ReplaceClaimAsync(TApplication application, Claim claim, Claim newClaim, CancellationToken cancellationToken);
Task RemoveClaimsAsync(TApplication application, IEnumerable<Claim> claims, CancellationToken cancellationToken);
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Identity.Service
@ -9,5 +10,9 @@ namespace Microsoft.AspNetCore.Identity.Service
where TApplication : class
{
Task<IdentityServiceResult> ValidateAsync(ApplicationManager<TApplication> manager, TApplication application);
Task<IdentityServiceResult> ValidateScopeAsync(ApplicationManager<TApplication> manager, TApplication application, string scope);
Task<IdentityServiceResult> ValidateRedirectUriAsync(ApplicationManager<TApplication> manager, TApplication application, string redirectUri);
Task<IdentityServiceResult> ValidateLogoutUriAsync(ApplicationManager<TApplication> manager, TApplication application, string logoutUri);
Task<IdentityServiceResult> ValidateClaimAsync(ApplicationManager<TApplication> manager, TApplication application, Claim claim);
}
}

View File

@ -93,6 +93,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IPasswordHasher<TApplication>, PasswordHasher<TApplication>>();
services.AddScoped<ISigningCredentialsPolicyProvider, DefaultSigningCredentialsPolicyProvider>();
services.AddScoped<ISigningCredentialsSource, DefaultSigningCredentialsSource>();
services.AddSingleton<IApplicationValidator<TApplication>, ApplicationValidator<TApplication>>();
services.AddSingleton<ApplicationErrorDescriber>();
// Session
services.AddTransient<SessionManager, SessionManager<TUser, TApplication>>();

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.InMemory.Tes
protected override void AddApplicationStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IApplicationStore<IdentityServiceApplication>>(
new ApplicationStore<IdentityServiceApplication, IdentityServiceScope<string>, IdentityServiceApplicationClaim<string>, IdentityServiceRedirectUri<string>, InMemoryContext, string, string>((InMemoryContext)context));
new ApplicationStore<IdentityServiceApplication, IdentityServiceScope<string>, IdentityServiceApplicationClaim<string>, IdentityServiceRedirectUri<string>, InMemoryContext, string, string>((InMemoryContext)context, new ApplicationErrorDescriber()));
}
protected override IdentityServiceApplication CreateTestApplication()

View File

@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test;
using Microsoft.AspNetCore.Identity.Service.Specification.Tests;
using Microsoft.AspNetCore.Identity.Test;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
@ -15,6 +17,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.Test
public class ApplicationStoreTest : IdentityServiceSpecificationTestBase<IdentityUser, IdentityServiceApplication>, IClassFixture<ScratchDatabaseFixture>
{
private readonly ScratchDatabaseFixture _fixture;
public static readonly ApplicationErrorDescriber ErrorDescriber = new ApplicationErrorDescriber();
public ApplicationStoreTest(ScratchDatabaseFixture fixture)
{
@ -24,7 +27,7 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.Test
protected override void AddApplicationStore(IServiceCollection services, object context = null)
{
services.AddSingleton<IApplicationStore<IdentityServiceApplication>>(
new ApplicationStore<IdentityServiceApplication, IdentityServiceScope<string>, IdentityServiceApplicationClaim<string>, IdentityServiceRedirectUri<string>, IdentityServiceDbContext<IdentityUser, IdentityServiceApplication>, string, string>((IdentityServiceDbContext<IdentityUser, IdentityServiceApplication>)context));
new ApplicationStore<IdentityServiceApplication, IdentityServiceScope<string>, IdentityServiceApplicationClaim<string>, IdentityServiceRedirectUri<string>, IdentityServiceDbContext<IdentityUser, IdentityServiceApplication>, string, string>((IdentityServiceDbContext<IdentityUser, IdentityServiceApplication>)context, new ApplicationErrorDescriber()));
}
public IdentityServiceDbContext<IdentityUser, IdentityServiceApplication> CreateContext(bool delete = false)
@ -59,5 +62,89 @@ namespace Microsoft.AspNetCore.Identity.Service.EntityFrameworkCore.Test
}
protected override bool ShouldSkipDbTests() => TestPlatformHelper.IsMono || !TestPlatformHelper.IsWindows;
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
public async Task ConcurrentUpdatesWillFail()
{
var application = CreateTestApplication();
using (var db = CreateContext())
{
var manager = CreateManager(db);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
}
using (var db = CreateContext())
using (var db2 = CreateContext())
{
var manager1 = CreateManager(db);
var manager2 = CreateManager(db2);
var application1 = await manager1.FindByIdAsync(application.Id);
var application2 = await manager2.FindByIdAsync(application.Id);
Assert.NotNull(application1);
Assert.NotNull(application2);
Assert.NotSame(application1, application2);
application1.Name = Guid.NewGuid().ToString();
application2.Name = Guid.NewGuid().ToString();
IdentityServiceResultAssert.IsSuccess(await manager1.UpdateAsync(application1));
IdentityServiceResultAssert.IsFailure(await manager2.UpdateAsync(application2), ErrorDescriber.ConcurrencyFailure());
}
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
public async Task ConcurrentUpdatesWillFailWithDetachedApplication()
{
var application = CreateTestApplication();
using (var db = CreateContext())
{
var manager = CreateManager(db);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
}
using (var db1 = CreateContext())
using (var db2 = CreateContext())
{
var manager1 = CreateManager(db1);
var manager2 = CreateManager(db2);
var application2 = await manager2.FindByIdAsync(application.Id);
Assert.NotNull(application2);
Assert.NotSame(application, application2);
application.Name= Guid.NewGuid().ToString();
application2.Name = Guid.NewGuid().ToString();
IdentityServiceResultAssert.IsSuccess(await manager1.UpdateAsync(application));
IdentityServiceResultAssert.IsFailure(await manager2.UpdateAsync(application2), ErrorDescriber.ConcurrencyFailure());
}
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
public async Task DeleteAModifiedApplicationWillFail()
{
var application = CreateTestApplication();
using (var db = CreateContext())
{
var manager = CreateManager(db);
IdentityServiceResultAssert.IsSuccess(await manager.CreateAsync(application));
}
using (var db = CreateContext())
using (var db2 = CreateContext())
{
var manager1 = CreateManager(db);
var manager2 = CreateManager(db2);
var application1 = await manager1.FindByIdAsync(application.Id);
var application2 = await manager2.FindByIdAsync(application.Id);
Assert.NotNull(application1);
Assert.NotNull(application2);
Assert.NotSame(application1, application2);
application1.Name = Guid.NewGuid().ToString();
IdentityServiceResultAssert.IsSuccess(await manager1.UpdateAsync(application1));
IdentityServiceResultAssert.IsFailure(await manager2.DeleteAsync(application2), ErrorDescriber.ConcurrencyFailure());
}
}
}
}

View File

@ -172,6 +172,12 @@ namespace Microsoft.AspNetCore.Identity.Service.InMemory.Test
return Task.CompletedTask;
}
public Task SetApplicationNameAsync(TApplication application, string name, CancellationToken cancellationToken)
{
application.Name = name;
return Task.CompletedTask;
}
public Task SetClientSecretHashAsync(TApplication application, string clientSecretHash, CancellationToken cancellationToken)
{
application.ClientSecretHash = clientSecretHash;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,525 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Identity.Service.Test
{
public class ApplicationValidatorTest
{
public ApplicationErrorDescriber errorDescriber = new ApplicationErrorDescriber();
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("~/")]
[InlineData("0123456789012345678901234567789001234567890")]
public async Task ValidateApplication_FailsForInvalidNames(string name)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication(name: name, clientId: Guid.NewGuid().ToString());
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError>
{
errorDescriber.InvalidApplicationName(name)
};
// Act
var result = await validator.ValidateAsync(manager, application);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Theory]
[InlineData("TestApplication")]
[InlineData("testapplication")]
[InlineData("TESTAPPLICATION")]
public async Task ValidateApplication_FailsForDuplicateApplicationNames(string name)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication("ApplicationId", name, Guid.NewGuid().ToString());
var manager = CreateTestManager(duplicateName: true);
var expectedError = new List<IdentityServiceError>
{
errorDescriber.DuplicateApplicationName(name)
};
// Act
var result = await validator.ValidateAsync(manager, application);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("~/")]
[InlineData("0123456789012345678901234567789001234567890")]
public async Task ValidateApplication_FailsForInvalidClientIds(string clientId)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication(clientId: clientId);
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError>
{
errorDescriber.InvalidApplicationClientId(clientId)
};
// Act
var result = await validator.ValidateAsync(manager, application);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Theory]
[InlineData("ClientId")]
[InlineData("clientid")]
[InlineData("CLIENTID")]
public async Task ValidateApplication_FailsForDuplicateClientIds(string clientId)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication("ApplicationId", "TestApplication", clientId);
var manager = CreateTestManager(duplicateClientId: true);
var expectedError = new List<IdentityServiceError>
{
errorDescriber.DuplicateApplicationClientId(clientId)
};
// Act
var result = await validator.ValidateAsync(manager, application);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateApplication_SucceedsWhenNameAndClientIdAreValid()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
// Act
var result = await validator.ValidateAsync(manager, application);
// Assert
Assert.True(result.Succeeded);
}
[Theory]
[InlineData("urn:ietf:wg:oauth:2.0:oob")]
[InlineData("URN:IETF:WG:OAUTH:2.0:OOB")]
[InlineData("https://www.example.com/signout-oidc")]
[InlineData("HTTPS://WWW.EXAMPLE.COM/SIGNOUT-OIDC")]
public async Task ValidateLogoutUri_FailsIfTheApplicationAlreadyContainsTheUri(string logoutUri)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.DuplicateLogoutUri(logoutUri) };
// Act
var result = await validator.ValidateLogoutUriAsync(manager, application, logoutUri);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateLogoutUri_FailsIfTheUriIsRelative()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.InvalidLogoutUri("/signout-oidc") };
// Act
var result = await validator.ValidateLogoutUriAsync(manager, application, "/signout-oidc");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateLogoutUri_FailsIfTheUriIsNotHttps()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.NoHttpsUri("http://www.example.com/signout-oidc") };
// Act
var result = await validator.ValidateLogoutUriAsync(manager, application, "http://www.example.com/signout-oidc");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateLogoutUri_FailsIfTheUriIsNotInTheSameDomainAsTheOthers()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.DifferentDomains() };
// Act
var result = await validator.ValidateLogoutUriAsync(manager, application, "https://www.contoso.com/signout-oidc");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateLogoutUri_FailsFailsForOtherNonHttpsUris()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> {
errorDescriber.NoHttpsUri("urn:self:aspnet:identity:integrated"),
errorDescriber.DifferentDomains()
};
// Act
var result = await validator.ValidateLogoutUriAsync(manager, application, "urn:self:aspnet:identity:integrated");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Theory]
[InlineData("https://www.example.com/another-path")]
[InlineData("HTTPS://WWW.EXAMPLE.COM/ANOTHER-PATH")]
public async Task ValidateLogoutUri_SucceedsForOtherUrisOnTheSameDomain(string logoutUri)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
// Act
var result = await validator.ValidateLogoutUriAsync(manager, application, logoutUri);
// Assert
Assert.True(result.Succeeded);
}
[Theory]
[InlineData("urn:ietf:wg:oauth:2.0:oob")]
[InlineData("URN:IETF:WG:OAUTH:2.0:OOB")]
[InlineData("https://www.example.com/signin-oidc")]
[InlineData("HTTPS://WWW.EXAMPLE.COM/SIGNIN-OIDC")]
public async Task ValidateRedirectUri_FailsIfTheApplicationAlreadyContainsTheUri(string redirectUri)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.DuplicateRedirectUri(redirectUri) };
// Act
var result = await validator.ValidateRedirectUriAsync(manager, application, redirectUri);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateRedirectUri_FailsIfTheUriIsRelative()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.InvalidRedirectUri("/signin-oidc") };
// Act
var result = await validator.ValidateRedirectUriAsync(manager, application, "/signin-oidc");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateRedirectUri_FailsIfTheUriIsNotHttps()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.NoHttpsUri("http://www.example.com/signin-oidc") };
// Act
var result = await validator.ValidateRedirectUriAsync(manager, application, "http://www.example.com/signin-oidc");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateRedirectUri_FailsIfTheUriIsNotInTheSameDomainAsTheOthers()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> { errorDescriber.DifferentDomains() };
// Act
var result = await validator.ValidateRedirectUriAsync(manager, application, "https://www.contoso.com/signin-oidc");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateRedirectUri_FailsForOtherNonHttpsUris()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError> {
errorDescriber.NoHttpsUri("urn:self:aspnet:identity:integrated"),
errorDescriber.DifferentDomains()
};
// Act
var result = await validator.ValidateRedirectUriAsync(manager, application, "urn:self:aspnet:identity:integrated");
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Theory]
[InlineData("https://www.example.com/another-path")]
[InlineData("HTTPS://WWW.EXAMPLE.COM/ANOTHER-PATH")]
public async Task ValidateRedirectUri_SucceedsForOtherUrisOnTheSameDomain(string redirectUri)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
// Act
var result = await validator.ValidateRedirectUriAsync(manager, application, redirectUri);
// Assert
Assert.True(result.Succeeded);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData("~/")]
[InlineData("012345678901234567890")]
public async Task ValidateScope_FailsForInvalidScopeValues(string scope)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError>
{
errorDescriber.InvalidScope(scope)
};
// Act
var result = await validator.ValidateScopeAsync(manager, application, scope);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Theory]
[InlineData("openid")]
[InlineData("openID")]
[InlineData("OPENID")]
public async Task ValidateScope_FailsForDuplicateScopeValues(string scope)
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
var expectedError = new List<IdentityServiceError>
{
errorDescriber.DuplicateScope(scope)
};
// Act
var result = await validator.ValidateScopeAsync(manager, application, scope);
// Assert
Assert.False(result.Succeeded);
Assert.Equal(expectedError, result.Errors, ErrorsComparer.Instance);
}
[Fact]
public async Task ValidateScope_SucceedsWhenScopesAreValid()
{
// Arrange
var validator = new ApplicationValidator<TestApplication>(new ApplicationErrorDescriber());
var application = CreateApplication();
var manager = CreateTestManager();
// Act
var result = await validator.ValidateScopeAsync(manager, application, "offline_access");
// Assert
Assert.True(result.Succeeded);
}
private TestApplication CreateApplication(
string id = "Id",
string name = "TestApplication",
string clientId = "ClientId") => new TestApplication
{
Id = id,
Name = name,
ClientId = clientId,
RedirectUris = new List<string>
{
"urn:ietf:wg:oauth:2.0:oob",
"https://www.example.com/signin-oidc"
},
LogoutUris = new List<string>
{
"urn:ietf:wg:oauth:2.0:oob",
"https://www.example.com/signout-oidc"
},
Scopes = new List<string> { "openid" }
};
private ApplicationManager<TestApplication> CreateTestManager(bool duplicateName = false, bool duplicateClientId = false)
{
var otherApplication = CreateApplication();
var store = new Mock<IApplicationStore<TestApplication>>();
if (duplicateName)
{
store.Setup(s => s.FindByNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(otherApplication);
}
if (duplicateClientId)
{
store.Setup(s => s.FindByClientIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(otherApplication);
}
store.Setup(s => s.GetApplicationIdAsync(It.IsAny<TestApplication>(), It.IsAny<CancellationToken>()))
.ReturnsAsync<TestApplication, CancellationToken, IApplicationStore<TestApplication>, string>((a, ct) => a.Id);
store.Setup(s => s.GetApplicationNameAsync(It.IsAny<TestApplication>(), It.IsAny<CancellationToken>()))
.ReturnsAsync<TestApplication, CancellationToken, IApplicationStore<TestApplication>, string>((a, ct) => a.Name);
store.Setup(s => s.GetApplicationClientIdAsync(It.IsAny<TestApplication>(), It.IsAny<CancellationToken>()))
.ReturnsAsync<TestApplication, CancellationToken, IApplicationStore<TestApplication>, string>((a, ct) => a.ClientId);
store.As<IRedirectUriStore<TestApplication>>()
.Setup(s => s.FindRegisteredUrisAsync(It.IsAny<TestApplication>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(otherApplication.RedirectUris);
store.As<IRedirectUriStore<TestApplication>>()
.Setup(s => s.FindRegisteredLogoutUrisAsync(It.IsAny<TestApplication>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(otherApplication.LogoutUris);
store.As<IApplicationScopeStore<TestApplication>>()
.Setup(s => s.FindScopesAsync(It.IsAny<TestApplication>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(otherApplication.Scopes);
return new ApplicationManager<TestApplication>(
Options.Create(new ApplicationOptions()),
store.Object,
Mock.Of<IPasswordHasher<TestApplication>>(),
Enumerable.Empty<IApplicationValidator<TestApplication>>(),
Mock.Of<ILogger<ApplicationManager<TestApplication>>>(),
new ApplicationErrorDescriber());
}
private class ErrorsComparer : IEqualityComparer<IEnumerable<IdentityServiceError>>
{
public static ErrorsComparer Instance = new ErrorsComparer();
public bool Equals(
IEnumerable<IdentityServiceError> left,
IEnumerable<IdentityServiceError> right)
{
var leftOrdered = left.OrderBy(o => o.Code).ThenBy(o => o.Description).ToArray();
var rightOrdered = right.OrderBy(o => o.Code).ThenBy(o => o.Description).ToArray();
return leftOrdered.Length == rightOrdered.Length &&
leftOrdered.Select((s, i) => s.Code.Equals(rightOrdered[i].Code) &&
s.Description.Equals(rightOrdered[i].Description)).All(a => a);
}
public int GetHashCode(IEnumerable<IdentityServiceError> obj)
{
return 1;
}
}
public class TestApplication
{
public string Id { get; set; }
public string Name { get; set; }
public string ClientId { get; set; }
public List<string> RedirectUris { get; set; }
public List<string> LogoutUris { get; set; }
public List<string> Scopes { get; set; }
}
}
}

View File

@ -28,10 +28,12 @@ namespace Microsoft.AspNetCore.Identity.Service
.ReturnsAsync(exists ? new IdentityServiceApplication() : null);
var manager = new ApplicationManager<IdentityServiceApplication>(
Options.Create(new ApplicationOptions()),
store.Object,
Mock.Of<IPasswordHasher<IdentityServiceApplication>>(),
Array.Empty<IApplicationValidator<IdentityServiceApplication>>(),
Mock.Of<ILogger<ApplicationManager<IdentityServiceApplication>>>());
Mock.Of<ILogger<ApplicationManager<IdentityServiceApplication>>>(),
new ApplicationErrorDescriber());
var clientValidator = new ClientApplicationValidator<IdentityServiceApplication>(
Options.Create(options),
@ -59,10 +61,12 @@ namespace Microsoft.AspNetCore.Identity.Service
.ReturnsAsync(false);
var manager = new ApplicationManager<IdentityServiceApplication>(
Options.Create(new ApplicationOptions()),
store.Object,
Mock.Of<IPasswordHasher<IdentityServiceApplication>>(),
Array.Empty<IApplicationValidator<IdentityServiceApplication>>(),
Mock.Of<ILogger<ApplicationManager<IdentityServiceApplication>>>());
Mock.Of<ILogger<ApplicationManager<IdentityServiceApplication>>>(),
new ApplicationErrorDescriber());
var clientValidator = new ClientApplicationValidator<IdentityServiceApplication>(
Options.Create(options),