Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ODS-6510] Apply DRY principle to security/authorization logic (through consolidation of common code) #1161

Merged
merged 15 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System;
using System.Collections.Generic;
using EdFi.Common.Configuration;
using EdFi.Ods.Api.Security.Authorization;
using EdFi.Ods.Api.Security.Authorization.Filtering;
using EdFi.Ods.Common;
using EdFi.Ods.Common.Context;
using EdFi.Ods.Common.Database.Querying;
using EdFi.Ods.Common.Database.Querying.Dialects;
using EdFi.Ods.Common.Models;
using EdFi.Ods.Common.Models.Domain;
using EdFi.Ods.Common.Providers.Queries;
using EdFi.Ods.Common.Providers.Queries.Criteria;
using EdFi.Ods.Common.Security;
using EdFi.Ods.Common.Security.Authorization;
using EdFi.Ods.Common.Security.Claims;
using EdFi.Security.DataAccess.Repositories;
Expand All @@ -32,12 +28,9 @@ public class PartitionRowNumbersCteQueryBuilderProvider : IAggregateRootQueryBui
private readonly IAggregateRootQueryCriteriaApplicator[] _additionalParametersCriteriaApplicator;

// Security dependencies
private readonly IContextProvider<DataManagementResourceContext> _dataManagementResourceContextProvider;
private readonly IApiClientContextProvider _apiClientContextProvider;
private readonly IAuthorizationContextProvider _authorizationContextProvider;
private readonly IAuthorizationBasisMetadataSelector _authorizationBasisMetadataSelector;
private readonly IAuthorizationFilteringProvider _authorizationFilteringProvider;
private readonly IAuthorizationFilterContextProvider _authorizationFilterContextProvider;
private readonly IDataManagementAuthorizationPlanFactory _dataManagementAuthorizationPlanFactory;
private readonly IContextProvider<DataManagementAuthorizationPlan> _authorizationPlanContextProvider;
private readonly ISecurityRepository _securityRepository;
private readonly IResourceClaimUriProvider _resourceClaimUriProvider;

