Added validation, storage tests
This commit is contained in:
parent
48f9d47e90
commit
904ff0e060
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ namespace Microsoft.AspNetCore.Identity.Service
|
|||
IdentityServiceRedirectUri<TApplicationKey>>
|
||||
where TApplicationKey : IEquatable<TApplicationKey>
|
||||
where TUserKey : IEquatable<TUserKey>
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>>();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in New Issue