Skip to content

Commit

Permalink
[ODS-6129] Improve Identities Service Extensibility (#953)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmcelhanon authored Feb 16, 2024
1 parent 4b5d6f2 commit 18635d6
Show file tree
Hide file tree
Showing 17 changed files with 292 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ namespace EdFi.Ods.Features.IdentityManagement.Models
/// <summary>
/// Implement this interface if the supporting service supports synchronous methods
/// </summary>
public interface IIdentityService
public interface IIdentityService<in TCreateRequest, in TSearchRequest, TSearchResponse, TIdentityResponse>
where TCreateRequest : IdentityCreateRequest
where TSearchRequest : IdentitySearchRequest
where TSearchResponse : IdentitySearchResponse<TIdentityResponse>
where TIdentityResponse : IdentityResponse
{
/// <summary>
/// Which IdentityServices does the supporting service implement
Expand All @@ -20,20 +24,20 @@ public interface IIdentityService
/// </summary>
/// <param name="createRequest">an array of identities to be created</param>
/// <returns>An identity response status of: Success</returns>
Task<IdentityResponseStatus<string>> Create(IdentityCreateRequest createRequest);
Task<IdentityResponseStatus<string>> Create(TCreateRequest createRequest);

/// <summary>
/// Find existing identities by their identifiers
/// </summary>
/// <param name="findRequest">Unique person identifiers to look up</param>
/// <returns>An identity response status of: Success with IdentityResponse[]</returns>
Task<IdentityResponseStatus<IdentitySearchResponse>> Find(params string[] findRequest);
Task<IdentityResponseStatus<TSearchResponse>> Find(params string[] findRequest);

/// <summary>
/// Search for exact and potential identity matches
/// </summary>
/// <param name="searchRequest"></param>
/// <returns>An identity response status of: Success</returns>
Task<IdentityResponseStatus<IdentitySearchResponse>> Search(params IdentitySearchRequest[] searchRequest);
Task<IdentityResponseStatus<TSearchResponse>> Search(params TSearchRequest[] searchRequest);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
namespace EdFi.Ods.Features.IdentityManagement.Models
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models
{
/// <summary>
/// Implement this interface if the supporting service supports asynchronous methods
/// </summary>
public interface IIdentityServiceAsync
public interface IIdentityServiceAsync<in TSearchRequest, TSearchResponse, TIdentityResponse>
where TSearchRequest : IdentitySearchRequest
where TSearchResponse : IdentitySearchResponse<TIdentityResponse>
where TIdentityResponse : IdentityResponse
{
/// <summary>
/// Which IdentityServices does the supporting service implement
Expand All @@ -22,13 +30,13 @@ public interface IIdentityServiceAsync
/// </summary>
/// <param name="searchRequest"></param>
/// <returns>An identity response status of: Success</returns>
Task<IdentityResponseStatus<string>> Search(params IdentitySearchRequest[] searchRequest);
Task<IdentityResponseStatus<string>> Search(params TSearchRequest[] searchRequest);

/// <summary>
/// Retrieve the results from a previously submitted search
/// </summary>
/// <param name="requestToken">a unique string representing the request</param>
/// <returns>An identity response status of: Incomplete, Success with IdentityResponse[], or NotFound</returns>
Task<IdentityResponseStatus<IdentitySearchResponse>> Response(string requestToken);
Task<IdentityResponseStatus<TSearchResponse>> Response(string requestToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models;

/// <summary>
/// Defines an interface that closes the generic types of the <see cref="IIdentityService{TCreateRequest,TSearchRequest,TSearchResponse,TIdentityResponse}" />
/// with the default Identities request/response models.
/// </summary>
public interface IIdentityServiceWithDefaultModels
: IIdentityService<IdentityCreateRequest, IdentitySearchRequest, IdentitySearchResponse<IdentityResponse>,
IdentityResponse> { };
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models;

/// <summary>
/// Defines an interface that closes the generic types of the <see cref="IIdentityServiceAsync{TSearchRequest,TSearchResponse,TIdentityResponse}" />
/// with the default Identities request/response models.
/// </summary>
public interface IIdentityServiceWithDefaultModelsAsync
: IIdentityServiceAsync<IdentitySearchRequest, IdentitySearchResponse<IdentityResponse>, IdentityResponse> { };
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using EdFi.Ods.Common.Expando;
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models
{
public class IdentityCreateRequest : Expando
public class IdentityCreateRequest
{
public string LastSurname { get; set; }

Expand All @@ -20,4 +23,4 @@ public class IdentityCreateRequest : Expando

public Location BirthLocation { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using EdFi.Ods.Common.Expando;
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models
{
public class IdentityResponse : Expando
public class IdentityResponse
{
public string UniqueId { get; set; }

Expand All @@ -24,4 +27,4 @@ public class IdentityResponse : Expando

public Location BirthLocation { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ public class IdentityResponseStatus<TResponse>

public IEnumerable<IdentityError> Errors { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using EdFi.Ods.Common.Expando;
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models
{
public class IdentitySearchRequest : Expando
public class IdentitySearchRequest
{
public string LastSurname { get; set; }

Expand All @@ -20,4 +23,4 @@ public class IdentitySearchRequest : Expando

public Location BirthLocation { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
namespace EdFi.Ods.Features.IdentityManagement.Models
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models
{
public class IdentitySearchResponse
public class IdentitySearchResponse<TIdentityResponse>
where TIdentityResponse : IdentityResponse
{
public SearchResponseStatus Status { get; set; }

public IdentitySearchResponses[] SearchResponses { get; set; }
public IdentitySearchResponses<TIdentityResponse>[] SearchResponses { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
namespace EdFi.Ods.Features.IdentityManagement.Models
// 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.

namespace EdFi.Ods.Features.IdentityManagement.Models
{
public class IdentitySearchResponses
public class IdentitySearchResponses<TIdentityResponse>
where TIdentityResponse : IdentityResponse
{
public IdentityResponse[] Responses { get; set; }
public TIdentityResponse[] Responses { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using EdFi.Ods.Common.Constants;
using EdFi.Ods.Common.Container;
using EdFi.Ods.Features.IdentityManagement;
using EdFi.Ods.Features.IdentityManagement.Models;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace EdFi.Ods.Features.Container.Modules
{
Expand All @@ -21,10 +21,13 @@ public IdentityModule(ApiSettings apiSettings)

public override void ApplyConfigurationSpecificRegistrations(ContainerBuilder builder)
{
builder.RegisterType<IdentitiesControllerOverrideConvention>()
.As<IApplicationModelConvention>()
.SingleInstance();

builder.RegisterType<UnimplementedIdentityService>()
.As<IIdentityService>()
.As<IIdentityServiceAsync>()
.SingleInstance();
.AsImplementedInterfaces()
.SingleInstance();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@ namespace EdFi.Ods.Features.Controllers
[Produces("application/json")]
[ApplyOdsRouteRootTemplate]
[Route($"{IdentityManagementConstants.IdentityRoutePrefix}/identities")]
public class IdentitiesController : ControllerBase
public abstract class IdentitiesControllerBase<TCreateRequest, TSearchRequest, TSearchResponse, TIdentityResponse> : ControllerBase
where TCreateRequest : IdentityCreateRequest
where TSearchRequest : IdentitySearchRequest
where TSearchResponse : IdentitySearchResponse<TIdentityResponse>
where TIdentityResponse : IdentityResponse
{
private const string InvalidServerResponse = "Invalid response from identity service: ";
private const string NoIdentitySystem = "There is no integrated Unique Identity System";
private readonly IIdentityService _identitySubsystem;
private readonly IIdentityServiceAsync _identitySubsystemAsync;

public IdentitiesController(IIdentityService identitySubsystem, IIdentityServiceAsync identitySubsystemAsync)
private readonly IIdentityService<TCreateRequest, TSearchRequest, TSearchResponse, TIdentityResponse> _identitySubsystem;
private readonly IIdentityServiceAsync<TSearchRequest, TSearchResponse, TIdentityResponse> _identitySubsystemAsync;

protected IdentitiesControllerBase(IIdentityService<TCreateRequest, TSearchRequest, TSearchResponse, TIdentityResponse> identitySubsystem,
IIdentityServiceAsync<TSearchRequest, TSearchResponse, TIdentityResponse> identitySubsystemAsync)
{
_identitySubsystem = identitySubsystem;
_identitySubsystemAsync = identitySubsystemAsync;
Expand Down Expand Up @@ -103,7 +109,7 @@ public async Task<IActionResult> GetById([FromRoute(Name = "id")] string uniqueI
/// <response code="501">The server does not support the requested function.</response>
/// <response code="502">The underlying identity system returned an error.</response>
[HttpPost]
public async Task<IActionResult> Create([FromBody] IdentityCreateRequest request)
public async Task<IActionResult> Create([FromBody] TCreateRequest request)
{
try
{
Expand Down Expand Up @@ -194,7 +200,7 @@ public async Task<IActionResult> Find([FromBody] string[] uniqueIds)
/// <response code="502">The underlying identity system returned an error.</response>
[HttpPost]
[Route("search")]
public async Task<IActionResult> Search([FromBody] IdentitySearchRequest[] criteria)
public async Task<IActionResult> Search([FromBody] TSearchRequest[] criteria)
{
try
{
Expand Down Expand Up @@ -294,4 +300,4 @@ public class ErrorResponse
public string IdentitySystemStatusCode { get; set; }
public IEnumerable<IdentityError> IdentitySystemErrors { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.Features.Controllers;
using EdFi.Ods.Features.IdentityManagement.Models;

namespace EdFi.Ods.Features.IdentityManagement;

/// <summary>
/// Closes the Identities base controller around the default request/response model types to enable ASP.NET to find
/// and instantiate it.
/// </summary>
/// <remarks>You can extend the default Identities request/response models by registering replacement services
/// with different (derived) model types, and then providing a new controller that derives from the <see cref="IdentitiesControllerBase{TCreateRequest,TSearchRequest,TSearchResponse,TIdentityResponse}"/>
/// class and closes the generic type definition so that ASP.NET will locate and instantiate it (instead of the
/// out-of-the-box <see cref="IdentitiesController" />.
/// </remarks>
public class IdentitiesController
: IdentitiesControllerBase<IdentityCreateRequest, IdentitySearchRequest, IdentitySearchResponse<IdentityResponse>,
IdentityResponse>
{
public IdentitiesController(
IIdentityServiceWithDefaultModels identitySubsystem,
IIdentityServiceWithDefaultModelsAsync identitySubsystemAsync)
: base(identitySubsystem, identitySubsystemAsync) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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.Linq;
using System.Reflection;
using EdFi.Ods.Common;
using EdFi.Ods.Features.Controllers;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace EdFi.Ods.Features.IdentityManagement;

/// <summary>
/// Implements a convention that looks for multiple controllers derived from the abstract <see cref="IdentitiesControllerBase{TCreateRequest, TSearchRequest, TSearchResponse, TIdentityResponse}" />
/// base controller class, and removes the <see cref="ControllerModel" /> entry for the default out-of-the-box <see cref="IdentitiesController" />
/// to prevent <see cref="AmbiguousMatchException" /> from occurring when resolving requests for identity management routes.
/// </summary>
public class IdentitiesControllerOverrideConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
// Find all Identities controllers (should only be either 1 or 2, if custom implementation provided)
var identitiesControllers = application.Controllers.Where(
m =>
{
// Eliminate most controllers using the namespace
if (!m.ControllerType.Namespace.StartsWith(Namespaces.Api.Controllers))
{
// Identities controllers (should) inherit from the IdentitiesControllerBase class that is generic
if (m.ControllerType.BaseType is { IsGenericType: true })
{
// Is it an identities controller?
if (m.ControllerType.BaseType.GetGenericTypeDefinition() == typeof(IdentitiesControllerBase<,,,>))
{
return true;
}
}
}

return false;
})
.ToArray();

// Determine if we will have an ambiguous match, and remove the UnimplementedIdentitiesController from the model
if (identitiesControllers.Length > 1)
{
foreach (var controllerModel in identitiesControllers)
{
if (controllerModel.ControllerType == typeof(IdentitiesController).GetTypeInfo())
{
application.Controllers.Remove(controllerModel);
}
}
}
}
}
Loading

0 comments on commit 18635d6

Please sign in to comment.