Expand All @@ -47,24 +40,18 @@ public PartitionRowNumbersCteQueryBuilderProvider(
IAggregateRootQueryCriteriaApplicator[] additionalParametersCriteriaApplicator,

// Security dependencies
IApiClientContextProvider apiClientContextProvider,
IContextProvider<DataManagementResourceContext> dataManagementResourceContextProvider,
IAuthorizationContextProvider authorizationContextProvider,
IAuthorizationBasisMetadataSelector authorizationBasisMetadataSelector,
IAuthorizationFilteringProvider authorizationFilteringProvider,
IAuthorizationFilterContextProvider authorizationFilterContextProvider,
IDataManagementAuthorizationPlanFactory dataManagementAuthorizationPlanFactory,
IContextProvider<DataManagementAuthorizationPlan> authorizationPlanContextProvider,
ISecurityRepository securityRepository,
IResourceClaimUriProvider resourceClaimUriProvider)
{
_dialect = dialect;
_databaseEngine = databaseEngine;
_additionalParametersCriteriaApplicator = additionalParametersCriteriaApplicator;
_apiClientContextProvider = apiClientContextProvider;
_dataManagementResourceContextProvider = dataManagementResourceContextProvider;
_authorizationContextProvider = authorizationContextProvider;
_authorizationBasisMetadataSelector = authorizationBasisMetadataSelector;
_authorizationFilteringProvider = authorizationFilteringProvider;
_authorizationFilterContextProvider = authorizationFilterContextProvider;
_dataManagementAuthorizationPlanFactory = dataManagementAuthorizationPlanFactory;
_authorizationPlanContextProvider = authorizationPlanContextProvider;
_securityRepository = securityRepository;
_resourceClaimUriProvider = resourceClaimUriProvider;
}
Expand All @@ -76,7 +63,7 @@ public QueryBuilder GetQueryBuilder(
IDictionary<string, string> additionalParameters)
{
// TODO: ODS-6510 - This needs to be invokes an authorization decorator of some sort -- copied from the data management controller pipeline. Also, look for approach to DRY here.
EstablishAuthorizationFilteringContext(entity);
EstablishAuthorizationPlan(entity);

var rowNumbersQueryBuilder = new QueryBuilder(_dialect);

Expand Down Expand Up @@ -121,38 +108,16 @@ public QueryBuilder GetQueryBuilder(
}

// TODO: ODS-6510 - THIS NEEDS TO REFACTORED OUT INTO A SECURITY COMPONENT SOMEWHERE - Pay attention to DRY
private void EstablishAuthorizationFilteringContext(dynamic aggregateRootEntity)
private void EstablishAuthorizationPlan(dynamic aggregateRootEntity)
{
// Establish the authorization context -- currently done in SetAuthorizationContext pipeline step, not accessible here
_authorizationContextProvider.SetResourceUris(
_resourceClaimUriProvider.GetResourceClaimUris(aggregateRootEntity));
// Establish the authorization context -- currently done in SetAuthorizationContext pipeline step, but not accessible here
_authorizationContextProvider.SetResourceUris(_resourceClaimUriProvider.GetResourceClaimUris(aggregateRootEntity));

_authorizationContextProvider.SetAction(_securityRepository.GetActionByName("Read").ActionUri);
string actionUri = _securityRepository.GetActionByName("Read").ActionUri;
_authorizationContextProvider.SetAction(actionUri); // TODO: This may not be needed as action is passed.

// Make sure Authorization context is present before proceeding
_authorizationContextProvider.VerifyAuthorizationContextExists();

// Build the AuthorizationContext
var apiClientContext = _apiClientContextProvider.GetApiClientContext();
var resource = _dataManagementResourceContextProvider.Get().Resource;
string[] resourceClaimUris = _authorizationContextProvider.GetResourceUris();
string requestActionUri = _authorizationContextProvider.GetAction();

var authorizationContext = new EdFiAuthorizationContext(
apiClientContext,
resource,
resourceClaimUris,
requestActionUri,
aggregateRootEntity.NHibernateEntityType);

// Get authorization filters
var authorizationBasisMetadata = _authorizationBasisMetadataSelector.SelectAuthorizationBasisMetadata(
apiClientContext.ClaimSetName,
resourceClaimUris,
requestActionUri);

var authorizationFiltering = _authorizationFilteringProvider.GetAuthorizationFiltering(authorizationContext, authorizationBasisMetadata);

_authorizationFilterContextProvider.SetFilterContext(authorizationFiltering);
// Establish the authorization plan
var authorizationPlan = _dataManagementAuthorizationPlanFactory.CreateAuthorizationPlan(actionUri);
_authorizationPlanContextProvider.Set(authorizationPlan);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System;
using System.Collections.Generic;
using EdFi.Security.DataAccess.Repositories;

namespace EdFi.Ods.Api.Security.Authorization.AuthorizationBasis;

public interface IActionBitValueProvider
{
int GetBitValue(string action);
}

public class ActionBitValueProvider : IActionBitValueProvider
{
private readonly Lazy<Dictionary<string, int>> _bitValuesByAction;

public ActionBitValueProvider(ISecurityRepository securityRepository)
{
_bitValuesByAction = new Lazy<Dictionary<string, int>>(
() => new Dictionary<string, int>
{
{ securityRepository.GetActionByName("Create").ActionUri, 1 },
{ securityRepository.GetActionByName("Read").ActionUri, 2 },
{ securityRepository.GetActionByName("Update").ActionUri, 4 },
{ securityRepository.GetActionByName("Delete").ActionUri, 8 },
{ securityRepository.GetActionByName("ReadChanges").ActionUri, 16 }
});
}

public int GetBitValue(string action)
{
if (_bitValuesByAction.Value.TryGetValue(action, out int result))
{
return result;
}

throw new NotSupportedException("The requested action is not supported for authorization.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using EdFi.Ods.Common.Exceptions;
using log4net;

namespace EdFi.Ods.Api.Security.Authorization.AuthorizationBasis;

public class AuthorizationBasisMetadataSelector : IAuthorizationBasisMetadataSelector
{
private readonly IAuthorizationStrategyResolver _authorizationStrategyResolver;
private readonly IClaimSetRequestEvaluator _claimSetRequestEvaluator;
private readonly IRequestEvaluationStrategiesSelector _requestEvaluationStrategiesSelector;
private readonly IRequestEvaluationValidationRuleSetSelector _requestEvaluationValidationRuleSetSelector;

private readonly ILog _logger = LogManager.GetLogger(typeof(AuthorizationBasisMetadataSelector));

/// <summary>
/// Initializes a new instance of the <see cref="AuthorizationBasisMetadataSelector"/> class.
/// </summary>
/// <param name="authorizationStrategyResolver"></param>
/// <param name="claimSetRequestEvaluator"></param>
/// <param name="requestEvaluationStrategiesSelector"></param>
/// <param name="requestEvaluationValidationRuleSetSelector"></param>
public AuthorizationBasisMetadataSelector(
IAuthorizationStrategyResolver authorizationStrategyResolver,
IClaimSetRequestEvaluator claimSetRequestEvaluator,
IRequestEvaluationStrategiesSelector requestEvaluationStrategiesSelector,
IRequestEvaluationValidationRuleSetSelector requestEvaluationValidationRuleSetSelector)
{
_authorizationStrategyResolver = authorizationStrategyResolver;
_claimSetRequestEvaluator = claimSetRequestEvaluator;
_requestEvaluationStrategiesSelector = requestEvaluationStrategiesSelector;
_requestEvaluationValidationRuleSetSelector = requestEvaluationValidationRuleSetSelector;
}

/// <inheritdoc cref="IAuthorizationBasisMetadataSelector.SelectAuthorizationBasisMetadata" />
public AuthorizationBasisMetadata SelectAuthorizationBasisMetadata(
string claimSetName,
IList<string> requestResourceClaimUris,
string requestAction)
{
// Perform basic claims check (and get relevant security metadata)
var requestEvaluation = _claimSetRequestEvaluator.EvaluateRequest(claimSetName, requestResourceClaimUris, requestAction);

if (!requestEvaluation.Success)
{
throw requestEvaluation.SecurityException;
}

// Get the authorization strategies to apply for this request
if (!_requestEvaluationStrategiesSelector.TryGetAuthorizationStrategyNames(
requestEvaluation,
out var authorizationStrategyNames))
{
// No authorization strategies were defined for this request
throw new SecurityConfigurationException(
SecurityConfigurationException.DefaultDetail,
string.Format(
"No authorization strategies were defined for the requested action '{0}' against resource URIs ['{1}'] matched by the caller's claim '{2}'.",
requestEvaluation.RequestedAction,
string.Join("', '", requestEvaluation.RequestedResourceUris),
requestEvaluation.ClaimSetResourceClaimLineage.First().ClaimName));
}

if (_logger.IsDebugEnabled)
{
_logger.Debug(
$"Authorization strategy '{string.Join("', '", authorizationStrategyNames)}' selected for request against resource '{requestResourceClaimUris.First()}'.");
}

string validationRuleSetName = _requestEvaluationValidationRuleSetSelector.GetValidationRuleSetName(requestEvaluation);

// Set outbound authorization details
return new AuthorizationBasisMetadata(
_authorizationStrategyResolver.GetAuthorizationStrategies(authorizationStrategyNames),
requestEvaluation.ClaimSetResourceClaimLineage.First(),
validationRuleSetName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using EdFi.Ods.Common.Security.Claims;

namespace EdFi.Ods.Api.Security.Authorization.AuthorizationBasis;

public interface IAuthorizationContextValidator
{
void Validate(IList<string> resourceClaimUris, string requestAction);
}

public class AuthorizationContextValidator : IAuthorizationContextValidator
{
public void Validate(IList<string> resourceClaimUris, string requestAction)
{
if (resourceClaimUris == null || resourceClaimUris.All(string.IsNullOrWhiteSpace))
{
throw new AuthorizationContextException("Authorization can only be performed if a resource is specified.");
}

if (resourceClaimUris.Count > 2)
{
throw new AuthorizationContextException($"Unexpected number of Resource URIs found in the authorization context. Expected up to 2, but found {resourceClaimUris.Count}.");
}

if (string.IsNullOrEmpty(requestAction))
{
throw new AuthorizationContextException("Authorization can only be performed if an action is specified.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using Autofac.Extras.DynamicProxy;
using EdFi.Ods.Api.Security.AuthorizationStrategies;
using EdFi.Ods.Common.Caching;
using EdFi.Ods.Common.Exceptions;
using EdFi.Ods.Common.Security.Authorization;

namespace EdFi.Ods.Api.Security.Authorization.AuthorizationBasis;

[Intercept(InterceptorCacheKeys.Security)]
public interface IAuthorizationStrategyResolver
{
IReadOnlyList<IAuthorizationStrategy> GetAuthorizationStrategies(IReadOnlyList<string> strategyNames);
}

public class AuthorizationStrategyResolver : IAuthorizationStrategyResolver
{
private readonly IAuthorizationStrategyProvider[] _authorizationStrategyProviders;

public AuthorizationStrategyResolver(IAuthorizationStrategyProvider[] authorizationStrategyProviders)
{
_authorizationStrategyProviders = authorizationStrategyProviders;
}

public IReadOnlyList<IAuthorizationStrategy> GetAuthorizationStrategies(IReadOnlyList<string> strategyNames)
{
return strategyNames.Select(
strategyName =>
{
foreach (var authorizationStrategyProvider in _authorizationStrategyProviders)
{
var authorizationStrategy = authorizationStrategyProvider.GetByName(strategyName);

if (authorizationStrategy != null)
{
return authorizationStrategy;
}
}

throw new SecurityConfigurationException(
mjaksn marked this conversation as resolved.
Show resolved Hide resolved
SecurityConfigurationException.DefaultDetail,
$"Could not find an authorization strategy implementation for strategy name '{strategyName}'.");
})
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Collections.Generic;
using EdFi.Ods.Common.Exceptions;
using EdFi.Ods.Common.Security.Claims;

namespace EdFi.Ods.Api.Security.Authorization.AuthorizationBasis;

public class ClaimSetRequestEvaluation
{
/// <summary>
/// Indicates whether the claims-based check was successful for the requested resource and action.
/// </summary>
public bool Success { get; set; }

/// <summary>
/// The security exception to be thrown, in the event of a failure.
/// </summary>
public SecurityAuthorizationException SecurityException { get; set; }

/// <summary>
/// The requested action.
/// </summary>
public string RequestedAction { get; set; }

/// <summary>
/// The possible resource claim URIs for the requested resource (i.e. schema-less (legacy) and schema-based representations).
/// </summary>
public IList<string> RequestedResourceUris { get; set; }

/// <summary>
/// Claim set-specific authorization metadata (which includes claim set-specific overrides) for claims assigned to the
/// claim set that intersect with the <see cref="DefaultResourceClaimLineage"/> and can be used to authorize the request.
/// </summary>
public IList<ClaimSetResourceClaimMetadata> ClaimSetResourceClaimLineage { get; set; }

/// <summary>
/// Default authorization metadata for the full lineage of resource claims that can be used to authorize the request.
/// </summary>
public IList<DefaultResourceClaimMetadata> DefaultResourceClaimLineage { get; set; }
}
Loading
Loading