diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs
index 4cef05d4..3e4eeb3a 100644
--- a/samples/Mvc.Server/Startup.cs
+++ b/samples/Mvc.Server/Startup.cs
@@ -186,15 +186,16 @@ namespace Mvc.Server
if (await manager.FindByClientIdAsync("mvc", cancellationToken) == null)
{
- var application = new OpenIddictApplication
+ var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "mvc",
+ ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
DisplayName = "MVC client application",
- LogoutRedirectUri = "http://localhost:53507/",
- RedirectUri = "http://localhost:53507/signin-oidc"
+ PostLogoutRedirectUris = { new Uri("http://localhost:53507/") },
+ RedirectUris = { new Uri("http://localhost:53507/signin-oidc") }
};
- await manager.CreateAsync(application, "901564A5-E7FE-42CB-B10D-61EF6A8F3654", cancellationToken);
+ await manager.CreateAsync(descriptor, cancellationToken);
}
// To test this sample with Postman, use the following settings:
@@ -208,14 +209,14 @@ namespace Mvc.Server
// * Request access token locally: yes
if (await manager.FindByClientIdAsync("postman", cancellationToken) == null)
{
- var application = new OpenIddictApplication
+ var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "postman",
DisplayName = "Postman",
- RedirectUri = "https://www.getpostman.com/oauth2/callback"
+ RedirectUris = { new Uri("https://www.getpostman.com/oauth2/callback") }
};
- await manager.CreateAsync(application, cancellationToken);
+ await manager.CreateAsync(descriptor, cancellationToken);
}
}
}
diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs
index 5738113a..11cbcdae 100644
--- a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs
+++ b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs
@@ -1,4 +1,7 @@
-namespace OpenIddict.Core
+using System;
+using System.Collections.Generic;
+
+namespace OpenIddict.Core
{
///
/// Represents an OpenIddict application descriptor.
@@ -9,37 +12,37 @@
/// Gets or sets the client identifier
/// associated with the application.
///
- public virtual string ClientId { get; set; }
+ public string ClientId { get; set; }
///
/// Gets or sets the client secret associated with the application.
/// Note: depending on the application manager used when creating it,
/// this property may be hashed or encrypted for security reasons.
///
- public virtual string ClientSecret { get; set; }
+ public string ClientSecret { get; set; }
///
/// Gets or sets the display name
/// associated with the application.
///
- public virtual string DisplayName { get; set; }
+ public string DisplayName { get; set; }
///
- /// Gets or sets the logout callback URL
+ /// Gets the logout callback URLs
/// associated with the application.
///
- public virtual string LogoutRedirectUri { get; set; }
+ public ISet PostLogoutRedirectUris { get; } = new HashSet();
///
- /// Gets or sets the callback URL
+ /// Gets the callback URLs
/// associated with the application.
///
- public virtual string RedirectUri { get; set; }
+ public ISet RedirectUris { get; } = new HashSet();
///
/// Gets or sets the application type
/// associated with the application.
///
- public virtual string Type { get; set; }
+ public string Type { get; set; }
}
}
diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
index 927661c8..04b129ec 100644
--- a/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
+++ b/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace OpenIddict.Core
{
@@ -13,9 +14,15 @@ namespace OpenIddict.Core
public string ApplicationId { get; set; }
///
- /// Gets or sets the scopes associated with the authorization.
+ /// Gets the scopes associated with the authorization.
///
- public IEnumerable Scopes { get; set; }
+ public ISet Scopes { get; } =
+ new HashSet(StringComparer.Ordinal);
+
+ ///
+ /// Gets or sets the status associated with the authorization.
+ ///
+ public string Status { get; set; }
///
/// Gets or sets the subject associated with the authorization.
diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs
index 724935f5..258713a9 100644
--- a/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs
+++ b/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs
@@ -37,6 +37,11 @@ namespace OpenIddict.Core
///
public string Hash { get; set; }
+ ///
+ /// Gets or sets the status associated with the token.
+ ///
+ public string Status { get; set; }
+
///
/// Gets or sets the subject associated with the token.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
index f9f91591..5d6beafa 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
@@ -206,9 +206,14 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
///
- public virtual Task FindByLogoutRedirectUriAsync(string address, CancellationToken cancellationToken)
+ public virtual Task FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
- return Store.FindByLogoutRedirectUriAsync(address, cancellationToken);
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("The address cannot be null or empty.", nameof(address));
+ }
+
+ return Store.FindByPostLogoutRedirectUriAsync(address, cancellationToken);
}
///
@@ -220,8 +225,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
///
- public virtual Task FindByRedirectUriAsync(string address, CancellationToken cancellationToken)
+ public virtual Task FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("The address cannot be null or empty.", nameof(address));
+ }
+
return Store.FindByRedirectUriAsync(address, cancellationToken);
}
@@ -245,6 +255,25 @@ namespace OpenIddict.Core
return Store.GetAsync(query, cancellationToken);
}
+ ///
+ /// Retrieves the client identifier associated with an application.
+ ///
+ /// The application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the client identifier associated with the application.
+ ///
+ public virtual Task GetClientIdAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+
+ return Store.GetClientIdAsync(application, cancellationToken);
+ }
+
///
/// Retrieves the client type associated with an application.
///
@@ -332,25 +361,6 @@ namespace OpenIddict.Core
return Store.GetTokensAsync(application, cancellationToken);
}
- ///
- /// Determines whether the specified application has a redirect_uri.
- ///
- /// The application.
- /// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation,
- /// whose result returns a boolean indicating whether a redirect_uri is registered.
- ///
- public virtual async Task HasRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken)
- {
- if (application == null)
- {
- throw new ArgumentNullException(nameof(application));
- }
-
- return !string.IsNullOrEmpty(await Store.GetRedirectUriAsync(application, cancellationToken));
- }
-
///
/// Determines whether an application is a confidential client.
///
@@ -502,7 +512,8 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
///
- public virtual async Task ValidateClientSecretAsync([NotNull] TApplication application, string secret, CancellationToken cancellationToken)
+ public virtual async Task ValidateClientSecretAsync(
+ [NotNull] TApplication application, string secret, CancellationToken cancellationToken)
{
if (application == null)
{
@@ -528,7 +539,7 @@ namespace OpenIddict.Core
if (!await ValidateClientSecretAsync(secret, value, cancellationToken))
{
Logger.LogWarning("Client authentication failed for {Client}.",
- await GetDisplayNameAsync(application, cancellationToken));
+ await GetClientIdAsync(application, cancellationToken));
return false;
}
@@ -545,16 +556,25 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns a boolean indicating whether the post_logout_redirect_uri was valid.
///
- public virtual async Task ValidateLogoutRedirectUriAsync(string address, CancellationToken cancellationToken)
+ public virtual async Task ValidatePostLogoutRedirectUriAsync(
+ [NotNull] string address, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("The address cannot be null or empty.", nameof(address));
+ }
+
// Warning: SQL engines like Microsoft SQL Server are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is used, string.Equals(Ordinal) is manually called here.
- foreach (var application in await Store.FindByLogoutRedirectUriAsync(address, cancellationToken))
+ foreach (var application in await Store.FindByPostLogoutRedirectUriAsync(address, cancellationToken))
{
- // Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison".
- if (string.Equals(address, await Store.GetLogoutRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
+ foreach (var uri in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
{
- return true;
+ // Note: the post_logout_redirect_uri must be compared using case-sensitive "Simple String Comparison".
+ if (string.Equals(uri, address, StringComparison.Ordinal))
+ {
+ return true;
+ }
}
}
@@ -565,31 +585,40 @@ namespace OpenIddict.Core
}
///
- /// Validates the redirect_uri associated with an application.
+ /// Validates the redirect_uri to ensure it's associated with an application.
///
/// The application.
- /// The address that should be compared to the redirect_uri stored in the database.
+ /// The address that should be compared to one of the redirect_uri stored in the database.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid.
///
- public virtual async Task ValidateRedirectUriAsync([NotNull] TApplication application, string address, CancellationToken cancellationToken)
+ public virtual async Task ValidateRedirectUriAsync(
+ [NotNull] TApplication application, [NotNull] string address, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
- // Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
- // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
- if (string.Equals(address, await Store.GetRedirectUriAsync(application, cancellationToken), StringComparison.Ordinal))
+ if (string.IsNullOrEmpty(address))
{
- return true;
+ throw new ArgumentException("The address cannot be null or empty.", nameof(address));
+ }
+
+ foreach (var uri in await Store.GetRedirectUrisAsync(application, cancellationToken))
+ {
+ // Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
+ // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
+ if (string.Equals(uri, address, StringComparison.Ordinal))
+ {
+ return true;
+ }
}
Logger.LogWarning("Client validation failed because '{RedirectUri}' was not a valid redirect_uri " +
- "for '{Client}'.", address, await GetDisplayNameAsync(application, cancellationToken));
+ "for {Client}.", address, await GetClientIdAsync(application, cancellationToken));
return false;
}
@@ -609,11 +638,43 @@ namespace OpenIddict.Core
ClientId = await Store.GetClientIdAsync(application, cancellationToken),
ClientSecret = await Store.GetClientSecretAsync(application, cancellationToken),
DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken),
- LogoutRedirectUri = await Store.GetLogoutRedirectUriAsync(application, cancellationToken),
- RedirectUri = await Store.GetRedirectUriAsync(application, cancellationToken),
Type = await Store.GetClientTypeAsync(application, cancellationToken)
};
+ foreach (var address in await Store.GetPostLogoutRedirectUrisAsync(application, cancellationToken))
+ {
+ // Ensure the address is not null or empty.
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("Callback URLs cannot be null or empty.");
+ }
+
+ // Ensure the address is a valid absolute URL.
+ if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
+ {
+ throw new ArgumentException("Callback URLs must be valid absolute URLs.");
+ }
+
+ descriptor.PostLogoutRedirectUris.Add(uri);
+ }
+
+ foreach (var address in await Store.GetRedirectUrisAsync(application, cancellationToken))
+ {
+ // Ensure the address is not null or empty.
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("Callback URLs cannot be null or empty.");
+ }
+
+ // Ensure the address is a valid absolute URL.
+ if (!Uri.TryCreate(address, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
+ {
+ throw new ArgumentException("Callback URLs must be valid absolute URLs.");
+ }
+
+ descriptor.RedirectUris.Add(uri);
+ }
+
await ValidateAsync(descriptor, cancellationToken);
}
@@ -665,36 +726,26 @@ namespace OpenIddict.Core
throw new ArgumentException("A client secret cannot be associated with a public application.", nameof(descriptor));
}
- // When a redirect_uri is specified, ensure it is valid and spec-compliant.
+ // When callback URLs are specified, ensure they are valid and spec-compliant.
// See https://tools.ietf.org/html/rfc6749#section-3.1 for more information.
- if (!string.IsNullOrEmpty(descriptor.RedirectUri))
+ foreach (var uri in descriptor.PostLogoutRedirectUris.Concat(descriptor.RedirectUris))
{
- // Ensure the redirect_uri is a valid and absolute URL.
- if (!Uri.TryCreate(descriptor.RedirectUri, UriKind.Absolute, out Uri uri))
- {
- throw new ArgumentException("The redirect_uri must be an absolute URL.");
- }
-
- // Ensure the redirect_uri doesn't contain a fragment.
- if (!string.IsNullOrEmpty(uri.Fragment))
+ // Ensure the address is not null.
+ if (uri == null)
{
- throw new ArgumentException("The redirect_uri cannot contain a fragment.");
+ throw new ArgumentException("Callback URLs cannot be null.");
}
- }
- // When a post_logout_redirect_uri is specified, ensure it is valid.
- if (!string.IsNullOrEmpty(descriptor.LogoutRedirectUri))
- {
- // Ensure the post_logout_redirect_uri is a valid and absolute URL.
- if (!Uri.TryCreate(descriptor.LogoutRedirectUri, UriKind.Absolute, out Uri uri))
+ // Ensure the address is a valid and absolute URL.
+ if (!uri.IsAbsoluteUri || !uri.IsWellFormedOriginalString())
{
- throw new ArgumentException("The post_logout_redirect_uri must be an absolute URL.");
+ throw new ArgumentException("Callback URLs must be valid absolute URLs.");
}
- // Ensure the post_logout_redirect_uri doesn't contain a fragment.
+ // Ensure the address doesn't contain a fragment.
if (!string.IsNullOrEmpty(uri.Fragment))
{
- throw new ArgumentException("The post_logout_redirect_uri cannot contain a fragment.");
+ throw new ArgumentException("Callback URLs cannot contain a fragment.");
}
}
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index 88738b2f..e4c9f1fd 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -221,6 +221,7 @@ namespace OpenIddict.Core
var descriptor = new OpenIddictAuthorizationDescriptor
{
+ Status = await Store.GetStatusAsync(authorization, cancellationToken),
Subject = await Store.GetSubjectAsync(authorization, cancellationToken)
};
@@ -242,11 +243,30 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(descriptor));
}
+ if (string.IsNullOrEmpty(descriptor.Status))
+ {
+ throw new ArgumentException("The status cannot be null or empty.");
+ }
+
if (string.IsNullOrEmpty(descriptor.Subject))
{
throw new ArgumentException("The subject cannot be null or empty.");
}
+ // Ensure that the scopes are not null or empty and do not contain spaces.
+ foreach (var scope in descriptor.Scopes)
+ {
+ if (string.IsNullOrEmpty(scope))
+ {
+ throw new ArgumentException("Scopes cannot be null or empty.", nameof(descriptor));
+ }
+
+ if (scope.Contains(OpenIddictConstants.Separators.Space))
+ {
+ throw new ArgumentException("Scopes cannot contain spaces.", nameof(descriptor));
+ }
+ }
+
return Task.FromResult(0);
}
}
diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
index 9774e570..cff827f5 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
@@ -65,14 +65,15 @@ namespace OpenIddict.Core
///
/// A that can be used to monitor the asynchronous operation, whose result returns the token.
///
- public virtual Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
+ public virtual async Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
- return Store.CreateAsync(descriptor, cancellationToken);
+ await ValidateAsync(descriptor, cancellationToken);
+ return await Store.CreateAsync(descriptor, cancellationToken);
}
///
@@ -505,22 +506,53 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(token));
}
- var type = await Store.GetTokenTypeAsync(token, cancellationToken);
- if (string.IsNullOrEmpty(type))
+ var descriptor = new OpenIddictTokenDescriptor
{
- throw new ArgumentException("The token type cannot be null or empty.", nameof(token));
+ Status = await Store.GetStatusAsync(token, cancellationToken),
+ Subject = await Store.GetSubjectAsync(token, cancellationToken),
+ Type = await Store.GetTokenTypeAsync(token, cancellationToken)
+ };
+
+ await ValidateAsync(descriptor, cancellationToken);
+ }
+
+ ///
+ /// Validates the token descriptor to ensure it's in a consistent state.
+ ///
+ /// The token descriptor.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ protected virtual Task ValidateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ if (string.IsNullOrEmpty(descriptor.Type))
+ {
+ throw new ArgumentException("The token type cannot be null or empty.", nameof(descriptor));
}
- if (!string.Equals(type, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(type, OpenIdConnectConstants.TokenTypeHints.RefreshToken, StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(descriptor.Type, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(descriptor.Type, OpenIdConnectConstants.TokenTypeHints.RefreshToken, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("The specified token type is not supported by the default token manager.");
}
- if (string.IsNullOrEmpty(await Store.GetSubjectAsync(token, cancellationToken)))
+ if (string.IsNullOrEmpty(descriptor.Status))
+ {
+ throw new ArgumentException("The status cannot be null or empty.");
+ }
+
+ if (string.IsNullOrEmpty(descriptor.Subject))
{
throw new ArgumentException("The subject cannot be null or empty.");
}
+
+ return Task.FromResult(0);
}
}
}
\ No newline at end of file
diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs
index 0b3a02e0..4ba722e7 100644
--- a/src/OpenIddict.Core/OpenIddictConstants.cs
+++ b/src/OpenIddict.Core/OpenIddictConstants.cs
@@ -37,6 +37,11 @@ namespace OpenIddict.Core
public const string TokenId = ".token_id";
}
+ public static class Separators
+ {
+ public const string Space = " ";
+ }
+
public static class Scopes
{
public const string Roles = "roles";
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
index 08965a07..76de9dbe 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
@@ -58,7 +58,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
///
- Task FindByIdAsync(string identifier, CancellationToken cancellationToken);
+ Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken);
///
/// Retrieves an application using its client identifier.
@@ -69,7 +69,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
///
- Task FindByClientIdAsync(string identifier, CancellationToken cancellationToken);
+ Task FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken);
///
/// Retrieves all the applications associated with the specified post_logout_redirect_uri.
@@ -80,7 +80,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
///
- Task FindByLogoutRedirectUriAsync(string address, CancellationToken cancellationToken);
+ Task FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken);
///
/// Retrieves all the applications associated with the specified redirect_uri.
@@ -91,7 +91,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
///
- Task FindByRedirectUriAsync(string address, CancellationToken cancellationToken);
+ Task FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken);
///
/// Executes the specified query.
@@ -163,26 +163,26 @@ namespace OpenIddict.Core
Task GetIdAsync([NotNull] TApplication application, CancellationToken cancellationToken);
///
- /// Retrieves the logout callback address associated with an application.
+ /// Retrieves the logout callback addresses associated with an application.
///
/// The application.
/// The that can be used to abort the operation.
///
- /// A that can be used to monitor the asynchronous operation,
- /// whose result returns the post_logout_redirect_uri associated with the application.
+ /// A that can be used to monitor the asynchronous operation, whose
+ /// result returns all the post_logout_redirect_uri associated with the application.
///
- Task GetLogoutRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken);
+ Task GetPostLogoutRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken);
///
- /// Retrieves the callback address associated with an application.
+ /// Retrieves the callback addresses associated with an application.
///
/// The application.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation,
- /// whose result returns the redirect_uri associated with the application.
+ /// whose result returns all the redirect_uri associated with the application.
///
- Task GetRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken);
+ Task GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken);
///
/// Retrieves the token identifiers associated with an application.
@@ -231,6 +231,30 @@ namespace OpenIddict.Core
///
Task SetClientTypeAsync([NotNull] TApplication application, [NotNull] string type, CancellationToken cancellationToken);
+ ///
+ /// Sets the logout callback addresses associated with an application.
+ ///
+ /// The application.
+ /// The logout callback addresses associated with the application
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ Task SetPostLogoutRedirectUrisAsync([NotNull] TApplication application,
+ [NotNull] string[] addresses, CancellationToken cancellationToken);
+
+ ///
+ /// Sets the callback addresses associated with an application.
+ ///
+ /// The application.
+ /// The callback addresses associated with the application
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ Task SetRedirectUrisAsync([NotNull] TApplication application,
+ [NotNull] string[] addresses, CancellationToken cancellationToken);
+
///
/// Updates an existing application.
///
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
index aaf3c41f..44b6b506 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
@@ -39,27 +39,27 @@ namespace OpenIddict.Core
Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken);
///
- /// Retrieves an authorization using its unique identifier.
+ /// Retrieves an authorization using its associated subject/client.
///
- /// The unique identifier associated with the authorization.
+ /// The subject associated with the authorization.
+ /// The client associated with the authorization.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation,
- /// whose result returns the authorization corresponding to the identifier.
+ /// whose result returns the authorization corresponding to the subject/client.
///
- Task FindByIdAsync(string identifier, CancellationToken cancellationToken);
+ Task FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken);
///
- /// Retrieves an authorization using its associated subject/client.
+ /// Retrieves an authorization using its unique identifier.
///
- /// The subject associated with the authorization.
- /// The client associated with the authorization.
+ /// The unique identifier associated with the authorization.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation,
- /// whose result returns the authorization corresponding to the subject/client.
+ /// whose result returns the authorization corresponding to the identifier.
///
- Task FindAsync(string subject, string client, CancellationToken cancellationToken);
+ Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken);
///
/// Executes the specified query.
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
index 97512467..db25f7e5 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
@@ -55,7 +55,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified authorization.
///
- Task FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken);
+ Task FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken);
///
/// Retrieves the list of tokens corresponding to the specified hash.
@@ -66,7 +66,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified hash.
///
- Task FindByHashAsync(string hash, CancellationToken cancellationToken);
+ Task FindByHashAsync([NotNull] string hash, CancellationToken cancellationToken);
///
/// Retrieves an token using its unique identifier.
@@ -77,7 +77,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
///
- Task FindByIdAsync(string identifier, CancellationToken cancellationToken);
+ Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken);
///
/// Retrieves the list of tokens corresponding to the specified subject.
@@ -88,7 +88,7 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified subject.
///
- Task FindBySubjectAsync(string subject, CancellationToken cancellationToken);
+ Task FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken);
///
/// Executes the specified query.
diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs
index 80dcaf0f..6e9d5370 100644
--- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs
+++ b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs
@@ -10,6 +10,7 @@ using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using AspNet.Security.OpenIdConnect.Primitives;
using JetBrains.Annotations;
using OpenIddict.Models;
@@ -68,8 +69,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
///
- public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken)
+ public virtual Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
var key = ConvertIdentifierFromString(identifier);
return GetAsync(applications => applications.Where(application => application.Id.Equals(key)), cancellationToken);
@@ -84,8 +90,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
///
- public virtual Task FindByClientIdAsync(string identifier, CancellationToken cancellationToken)
+ public virtual Task FindByClientIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
return GetAsync(applications => applications.Where(application => application.ClientId.Equals(identifier)), cancellationToken);
}
@@ -98,9 +109,60 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified post_logout_redirect_uri.
///
- public virtual Task FindByLogoutRedirectUriAsync(string address, CancellationToken cancellationToken)
+ public virtual async Task FindByPostLogoutRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
- return ListAsync(applications => applications.Where(application => application.LogoutRedirectUri == address), cancellationToken);
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("The address cannot be null or empty.", nameof(address));
+ }
+
+ // To optimize the efficiency of the query, only the applications whose stringified
+ // LogoutRedirectUris property contains the specified address are returned. Once the
+ // applications are retrieved, the LogoutRedirectUri property is manually split.
+ var candidates = await ListAsync(applications => applications.Where(application =>
+ application.PostLogoutRedirectUris.Contains(address)), cancellationToken);
+
+ if (candidates.Length == 0)
+ {
+ return new TApplication[0];
+ }
+
+ // Optimization: to save an allocation when no application matches
+ // the specified address, the results list is lazily initialized
+ // when at least one matching application was found in the database.
+ List results = null;
+
+ foreach (var candidate in candidates)
+ {
+ var uris = candidate.PostLogoutRedirectUris?.Split(
+ OpenIdConnectConstants.Separators.Space,
+ StringSplitOptions.RemoveEmptyEntries);
+
+ if (uris == null)
+ {
+ continue;
+ }
+
+ foreach (var uri in uris)
+ {
+ // Note: the post_logout_redirect_uri must be compared
+ // using case-sensitive "Simple String Comparison".
+ if (!string.Equals(uri, address, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ // Ensure the results list was initialized before using it.
+ if (results == null)
+ {
+ results = new List(capacity: 1);
+ }
+
+ results.Add(candidate);
+ }
+ }
+
+ return results?.ToArray() ?? new TApplication[0];
}
///
@@ -112,9 +174,60 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation, whose result
/// returns the client applications corresponding to the specified redirect_uri.
///
- public virtual Task FindByRedirectUriAsync(string address, CancellationToken cancellationToken)
+ public virtual async Task FindByRedirectUriAsync([NotNull] string address, CancellationToken cancellationToken)
{
- return ListAsync(applications => applications.Where(application => application.RedirectUri == address), cancellationToken);
+ if (string.IsNullOrEmpty(address))
+ {
+ throw new ArgumentException("The address cannot be null or empty.", nameof(address));
+ }
+
+ // To optimize the efficiency of the query, only the applications whose stringified
+ // RedirectUris property contains the specified address are returned. Once the
+ // applications are retrieved, the RedirectUri property is manually split.
+ var candidates = await ListAsync(applications => applications.Where(application =>
+ application.RedirectUris.Contains(address)), cancellationToken);
+
+ if (candidates.Length == 0)
+ {
+ return new TApplication[0];
+ }
+
+ // Optimization: to save an allocation when no application matches
+ // the specified address, the results list is lazily initialized
+ // when at least one matching application was found in the database.
+ List results = null;
+
+ foreach (var candidate in candidates)
+ {
+ var uris = candidate.RedirectUris?.Split(
+ OpenIdConnectConstants.Separators.Space,
+ StringSplitOptions.RemoveEmptyEntries);
+
+ if (uris == null)
+ {
+ continue;
+ }
+
+ foreach (var uri in uris)
+ {
+ // Note: the redirect_uri must be compared using case-sensitive "Simple String Comparison".
+ // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest for more information.
+ if (!string.Equals(uri, address, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ // Ensure the results list was initialized before using it.
+ if (results == null)
+ {
+ results = new List(capacity: 1);
+ }
+
+ results.Add(candidate);
+ }
+ }
+
+ return results?.ToArray() ?? new TApplication[0];
}
///
@@ -227,41 +340,55 @@ namespace OpenIddict.Core
}
///
- /// Retrieves the logout callback address associated with an application.
+ /// Retrieves the logout callback addresses associated with an application.
///
/// The application.
/// The that can be used to abort the operation.
///
- /// A that can be used to monitor the asynchronous operation,
- /// whose result returns the post_logout_redirect_uri associated with the application.
+ /// A that can be used to monitor the asynchronous operation, whose
+ /// result returns all the post_logout_redirect_uri associated with the application.
///
- public virtual Task GetLogoutRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ public virtual Task GetPostLogoutRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
- return Task.FromResult(application.LogoutRedirectUri);
+ if (string.IsNullOrEmpty(application.PostLogoutRedirectUris))
+ {
+ return Task.FromResult(new string[0]);
+ }
+
+ return Task.FromResult(application.PostLogoutRedirectUris.Split(
+ OpenIdConnectConstants.Separators.Space,
+ StringSplitOptions.RemoveEmptyEntries));
}
///
- /// Retrieves the callback address associated with an application.
+ /// Retrieves the callback addresses associated with an application.
///
/// The application.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation,
- /// whose result returns the redirect_uri associated with the application.
+ /// whose result returns all the redirect_uri associated with the application.
///
- public virtual Task GetRedirectUriAsync([NotNull] TApplication application, CancellationToken cancellationToken)
+ public virtual Task GetRedirectUrisAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
- return Task.FromResult(application.RedirectUri);
+ if (string.IsNullOrEmpty(application.RedirectUris))
+ {
+ return Task.FromResult(new string[0]);
+ }
+
+ return Task.FromResult(application.RedirectUris.Split(
+ OpenIdConnectConstants.Separators.Space,
+ StringSplitOptions.RemoveEmptyEntries));
}
///
@@ -356,6 +483,80 @@ namespace OpenIddict.Core
return Task.FromResult(0);
}
+ ///
+ /// Sets the logout callback addresses associated with an application.
+ ///
+ /// The application.
+ /// The logout callback addresses associated with the application
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual Task SetPostLogoutRedirectUrisAsync([NotNull] TApplication application,
+ [NotNull] string[] addresses, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentException(nameof(application));
+ }
+
+ if (addresses == null)
+ {
+ throw new ArgumentException(nameof(addresses));
+ }
+
+ if (addresses.Any(address => string.IsNullOrEmpty(address)))
+ {
+ throw new ArgumentException("Callback addresses cannot be null or empty.", nameof(addresses));
+ }
+
+ if (addresses.Any(address => address.Contains(OpenIddictConstants.Separators.Space)))
+ {
+ throw new ArgumentException("Callback addresses cannot contain spaces.", nameof(addresses));
+ }
+
+ application.PostLogoutRedirectUris = string.Join(OpenIddictConstants.Separators.Space, addresses);
+
+ return Task.FromResult(0);
+ }
+
+ ///
+ /// Sets the callback addresses associated with an application.
+ ///
+ /// The application.
+ /// The callback addresses associated with the application
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual Task SetRedirectUrisAsync([NotNull] TApplication application,
+ [NotNull] string[] addresses, CancellationToken cancellationToken)
+ {
+ if (application == null)
+ {
+ throw new ArgumentException(nameof(application));
+ }
+
+ if (addresses == null)
+ {
+ throw new ArgumentException(nameof(addresses));
+ }
+
+ if (addresses.Any(address => string.IsNullOrEmpty(address)))
+ {
+ throw new ArgumentException("Callback addresses cannot be null or empty.", nameof(addresses));
+ }
+
+ if (addresses.Any(address => address.Contains(OpenIddictConstants.Separators.Space)))
+ {
+ throw new ArgumentException("Callback addresses cannot contain spaces.", nameof(addresses));
+ }
+
+ application.RedirectUris = string.Join(OpenIddictConstants.Separators.Space, addresses);
+
+ return Task.FromResult(0);
+ }
+
///
/// Updates an existing application.
///
diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs
index d6e68bd2..7af0b129 100644
--- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs
@@ -58,8 +58,18 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the subject/client.
///
- public virtual Task FindAsync(string subject, string client, CancellationToken cancellationToken)
+ public virtual Task FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
+ }
+
+ if (string.IsNullOrEmpty(client))
+ {
+ throw new ArgumentException("The client cannot be null or empty.", nameof(client));
+ }
+
var key = ConvertIdentifierFromString(client);
return GetAsync(authorizations =>
@@ -78,8 +88,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the authorization corresponding to the identifier.
///
- public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken)
+ public virtual Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
var key = ConvertIdentifierFromString(identifier);
return GetAsync(authorizations => authorizations.Where(authorization => authorization.Id.Equals(key)), cancellationToken);
diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs
index f10d782d..de3dec28 100644
--- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs
@@ -65,8 +65,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified authorization.
///
- public virtual Task FindByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
+ public virtual Task FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
var key = ConvertIdentifierFromString(identifier);
return ListAsync(tokens => tokens.Where(token => token.Authorization.Id.Equals(key)), cancellationToken);
@@ -81,8 +86,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified hash.
///
- public virtual Task FindByHashAsync(string hash, CancellationToken cancellationToken)
+ public virtual Task FindByHashAsync([NotNull] string hash, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(hash))
+ {
+ throw new ArgumentException("The hash cannot be null or empty.", nameof(hash));
+ }
+
return GetAsync(tokens => tokens.Where(token => token.Hash == hash), cancellationToken);
}
@@ -95,8 +105,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
///
- public virtual Task FindByIdAsync(string identifier, CancellationToken cancellationToken)
+ public virtual Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
var key = ConvertIdentifierFromString(identifier);
return GetAsync(tokens => tokens.Where(token => token.Id.Equals(key)), cancellationToken);
@@ -111,8 +126,13 @@ namespace OpenIddict.Core
/// A that can be used to monitor the asynchronous operation,
/// whose result returns the tokens corresponding to the specified subject.
///
- public virtual Task FindBySubjectAsync(string subject, CancellationToken cancellationToken)
+ public virtual Task FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken)
{
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
+ }
+
return ListAsync(tokens => tokens.Where(token => token.Subject == subject), cancellationToken);
}
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
index 89629b5c..52cc69b4 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
@@ -123,11 +123,23 @@ namespace OpenIddict.EntityFramework
ClientId = descriptor.ClientId,
ClientSecret = descriptor.ClientSecret,
DisplayName = descriptor.DisplayName,
- LogoutRedirectUri = descriptor.LogoutRedirectUri,
- RedirectUri = descriptor.RedirectUri,
Type = descriptor.Type
};
+ if (descriptor.PostLogoutRedirectUris.Count != 0)
+ {
+ application.PostLogoutRedirectUris = string.Join(
+ OpenIddictConstants.Separators.Space,
+ descriptor.PostLogoutRedirectUris.Select(uri => uri.OriginalString));
+ }
+
+ if (descriptor.RedirectUris.Count != 0)
+ {
+ application.RedirectUris = string.Join(
+ OpenIddictConstants.Separators.Space,
+ descriptor.RedirectUris.Select(uri => uri.OriginalString));
+ }
+
return CreateAsync(application, cancellationToken);
}
@@ -156,6 +168,25 @@ namespace OpenIddict.EntityFramework
catch (DbUpdateConcurrencyException) { }
}
+ ///
+ /// Retrieves an application using its unique identifier.
+ ///
+ /// The unique identifier associated with the application.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the client application corresponding to the identifier.
+ ///
+ public override Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
+ return Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier));
+ }
+
///
/// Executes the specified query.
///
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
index af65eca1..217bfeed 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
@@ -125,16 +125,19 @@ namespace OpenIddict.EntityFramework
var authorization = new TAuthorization
{
- Scope = string.Join(" ", descriptor.Scopes),
+ Status = descriptor.Status,
Subject = descriptor.Subject
};
+ if (descriptor.Scopes.Count != 0)
+ {
+ authorization.Scopes = string.Join(OpenIddictConstants.Separators.Space, descriptor.Scopes);
+ }
+
// Bind the authorization to the specified application, if applicable.
if (!string.IsNullOrEmpty(descriptor.ApplicationId))
{
- var key = ConvertIdentifierFromString(descriptor.ApplicationId);
-
- var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.ApplicationId));
if (application == null)
{
throw new InvalidOperationException("The application associated with the authorization cannot be found.");
@@ -146,6 +149,25 @@ namespace OpenIddict.EntityFramework
return await CreateAsync(authorization, cancellationToken);
}
+ ///
+ /// Retrieves an authorization using its unique identifier.
+ ///
+ /// The unique identifier associated with the authorization.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the authorization corresponding to the identifier.
+ ///
+ public override Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
+ return Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier));
+ }
+
///
/// Executes the specified query.
///
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
index 886200b7..1987e46e 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
@@ -134,6 +134,7 @@ namespace OpenIddict.EntityFramework
CreationDate = descriptor.CreationDate,
ExpirationDate = descriptor.ExpirationDate,
Hash = descriptor.Hash,
+ Status = descriptor.Status,
Subject = descriptor.Subject,
Type = descriptor.Type
};
@@ -141,9 +142,7 @@ namespace OpenIddict.EntityFramework
// Bind the token to the specified client application, if applicable.
if (!string.IsNullOrEmpty(descriptor.ApplicationId))
{
- var key = ConvertIdentifierFromString(descriptor.ApplicationId);
-
- var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.ApplicationId));
if (application == null)
{
throw new InvalidOperationException("The application associated with the token cannot be found.");
@@ -155,9 +154,7 @@ namespace OpenIddict.EntityFramework
// Bind the token to the specified authorization, if applicable.
if (!string.IsNullOrEmpty(descriptor.AuthorizationId))
{
- var key = ConvertIdentifierFromString(descriptor.AuthorizationId);
-
- var authorization = await Authorizations.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.AuthorizationId));
if (authorization == null)
{
throw new InvalidOperationException("The authorization associated with the token cannot be found.");
@@ -192,6 +189,25 @@ namespace OpenIddict.EntityFramework
catch (DbUpdateConcurrencyException) { }
}
+ ///
+ /// Retrieves an token using its unique identifier.
+ ///
+ /// The unique identifier associated with the token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the token corresponding to the unique identifier.
+ ///
+ public override Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
+ }
+
+ return Tokens.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier));
+ }
+
///
/// Executes the specified query.
///
@@ -250,15 +266,13 @@ namespace OpenIddict.EntityFramework
if (!string.IsNullOrEmpty(identifier))
{
- var key = ConvertIdentifierFromString(identifier);
-
- var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Id.Equals(key));
+ var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier));
if (authorization == null)
{
throw new InvalidOperationException("The authorization associated with the token cannot be found.");
}
- authorization.Tokens.Add(token);
+ token.Authorization = authorization;
}
else
@@ -267,7 +281,7 @@ namespace OpenIddict.EntityFramework
// Try to retrieve the authorization associated with the token.
// If none can be found, assume that no authorization is attached.
- var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
+ var authorization = await Authorizations.FirstOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
if (authorization != null)
{
authorization.Tokens.Remove(token);
@@ -293,15 +307,13 @@ namespace OpenIddict.EntityFramework
if (!string.IsNullOrEmpty(identifier))
{
- var key = ConvertIdentifierFromString(identifier);
-
- var application = await Applications.SingleOrDefaultAsync(element => element.Id.Equals(key));
+ var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(identifier));
if (application == null)
{
throw new InvalidOperationException("The application associated with the token cannot be found.");
}
- application.Tokens.Add(token);
+ token.Application = application;
}
else
@@ -310,7 +322,7 @@ namespace OpenIddict.EntityFramework
// Try to retrieve the application associated with the token.
// If none can be found, assume that no application is attached.
- var application = await Applications.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
+ var application = await Applications.FirstOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
if (application != null)
{
application.Tokens.Remove(token);
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
index 0ba5a373..5108f6fe 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
@@ -122,11 +122,23 @@ namespace OpenIddict.EntityFrameworkCore
ClientId = descriptor.ClientId,
ClientSecret = descriptor.ClientSecret,
DisplayName = descriptor.DisplayName,
- LogoutRedirectUri = descriptor.LogoutRedirectUri,
- RedirectUri = descriptor.RedirectUri,
Type = descriptor.Type
};
+ if (descriptor.PostLogoutRedirectUris.Count != 0)
+ {
+ application.PostLogoutRedirectUris = string.Join(
+ OpenIddictConstants.Separators.Space,
+ descriptor.PostLogoutRedirectUris.Select(uri => uri.OriginalString));
+ }
+
+ if (descriptor.RedirectUris.Count != 0)
+ {
+ application.RedirectUris = string.Join(
+ OpenIddictConstants.Separators.Space,
+ descriptor.RedirectUris.Select(uri => uri.OriginalString));
+ }
+
return CreateAsync(application, cancellationToken);
}
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
index 6a1b7c83..cfcd38e6 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
@@ -124,10 +124,15 @@ namespace OpenIddict.EntityFrameworkCore
var authorization = new TAuthorization
{
- Scope = string.Join(" ", descriptor.Scopes),
+ Status = descriptor.Status,
Subject = descriptor.Subject
};
+ if (descriptor.Scopes.Count != 0)
+ {
+ authorization.Scopes = string.Join(OpenIddictConstants.Separators.Space, descriptor.Scopes);
+ }
+
// Bind the authorization to the specified application, if applicable.
if (!string.IsNullOrEmpty(descriptor.ApplicationId))
{
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
index 39515293..8b9d696f 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
@@ -133,6 +133,7 @@ namespace OpenIddict.EntityFrameworkCore
CreationDate = descriptor.CreationDate,
ExpirationDate = descriptor.ExpirationDate,
Hash = descriptor.Hash,
+ Status = descriptor.Status,
Subject = descriptor.Subject,
Type = descriptor.Type
};
@@ -257,7 +258,7 @@ namespace OpenIddict.EntityFrameworkCore
throw new InvalidOperationException("The authorization associated with the token cannot be found.");
}
- authorization.Tokens.Add(token);
+ token.Authorization = authorization;
}
else
@@ -266,7 +267,7 @@ namespace OpenIddict.EntityFrameworkCore
// Try to retrieve the authorization associated with the token.
// If none can be found, assume that no authorization is attached.
- var authorization = await Authorizations.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
+ var authorization = await Authorizations.FirstOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
if (authorization != null)
{
authorization.Tokens.Remove(token);
@@ -300,7 +301,7 @@ namespace OpenIddict.EntityFrameworkCore
throw new InvalidOperationException("The application associated with the token cannot be found.");
}
- application.Tokens.Add(token);
+ token.Application = application;
}
else
@@ -309,7 +310,7 @@ namespace OpenIddict.EntityFrameworkCore
// Try to retrieve the application associated with the token.
// If none can be found, assume that no application is attached.
- var application = await Applications.SingleOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
+ var application = await Applications.FirstOrDefaultAsync(element => element.Tokens.Any(t => t.Id.Equals(key)));
if (application != null)
{
application.Tokens.Remove(token);
diff --git a/src/OpenIddict.Models/OpenIddictApplication.cs b/src/OpenIddict.Models/OpenIddictApplication.cs
index 120ac6ef..4ddea077 100644
--- a/src/OpenIddict.Models/OpenIddictApplication.cs
+++ b/src/OpenIddict.Models/OpenIddictApplication.cs
@@ -64,16 +64,18 @@ namespace OpenIddict.Models
public virtual TKey Id { get; set; }
///
- /// Gets or sets the logout callback URL
- /// associated with the current application.
+ /// Gets or sets the logout callback URLs
+ /// associated with the current application,
+ /// stored as a unique space-separated string.
///
- public virtual string LogoutRedirectUri { get; set; }
+ public virtual string PostLogoutRedirectUris { get; set; }
///
- /// Gets or sets the callback URL
- /// associated with the current application.
+ /// Gets or sets the callback URLs
+ /// associated with the current application,
+ /// stored as a unique space-separated string.
///
- public virtual string RedirectUri { get; set; }
+ public virtual string RedirectUris { get; set; }
///
/// Gets the list of the tokens associated with this application.
diff --git a/src/OpenIddict.Models/OpenIddictAuthorization.cs b/src/OpenIddict.Models/OpenIddictAuthorization.cs
index 2da90c02..5ec20cd3 100644
--- a/src/OpenIddict.Models/OpenIddictAuthorization.cs
+++ b/src/OpenIddict.Models/OpenIddictAuthorization.cs
@@ -48,12 +48,12 @@ namespace OpenIddict.Models
/// Gets or sets the space-delimited scopes
/// associated with the current authorization.
///
- public virtual string Scope { get; set; }
+ public virtual string Scopes { get; set; }
///
/// Gets or sets the status of the current authorization.
///
- public virtual string Status { get; set; } = "valid";
+ public virtual string Status { get; set; }
///
/// Gets or sets the subject associated with the current authorization.
diff --git a/src/OpenIddict.Models/OpenIddictToken.cs b/src/OpenIddict.Models/OpenIddictToken.cs
index aeef2b6a..bf2026e7 100644
--- a/src/OpenIddict.Models/OpenIddictToken.cs
+++ b/src/OpenIddict.Models/OpenIddictToken.cs
@@ -78,7 +78,7 @@ namespace OpenIddict.Models
///
/// Gets or sets the status of the current token.
///
- public virtual string Status { get; set; } = "valid";
+ public virtual string Status { get; set; }
///
/// Gets or sets the subject associated with the current token.
diff --git a/src/OpenIddict/OpenIddictProvider.Authentication.cs b/src/OpenIddict/OpenIddictProvider.Authentication.cs
index 9425c901..d4772f84 100644
--- a/src/OpenIddict/OpenIddictProvider.Authentication.cs
+++ b/src/OpenIddict/OpenIddictProvider.Authentication.cs
@@ -266,33 +266,20 @@ namespace OpenIddict
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
- error: OpenIdConnectConstants.Errors.InvalidClient,
+ error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Application not found in the database: ensure that your client_id is correct.");
return;
}
- // Ensure a redirect_uri was associated with the application.
- if (!await applications.HasRedirectUriAsync(application, context.HttpContext.RequestAborted))
- {
- logger.LogError("The authorization request was rejected because no redirect_uri " +
- "was registered with the application '{ClientId}'.", context.ClientId);
-
- context.Reject(
- error: OpenIdConnectConstants.Errors.UnauthorizedClient,
- description: "The client application is not allowed to use interactive flows.");
-
- return;
- }
-
- // Ensure the redirect_uri is valid.
+ // Ensure that the specified redirect_uri is valid and is associated with the client application.
if (!await applications.ValidateRedirectUriAsync(application, context.RedirectUri, context.HttpContext.RequestAborted))
{
logger.LogError("The authorization request was rejected because the redirect_uri " +
"was invalid: '{RedirectUri}'.", context.RedirectUri);
context.Reject(
- error: OpenIdConnectConstants.Errors.InvalidClient,
+ error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Invalid redirect_uri.");
return;
diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs
index b45cbad1..70654854 100644
--- a/src/OpenIddict/OpenIddictProvider.Serialization.cs
+++ b/src/OpenIddict/OpenIddictProvider.Serialization.cs
@@ -217,6 +217,7 @@ namespace OpenIddict
{
CreationDate = ticket.Properties.IssuedUtc,
ExpirationDate = ticket.Properties.ExpiresUtc,
+ Status = OpenIddictConstants.Statuses.Valid,
Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject),
Type = type
};
@@ -288,13 +289,7 @@ namespace OpenIddict
{
Debug.Assert(!string.IsNullOrEmpty(descriptor.ApplicationId), "The client identifier shouldn't be null.");
- var authorization = await authorizations.CreateAsync(new OpenIddictAuthorizationDescriptor
- {
- ApplicationId = descriptor.ApplicationId,
- Scopes = request.GetScopes(),
- Subject = descriptor.Subject
- }, context.RequestAborted);
-
+ var authorization = await CreateAuthorizationAsync(descriptor, context, request);
if (authorization != null)
{
descriptor.AuthorizationId = await authorizations.GetIdAsync(authorization, context.RequestAborted);
@@ -433,5 +428,26 @@ namespace OpenIddict
return ticket;
}
+
+ private Task CreateAuthorizationAsync(
+ [NotNull] OpenIddictTokenDescriptor token,
+ [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request)
+ {
+ var authorizations = context.RequestServices.GetRequiredService>();
+
+ var descriptor = new OpenIddictAuthorizationDescriptor
+ {
+ ApplicationId = token.ApplicationId,
+ Status = OpenIddictConstants.Statuses.Valid,
+ Subject = token.Subject
+ };
+
+ foreach (var scope in request.GetScopes())
+ {
+ descriptor.Scopes.Add(scope);
+ }
+
+ return authorizations.CreateAsync(descriptor, context.RequestAborted);
+ }
}
}
\ No newline at end of file
diff --git a/src/OpenIddict/OpenIddictProvider.Session.cs b/src/OpenIddict/OpenIddictProvider.Session.cs
index 06adf6aa..06889d89 100644
--- a/src/OpenIddict/OpenIddictProvider.Session.cs
+++ b/src/OpenIddict/OpenIddictProvider.Session.cs
@@ -88,17 +88,43 @@ namespace OpenIddict
var logger = context.HttpContext.RequestServices.GetRequiredService>>();
// If an optional post_logout_redirect_uri was provided, validate it.
- if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) &&
- !await applications.ValidateLogoutRedirectUriAsync(context.PostLogoutRedirectUri, context.HttpContext.RequestAborted))
+ if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri))
{
- logger.LogError("The logout request was rejected because the specified post_logout_redirect_uri " +
- "was invalid: '{PostLogoutRedirectUri}'.", context.PostLogoutRedirectUri);
+ if (!Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
+ {
+ logger.LogError("The logout request was rejected because the specified post_logout_redirect_uri was not " +
+ "a valid absolute URL: {PostLogoutRedirectUri}.", context.PostLogoutRedirectUri);
- context.Reject(
- error: OpenIdConnectConstants.Errors.InvalidClient,
- description: "Invalid post_logout_redirect_uri.");
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidRequest,
+ description: "The 'post_logout_redirect_uri' parameter must be a valid absolute URL.");
- return;
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(uri.Fragment))
+ {
+ logger.LogError("The logout request was rejected because the 'post_logout_redirect_uri' contained " +
+ "a URL fragment: {PostLogoutRedirectUri}.", context.PostLogoutRedirectUri);
+
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidRequest,
+ description: "The 'post_logout_redirect_uri' parameter must not include a fragment.");
+
+ return;
+ }
+
+ if (!await applications.ValidatePostLogoutRedirectUriAsync(context.PostLogoutRedirectUri, context.HttpContext.RequestAborted))
+ {
+ logger.LogError("The logout request was rejected because the specified post_logout_redirect_uri " +
+ "was unknown: {PostLogoutRedirectUri}.", context.PostLogoutRedirectUri);
+
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidRequest,
+ description: "Invalid post_logout_redirect_uri.");
+
+ return;
+ }
}
context.Validate();
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
index 6424327d..99dc5dfc 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
@@ -338,50 +338,12 @@ namespace OpenIddict.Tests
});
// Assert
- Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
+ Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once());
}
- [Fact]
- public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenClientHasNoRedirectUri()
- {
- // Arrange
- var application = new OpenIddictApplication();
-
- var manager = CreateApplicationManager(instance =>
- {
- instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
- .ReturnsAsync(application);
-
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(false);
- });
-
- var server = CreateAuthorizationServer(builder =>
- {
- builder.Services.AddSingleton(manager);
- });
-
- var client = new OpenIdConnectClient(server.CreateClient());
-
- // Act
- var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest
- {
- ClientId = "Fabrikam",
- RedirectUri = "http://www.fabrikam.com/path",
- ResponseType = OpenIdConnectConstants.ResponseTypes.Code
- });
-
- // Assert
- Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error);
- Assert.Equal("The client application is not allowed to use interactive flows.", response.ErrorDescription);
-
- Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.HasRedirectUriAsync(application, It.IsAny()), Times.Once());
- }
-
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid()
{
@@ -393,9 +355,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(false);
});
@@ -416,11 +375,10 @@ namespace OpenIddict.Tests
});
// Assert
- Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
+ Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Invalid redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.HasRedirectUriAsync(application, It.IsAny()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once());
}
@@ -440,9 +398,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -472,7 +427,6 @@ namespace OpenIddict.Tests
Assert.Equal("Confidential clients are not allowed to retrieve a token from the authorization endpoint.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.HasRedirectUriAsync(application, It.IsAny()), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application, It.IsAny()), Times.Once());
}
@@ -492,9 +446,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -549,9 +500,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -627,9 +575,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -671,9 +616,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
index 76cfac78..9e4f887b 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
@@ -238,9 +238,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -301,9 +298,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -375,9 +369,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -447,9 +438,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -508,9 +496,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
@@ -574,9 +559,6 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- instance.Setup(mock => mock.HasRedirectUriAsync(application, It.IsAny()))
- .ReturnsAsync(true);
-
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
index 94972245..4b66a1dd 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Session.cs
@@ -62,13 +62,36 @@ namespace OpenIddict.Tests
Assert.Equal("Invalid request: timeout expired.", response.ErrorDescription);
}
+ [Theory]
+ [InlineData("/path", "The 'post_logout_redirect_uri' parameter must be a valid absolute URL.")]
+ [InlineData("/tmp/file.xml", "The 'post_logout_redirect_uri' parameter must be a valid absolute URL.")]
+ [InlineData("C:\\tmp\\file.xml", "The 'post_logout_redirect_uri' parameter must be a valid absolute URL.")]
+ [InlineData("http://www.fabrikam.com/path#param=value", "The 'post_logout_redirect_uri' parameter must not include a fragment.")]
+ public async Task ValidateLogoutRequest_RequestIsRejectedWhenRedirectUriIsInvalid(string address, string message)
+ {
+ // Arrange
+ var server = CreateAuthorizationServer();
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(LogoutEndpoint, new OpenIdConnectRequest
+ {
+ PostLogoutRedirectUri = address
+ });
+
+ // Assert
+ Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
+ Assert.Equal(message, response.ErrorDescription);
+ }
+
[Fact]
- public async Task ValidateLogoutRequest_RequestIsRejectedWhenRedirectUriIsInvalid()
+ public async Task ValidateLogoutRequest_RequestIsRejectedWhenRedirectUriIsUnknown()
{
// Arrange
var manager = CreateApplicationManager(instance =>
{
- instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
+ instance.Setup(mock => mock.ValidatePostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(false);
});
@@ -86,10 +109,10 @@ namespace OpenIddict.Tests
});
// Assert
- Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
+ Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Invalid post_logout_redirect_uri.", response.ErrorDescription);
- Mock.Get(manager).Verify(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.ValidatePostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()), Times.Once());
}
[Fact]
@@ -102,7 +125,7 @@ namespace OpenIddict.Tests
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
- instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
+ instance.Setup(mock => mock.ValidatePostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
}));
@@ -139,7 +162,7 @@ namespace OpenIddict.Tests
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
- instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
+ instance.Setup(mock => mock.ValidatePostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(true);
}));
});
@@ -165,7 +188,7 @@ namespace OpenIddict.Tests
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
- instance.Setup(mock => mock.ValidateLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
+ instance.Setup(mock => mock.ValidatePostLogoutRedirectUriAsync("http://www.fabrikam.com/path", It.IsAny()))
.ReturnsAsync(false);
}));