Skip to content

Commit

Permalink
[ODS-6265] Cherry-Pick ODS-5987 into main6x - Relationship Authorizat…
Browse files Browse the repository at this point in the history
…ion for StudentAssessment based on ReportingSchool (#981)

Co-authored-by: Geoff McElhanon <[email protected]>
Co-authored-by: Audrey Shay <[email protected]>
  • Loading branch information
3 people authored Mar 5, 2024
1 parent e41c265 commit 01a63f6
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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.Concurrent;
using System.Linq;
using EdFi.Ods.Common.Specifications;

namespace EdFi.Ods.Common.Models.Domain;

public static class DomainModelConventionExtensions
{
private static readonly ConcurrentDictionary<DomainModel, Entity[]> _personEntitiesByDomainModel = new();

/// <summary>
/// Gets the entities of the domain model that are person types.
/// </summary>
/// <param name="domainModel">The <see cref="DomainModel" /> to be evaluated.</param>
/// <returns>An array of entities representing the person types.</returns>
public static Entity[] GetPersonEntities(this DomainModel domainModel)
{
return _personEntitiesByDomainModel.GetOrAdd(domainModel,
dm =>
dm.Aggregates
.Select(a => a.AggregateRoot)
.Where(e =>
e.Identifier.Properties.Count == 1
&& UniqueIdConventions.IsUSI(e.Identifier.Properties[0].PropertyName, e.Name))
.ToArray());
}
}
70 changes: 70 additions & 0 deletions Application/EdFi.Ods.Common/Specifications/UniqueIdConventions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 EdFi.Common.Extensions;

namespace EdFi.Ods.Common.Specifications;

public static class UniqueIdConventions
{
public const string UsiSuffix = "USI";
public const string UniqueIdSuffix = "UniqueId";

/// <summary>
/// Indicates whether the supplied property name matches the USI naming convention.
/// </summary>
/// <param name="propertyName">The property name to be evaluated.</param>
/// <param name="personEntityName">If supplied, ensures the property name also matches the person entity name as the prefix.</param>
/// <returns><b>true</b> if the property matches the convention; otherwise <b>false</b>.</returns>
public static bool IsUSI(string propertyName, string personEntityName = null)
{
if (personEntityName == null)
{
return propertyName.EndsWith(UsiSuffix);
}

return propertyName.StartsWithIgnoreCase(personEntityName)
&& propertyName.EndsWith(UsiSuffix)
&& propertyName.Length == (personEntityName.Length + UsiSuffix.Length);
}

public static string RemoveUsiSuffix(string propertyName)
{
return propertyName.ReplaceSuffix(UsiSuffix, string.Empty);
}

public static string GetUsiPropertyName(string uniqueIdColumnName)
{
return uniqueIdColumnName.ReplaceSuffix(UniqueIdSuffix, UsiSuffix);
}

/// <summary>
/// Indicates whether the supplied property name matches the UniqueId naming convention.
/// </summary>
/// <param name="propertyName">The property name to be evaluated.</param>
/// <param name="personEntityName">If supplied, ensures the property name also matches the person entity name as the prefix.</param>
/// <returns><b>true</b> if the property matches the convention; otherwise <b>false</b>.</returns>
public static bool IsUniqueId(string propertyName, string personEntityName = null)
{
if (personEntityName == null)
{
return propertyName.EndsWith(UniqueIdSuffix);
}

return propertyName.StartsWithIgnoreCase(personEntityName)
&& propertyName.EndsWith(UniqueIdSuffix)
&& propertyName.Length == (personEntityName.Length + UniqueIdSuffix.Length);
}

public static string RemoveUniqueIdSuffix(string propertyName)
{
return propertyName.ReplaceSuffix(UniqueIdSuffix, string.Empty);
}

public static string GetUniqueIdPropertyName(string usiPropertyName)
{
return usiPropertyName.ReplaceSuffix(UsiSuffix, UniqueIdSuffix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
// 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.Configuration;
using System.Linq;
using Autofac;
using EdFi.Ods.Common.Extensions;
using EdFi.Ods.Api.Security.AuthorizationStrategies.Relationships;

namespace EdFi.Ods.Standard.Container.Modules
Expand All @@ -16,48 +12,13 @@ public class RelationshipsAuthorizationContextDataProviderModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(Marker_EdFi_Ods_Standard).Assembly)
.AsClosedTypesOf(typeof(IRelationshipsAuthorizationContextDataProvider<,>))
.AsImplementedInterfaces();

var relationshipContextDataProviderTypes = typeof(Marker_EdFi_Ods_Standard).Assembly.GetTypes()
.Where(
t => !t.IsAbstract && typeof(IRelationshipsAuthorizationContextDataProvider<,>).IsAssignableFromGeneric(t))
.ToList();

// Register all context data providers including any defined in extension assemblies.
// NOTE: If there is no entries for relationshipContextDataProviderTypes this means that the EdFi.Ods.Standard.Security assembly is not loaded.
// and this can be resolved by calling AssemblyLoader.EnsureLoaded<Marker_EdFi_Ods_Standard_Security>(); in your configuration
if (!relationshipContextDataProviderTypes.Any())
{
throw new ConfigurationErrorsException(
"Unable to find any relationship-based authorization context providers dynamically.");
}

foreach (var providerType in relationshipContextDataProviderTypes)
{
var partiallyClosedInterfaceType =
providerType.GetInterfaces()
.SingleOrDefault(i => i.Name == typeof(IRelationshipsAuthorizationContextDataProvider<,>).Name);

var modelType = partiallyClosedInterfaceType?.GetGenericArguments()[0];

var closedInterfaceType =
typeof(IRelationshipsAuthorizationContextDataProvider<,>)
.MakeGenericType(modelType, GetRelationshipBasedAuthorizationStrategyContextDataType());

var closedServiceType =
providerType
.MakeGenericType(GetRelationshipBasedAuthorizationStrategyContextDataType());

// Register the authorization context data provider, but allow existing "override" registrations to persist
builder.RegisterType(closedServiceType)
.As(closedInterfaceType)
.SingleInstance()
.PreserveExistingDefaults();
}

Type GetRelationshipBasedAuthorizationStrategyContextDataType() => typeof(RelationshipsAuthorizationContextData);
builder.RegisterAssemblyOpenGenericTypes(typeof(Marker_EdFi_Ods_Standard).Assembly)
.Where(t =>
// Avoid registering any overrides here
t.Namespace?.Contains(".Overrides") != true
&& t.IsAssignableTo<IRelationshipsAuthorizationContextDataProvider<RelationshipsAuthorizationContextData>>())
.AsImplementedInterfaces()
.SingleInstance();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType<DisciplineActionRelationshipsAuthorizationContextDataProvider<RelationshipsAuthorizationContextData>>()
.As<IRelationshipsAuthorizationContextDataProvider<IDisciplineAction, RelationshipsAuthorizationContextData>>()
.SingleInstance();

// Establish authorization context for StudentAssessment using the ReportedSchoolId and StudentUSI rather than
// using the default convention (only StudentUSI)
builder.RegisterType<StudentAssessmentRelationshipsAuthorizationContextDataProvider<RelationshipsAuthorizationContextData>>()
.As<IRelationshipsAuthorizationContextDataProvider<IStudentAssessment, RelationshipsAuthorizationContextData>>()
.SingleInstance();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 EdFi.Ods.Api.Security.AuthorizationStrategies.Relationships;
using EdFi.Ods.Entities.Common.EdFi;
using EdFi.Ods.Entities.NHibernate.StudentAssessmentAggregate.EdFi;
using System;
using System.Diagnostics.CodeAnalysis;

namespace EdFi.Ods.Standard.Security.Authorization.Overrides
{
/// <summary>
/// Creates and returns an <see cref="RelationshipsAuthorizationContextData"/> instance for making authorization decisions for access to the edfi.StudentAssessment table of the StudentAssessment aggregate in the Ods Database.
/// </summary>
[ExcludeFromCodeCoverage]
public class StudentAssessmentRelationshipsAuthorizationContextDataProvider<TContextData> : IRelationshipsAuthorizationContextDataProvider<IStudentAssessment, TContextData>
where TContextData : RelationshipsAuthorizationContextData, new()
{
/// <summary>
/// Creates and returns an <see cref="TContextData"/> instance based on the supplied resource.
/// </summary>
public TContextData GetContextData(IStudentAssessment resource)
{
if (resource == null)
{
throw new ArgumentNullException(nameof(resource), "The 'studentAssessment' resource for obtaining authorization context data cannot be null.");
}

var entity = resource as StudentAssessment;

var contextData = new TContextData
{
SchoolId = entity.ReportedSchoolId, // Role name applied and not part of primary key
StudentUSI = entity.StudentUSI == default ? null : entity.StudentUSI // Primary key property, USI
};

return contextData;
}

/// <summary>
/// Creates and returns a signature key based on the resource, which can then be used to get and instance of IEdFiSignatureAuthorizationProvider
/// </summary>
public string[] GetAuthorizationContextPropertyNames()
{
var properties = new[]
{
"ReportedSchoolId",
"StudentUSI",
};

return properties;
}

/// <summary>
/// Creates and returns an <see cref="RelationshipsAuthorizationContextData"/> instance based on the supplied resource.
/// </summary>
public TContextData GetContextData(object resource)
{
return GetContextData((StudentAssessment)resource);
}
}
}

0 comments on commit 01a63f6

Please sign in to comment.