192 lines
7.4 KiB
C#
192 lines
7.4 KiB
C#
// 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.Tasks;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Microsoft.AspNetCore.Identity.Service
|
|
{
|
|
public class ClientApplicationValidator<TApplication> : IClientIdValidator, IRedirectUriResolver, IScopeResolver
|
|
where TApplication : class
|
|
{
|
|
private readonly IOptions<IdentityServiceOptions> _options;
|
|
private readonly SessionManager _sessionManager;
|
|
private readonly ApplicationManager<TApplication> _applicationManager;
|
|
private readonly ProtocolErrorProvider _errorProvider;
|
|
|
|
public ClientApplicationValidator(
|
|
IOptions<IdentityServiceOptions> options,
|
|
SessionManager sessionManager,
|
|
ApplicationManager<TApplication> applicationManager,
|
|
ProtocolErrorProvider errorProvider)
|
|
{
|
|
_options = options;
|
|
_sessionManager = sessionManager;
|
|
_applicationManager = applicationManager;
|
|
_errorProvider = errorProvider;
|
|
}
|
|
|
|
public Task<bool> ValidateClientCredentialsAsync(string clientId, string clientSecret)
|
|
{
|
|
return _applicationManager.ValidateClientCredentialsAsync(clientId, clientSecret);
|
|
}
|
|
|
|
public async Task<bool> ValidateClientIdAsync(string clientId)
|
|
{
|
|
return await _applicationManager.FindByClientIdAsync(clientId) != null;
|
|
}
|
|
|
|
public async Task<RedirectUriResolutionResult> ResolveLogoutUriAsync(string clientId, string logoutUrl)
|
|
{
|
|
if (logoutUrl == null)
|
|
{
|
|
return RedirectUriResolutionResult.Valid(logoutUrl);
|
|
}
|
|
|
|
var sessions = await _sessionManager.GetCurrentSessions();
|
|
if (clientId == null)
|
|
{
|
|
foreach (var identity in sessions.Identities)
|
|
{
|
|
if (identity.HasClaim(IdentityServiceClaimTypes.LogoutRedirectUri, logoutUrl))
|
|
{
|
|
return RedirectUriResolutionResult.Valid(logoutUrl);
|
|
}
|
|
}
|
|
|
|
return RedirectUriResolutionResult.Invalid(null);
|
|
}
|
|
|
|
foreach (var identity in sessions.Identities)
|
|
{
|
|
if (identity.HasClaim(IdentityServiceClaimTypes.ClientId, clientId) &&
|
|
identity.HasClaim(IdentityServiceClaimTypes.LogoutRedirectUri, logoutUrl))
|
|
{
|
|
return RedirectUriResolutionResult.Valid(logoutUrl);
|
|
}
|
|
}
|
|
|
|
return RedirectUriResolutionResult.Invalid(null);
|
|
}
|
|
|
|
public async Task<RedirectUriResolutionResult> ResolveRedirectUriAsync(string clientId, string redirectUrl)
|
|
{
|
|
if (clientId == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(clientId));
|
|
}
|
|
|
|
var app = await _applicationManager.FindByClientIdAsync(clientId);
|
|
if (app == null)
|
|
{
|
|
return RedirectUriResolutionResult.Invalid(_errorProvider.InvalidClientId(clientId));
|
|
}
|
|
|
|
var redirectUris = await _applicationManager.FindRegisteredUrisAsync(app);
|
|
if (redirectUrl == null && redirectUris.Count() == 1)
|
|
{
|
|
return RedirectUriResolutionResult.Valid(redirectUris.Single());
|
|
}
|
|
|
|
foreach (var uri in redirectUris)
|
|
{
|
|
if (string.Equals(uri, redirectUrl, StringComparison.Ordinal))
|
|
{
|
|
return RedirectUriResolutionResult.Valid(redirectUrl);
|
|
}
|
|
}
|
|
|
|
return RedirectUriResolutionResult.Invalid(_errorProvider.InvalidRedirectUri(redirectUrl));
|
|
}
|
|
|
|
public async Task<ScopeResolutionResult> ResolveScopesAsync(string clientId, IEnumerable<string> scopes)
|
|
{
|
|
var authorizedParty = await _applicationManager.FindByClientIdAsync(clientId);
|
|
var authorizedPartyScopes = await _applicationManager.FindScopesAsync(authorizedParty);
|
|
|
|
var result = new List<ApplicationScope>();
|
|
|
|
string resourceName = null;
|
|
TApplication resourceApplication = null;
|
|
IEnumerable<string> resourceApplicationScopes = null;
|
|
|
|
foreach (var scope in scopes)
|
|
{
|
|
var (wellFormed, canonical, name, scopeValue) = ParseScope(scope);
|
|
if (!wellFormed)
|
|
{
|
|
return ScopeResolutionResult.Invalid(_errorProvider.InvalidScope(scope));
|
|
}
|
|
|
|
if (canonical && authorizedPartyScopes.Any(s => s.Equals(scope, StringComparison.Ordinal)))
|
|
{
|
|
result.Add(ApplicationScope.CanonicalScopes[scope]);
|
|
}
|
|
if (canonical)
|
|
{
|
|
// We purposely ignore canonical scopes not allowed by the client application.
|
|
continue;
|
|
}
|
|
|
|
resourceName = resourceName ?? name;
|
|
if (resourceName != null && !resourceName.Equals(name, StringComparison.Ordinal))
|
|
{
|
|
return ScopeResolutionResult.Invalid(_errorProvider.MultipleResourcesNotSupported(resourceName, name));
|
|
}
|
|
|
|
if (resourceApplicationScopes == null)
|
|
{
|
|
resourceApplication = await _applicationManager.FindByNameAsync(resourceName);
|
|
if (resourceApplication == null)
|
|
{
|
|
return ScopeResolutionResult.Invalid(_errorProvider.InvalidScope(scope));
|
|
}
|
|
|
|
resourceApplicationScopes = await _applicationManager.FindScopesAsync(resourceApplication);
|
|
}
|
|
|
|
if (!resourceApplicationScopes.Contains(scopeValue, StringComparer.Ordinal))
|
|
{
|
|
return ScopeResolutionResult.Invalid(_errorProvider.InvalidScope(scope));
|
|
}
|
|
else
|
|
{
|
|
var resourceClientId = await _applicationManager.GetApplicationClientIdAsync(resourceApplication);
|
|
result.Add(new ApplicationScope(resourceClientId, scopeValue));
|
|
}
|
|
}
|
|
|
|
return ScopeResolutionResult.Valid(result);
|
|
}
|
|
|
|
private (bool wellFormed, bool canonical, string name, string value) ParseScope(string scope)
|
|
{
|
|
if (ApplicationScope.CanonicalScopes.TryGetValue(scope, out var canonicalScope))
|
|
{
|
|
return (true, true, null, scope);
|
|
}
|
|
|
|
var prefix = _options.Value.Issuer;
|
|
if (scope.StartsWith(prefix))
|
|
{
|
|
var start = prefix.EndsWith("/") ? prefix.Length : prefix.Length + 1;
|
|
var end = scope.IndexOf('/', start);
|
|
if (end == -1 | end == scope.Length - 1)
|
|
{
|
|
return (false, false, null, null);
|
|
}
|
|
var applicationName = scope.Substring(start, end - start);
|
|
var scopeValue = scope.Substring(end + 1);
|
|
return (true, false, applicationName, scopeValue);
|
|
}
|
|
else
|
|
{
|
|
return (false, false, null, null);
|
|
}
|
|
}
|
|
}
|
|
}
|