diff --git a/NP.Lti13Platform.AssignmentGradeServices/Configs/ServiceEndpointsConfig.cs b/NP.Lti13Platform.AssignmentGradeServices/Configs/ServiceEndpointsConfig.cs index 2b833e9..2a0ea8d 100644 --- a/NP.Lti13Platform.AssignmentGradeServices/Configs/ServiceEndpointsConfig.cs +++ b/NP.Lti13Platform.AssignmentGradeServices/Configs/ServiceEndpointsConfig.cs @@ -1,19 +1,18 @@ -namespace NP.Lti13Platform.AssignmentGradeServices.Configs +namespace NP.Lti13Platform.AssignmentGradeServices.Configs; + +public class ServiceEndpointsConfig { - public class ServiceEndpointsConfig - { - /// - /// Endpoint used to get a list of line items or create a new line item. - /// Must include route parameters for {deploymentId} and {contextId}. - /// - /// Default: /lti13/{deploymentId}/{contextId}/lineItems - public string LineItemsUrl { get; set; } = "/lti13/{deploymentId}/{contextId}/lineItems"; + /// + /// Endpoint used to get a list of line items or create a new line item. + /// Must include route parameters for {deploymentId} and {contextId}. + /// + /// Default: /lti13/{deploymentId}/{contextId}/lineItems + public string LineItemsUrl { get; set; } = "/lti13/{deploymentId}/{contextId}/lineItems"; - /// - /// Endpoint used to Get/Update/Delete a line item. Also used as the base url for getting results or posting scores. - /// Must include route parameters for {deploymentId}, {contextId} and {lineItemId}. - /// - /// Default:/lti13/{deploymentId}/{contextId}/lineItems/{lineItemId} - public string LineItemUrl { get; set; } = "/lti13/{deploymentId}/{contextId}/lineItems/{lineItemId}"; - } + /// + /// Endpoint used to Get/Update/Delete a line item. Also used as the base url for getting results or posting scores. + /// Must include route parameters for {deploymentId}, {contextId} and {lineItemId}. + /// + /// Default:/lti13/{deploymentId}/{contextId}/lineItems/{lineItemId} + public string LineItemUrl { get; set; } = "/lti13/{deploymentId}/{contextId}/lineItems/{lineItemId}"; } diff --git a/NP.Lti13Platform.AssignmentGradeServices/Constants.cs b/NP.Lti13Platform.AssignmentGradeServices/Constants.cs index 20ec48e..7f92ea9 100644 --- a/NP.Lti13Platform.AssignmentGradeServices/Constants.cs +++ b/NP.Lti13Platform.AssignmentGradeServices/Constants.cs @@ -1,25 +1,24 @@ -namespace NP.Lti13Platform.AssignmentGradeServices +namespace NP.Lti13Platform.AssignmentGradeServices; + +internal static class RouteNames { - internal static class RouteNames - { - internal const string GET_LINE_ITEMS = "GET_LINE_ITEMS"; - internal const string GET_LINE_ITEM = "GET_LINE_ITEM"; - internal const string GET_LINE_ITEM_RESULTS = "GET_LINE_ITEM_RESULTS"; - } + internal static readonly string GET_LINE_ITEMS = "GET_LINE_ITEMS"; + internal static readonly string GET_LINE_ITEM = "GET_LINE_ITEM"; + internal static readonly string GET_LINE_ITEM_RESULTS = "GET_LINE_ITEM_RESULTS"; +} - internal static class ContentTypes - { - internal const string LineItemContainer = "application/vnd.ims.lis.v2.lineitemcontainer+json"; - internal const string LineItem = "application/vnd.ims.lis.v2.lineitem+json"; - internal const string ResultContainer = "application/vnd.ims.lis.v2.resultcontainer+json"; - internal const string Score = "application/vnd.ims.lis.v1.score+json"; - } +internal static class ContentTypes +{ + internal static readonly string LineItemContainer = "application/vnd.ims.lis.v2.lineitemcontainer+json"; + internal static readonly string LineItem = "application/vnd.ims.lis.v2.lineitem+json"; + internal static readonly string ResultContainer = "application/vnd.ims.lis.v2.resultcontainer+json"; + internal static readonly string Score = "application/vnd.ims.lis.v1.score+json"; +} - public static class ServiceScopes - { - public const string LineItem = "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem"; - public const string LineItemReadOnly = "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly"; - public const string ResultReadOnly = "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly"; - public const string Score = "https://purl.imsglobal.org/spec/lti-ags/scope/score"; - } +public static class ServiceScopes +{ + public static readonly string LineItem = "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem"; + public static readonly string LineItemReadOnly = "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly"; + public static readonly string ResultReadOnly = "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly"; + public static readonly string Score = "https://purl.imsglobal.org/spec/lti-ags/scope/score"; } diff --git a/NP.Lti13Platform.AssignmentGradeServices/Populators/ServiceEndpointsPopulator.cs b/NP.Lti13Platform.AssignmentGradeServices/Populators/ServiceEndpointsPopulator.cs index f7b207c..be82009 100644 --- a/NP.Lti13Platform.AssignmentGradeServices/Populators/ServiceEndpointsPopulator.cs +++ b/NP.Lti13Platform.AssignmentGradeServices/Populators/ServiceEndpointsPopulator.cs @@ -5,56 +5,55 @@ using NP.Lti13Platform.Core.Services; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.AssignmentGradeServices.Populators +namespace NP.Lti13Platform.AssignmentGradeServices.Populators; + +public interface IServiceEndpoints { - public interface IServiceEndpoints - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti-ags/claim/endpoint")] - public LineItemServiceEndpoints? ServiceEndpoints { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti-ags/claim/endpoint")] + public LineItemServiceEndpoints? ServiceEndpoints { get; set; } - public class LineItemServiceEndpoints - { - [JsonPropertyName("scope")] - public required IEnumerable Scopes { get; set; } + public class LineItemServiceEndpoints + { + [JsonPropertyName("scope")] + public required IEnumerable Scopes { get; set; } - [JsonPropertyName("lineitems")] - public string? LineItemsUrl { get; set; } + [JsonPropertyName("lineitems")] + public string? LineItemsUrl { get; set; } - [JsonPropertyName("lineitem")] - public string? LineItemUrl { get; set; } - } + [JsonPropertyName("lineitem")] + public string? LineItemUrl { get; set; } } +} - public class ServiceEndpointsPopulator(LinkGenerator linkGenerator, ILti13CoreDataService dataService, ILti13AssignmentGradeConfigService assignmentGradeService) : Populator +public class ServiceEndpointsPopulator(LinkGenerator linkGenerator, ILti13CoreDataService dataService, ILti13AssignmentGradeConfigService assignmentGradeService) : Populator +{ + public override async Task PopulateAsync(IServiceEndpoints obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IServiceEndpoints obj, MessageScope scope, CancellationToken cancellationToken = default) + var lineItemScopes = scope.Tool.ServiceScopes + .Intersect([ServiceScopes.LineItem, ServiceScopes.LineItemReadOnly, ServiceScopes.ResultReadOnly, ServiceScopes.Score]) + .ToList(); + + if (lineItemScopes.Count > 0 && scope.Context != null) { - var lineItemScopes = scope.Tool.ServiceScopes - .Intersect([ServiceScopes.LineItem, ServiceScopes.LineItemReadOnly, ServiceScopes.ResultReadOnly, ServiceScopes.Score]) - .ToList(); + string? lineItemId = null; - if (lineItemScopes.Count > 0 && scope.Context != null) + if (scope.ResourceLink != null) { - string? lineItemId = null; - - if (scope.ResourceLink != null) + var lineItems = await dataService.GetLineItemsAsync(scope.Deployment.Id, scope.Context.Id, 0, 1, null, scope.ResourceLink.Id, null, cancellationToken); + if (lineItems.TotalItems == 1) { - var lineItems = await dataService.GetLineItemsAsync(scope.Deployment.Id, scope.Context.Id, 0, 1, null, scope.ResourceLink.Id, null, cancellationToken); - if (lineItems.TotalItems == 1) - { - lineItemId = lineItems.Items.FirstOrDefault()?.Id; - } + lineItemId = lineItems.Items.FirstOrDefault()?.Id; } + } - var config = await assignmentGradeService.GetConfigAsync(scope.Tool.ClientId, cancellationToken); + var config = await assignmentGradeService.GetConfigAsync(scope.Tool.ClientId, cancellationToken); - obj.ServiceEndpoints = new IServiceEndpoints.LineItemServiceEndpoints - { - Scopes = lineItemScopes, - LineItemsUrl = linkGenerator.GetUriByName(RouteNames.GET_LINE_ITEMS, new { deploymentId = scope.Deployment.Id, contextId = scope.Context.Id }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)), - LineItemUrl = string.IsNullOrWhiteSpace(lineItemId) ? null : linkGenerator.GetUriByName(RouteNames.GET_LINE_ITEM, new { deploymentId = scope.Deployment.Id, contextId = scope.Context.Id, lineItemId }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)), - }; - } + obj.ServiceEndpoints = new IServiceEndpoints.LineItemServiceEndpoints + { + Scopes = lineItemScopes, + LineItemsUrl = linkGenerator.GetUriByName(RouteNames.GET_LINE_ITEMS, new { deploymentId = scope.Deployment.Id, contextId = scope.Context.Id }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)), + LineItemUrl = string.IsNullOrWhiteSpace(lineItemId) ? null : linkGenerator.GetUriByName(RouteNames.GET_LINE_ITEM, new { deploymentId = scope.Deployment.Id, contextId = scope.Context.Id, lineItemId }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)), + }; } } } diff --git a/NP.Lti13Platform.AssignmentGradeServices/Services/DefaultAssignmentGradeConfigService.cs b/NP.Lti13Platform.AssignmentGradeServices/Services/DefaultAssignmentGradeConfigService.cs index 24752ee..72cc069 100644 --- a/NP.Lti13Platform.AssignmentGradeServices/Services/DefaultAssignmentGradeConfigService.cs +++ b/NP.Lti13Platform.AssignmentGradeServices/Services/DefaultAssignmentGradeConfigService.cs @@ -2,19 +2,18 @@ using Microsoft.Extensions.Options; using NP.Lti13Platform.AssignmentGradeServices.Configs; -namespace NP.Lti13Platform.AssignmentGradeServices.Services +namespace NP.Lti13Platform.AssignmentGradeServices.Services; + +internal class DefaultAssignmentGradeConfigService(IOptionsMonitor config, IHttpContextAccessor httpContextAccessor) : ILti13AssignmentGradeConfigService { - internal class DefaultAssignmentGradeConfigService(IOptionsMonitor config, IHttpContextAccessor httpContextAccessor) : ILti13AssignmentGradeConfigService + public async Task GetConfigAsync(string clientId, CancellationToken cancellationToken = default) { - public async Task GetConfigAsync(string clientId, CancellationToken cancellationToken = default) + var servicesConfig = config.CurrentValue; + if (servicesConfig.ServiceAddress == ServicesConfig.DefaultUri) { - var servicesConfig = config.CurrentValue; - if (servicesConfig.ServiceAddress == ServicesConfig.DefaultUri) - { - servicesConfig = servicesConfig with { ServiceAddress = new UriBuilder(httpContextAccessor.HttpContext?.Request.Scheme, httpContextAccessor.HttpContext?.Request.Host.Value).Uri }; - } - - return await Task.FromResult(servicesConfig); + servicesConfig = servicesConfig with { ServiceAddress = new UriBuilder(httpContextAccessor.HttpContext?.Request.Scheme, httpContextAccessor.HttpContext?.Request.Host.Value).Uri }; } + + return await Task.FromResult(servicesConfig); } } \ No newline at end of file diff --git a/NP.Lti13Platform.AssignmentGradeServices/Startup.cs b/NP.Lti13Platform.AssignmentGradeServices/Startup.cs index 668b71a..6608237 100644 --- a/NP.Lti13Platform.AssignmentGradeServices/Startup.cs +++ b/NP.Lti13Platform.AssignmentGradeServices/Startup.cs @@ -16,583 +16,549 @@ using System.Net.Http.Headers; using System.Security.Claims; -namespace NP.Lti13Platform.AssignmentGradeServices +namespace NP.Lti13Platform.AssignmentGradeServices; + +public static class Startup { - public static class Startup + public static Lti13PlatformBuilder AddLti13PlatformAssignmentGradeServices(this Lti13PlatformBuilder builder) { - public static Lti13PlatformBuilder AddLti13PlatformAssignmentGradeServices(this Lti13PlatformBuilder builder) - { - builder.ExtendLti13Message(); - - builder.Services.AddOptions().BindConfiguration("Lti13Platform:AssignmentGradeServices"); - builder.Services.TryAddSingleton(); - - return builder; - } - - public static Lti13PlatformBuilder WithLti13AssignmentGradeDataService(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : ILti13AssignmentGradeDataService - { - builder.Services.Add(new ServiceDescriptor(typeof(ILti13AssignmentGradeDataService), typeof(T), serviceLifetime)); - return builder; - } - - public static Lti13PlatformBuilder WithLti13AssignmentGradeConfigService(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : ILti13AssignmentGradeConfigService - { - builder.Services.Add(new ServiceDescriptor(typeof(ILti13AssignmentGradeConfigService), typeof(T), serviceLifetime)); - return builder; - } - - public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this IEndpointRouteBuilder app, Func? configure = null) - { - ServiceEndpointsConfig config = new(); - config = configure?.Invoke(config) ?? config; - - app.MapGet(config.LineItemsUrl, - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string? resource_id, string? resource_link_id, string? tag, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) => - { - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; - - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + builder.ExtendLti13Message(); - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) - { - return Results.NotFound(); - } - - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) - { - return Results.NotFound(); - } - - var lineItemsResponse = await coreDataService.GetLineItemsAsync(deploymentId, contextId, pageIndex, limit ?? int.MaxValue, resource_id, resource_link_id, tag, cancellationToken); + builder.Services.AddOptions().BindConfiguration("Lti13Platform:AssignmentGradeServices"); + builder.Services.TryAddSingleton(); - if (lineItemsResponse.TotalItems > 0 && limit.HasValue) - { - var links = new Collection(); - if (pageIndex > 0) - { - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = pageIndex - 1 })}>; rel=\"prev\""); - } + return builder; + } - if (lineItemsResponse.TotalItems > limit * (pageIndex + 1)) - { - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = pageIndex + 1 })}>; rel=\"next\""); - } + public static Lti13PlatformBuilder WithLti13AssignmentGradeDataService(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : ILti13AssignmentGradeDataService + { + builder.Services.Add(new ServiceDescriptor(typeof(ILti13AssignmentGradeDataService), typeof(T), serviceLifetime)); + return builder; + } - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = 0 })}>; rel=\"first\""); + public static Lti13PlatformBuilder WithLti13AssignmentGradeConfigService(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : ILti13AssignmentGradeConfigService + { + builder.Services.Add(new ServiceDescriptor(typeof(ILti13AssignmentGradeConfigService), typeof(T), serviceLifetime)); + return builder; + } - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = Math.Ceiling(lineItemsResponse.TotalItems * 1.0 / limit.GetValueOrDefault()) - 1 })}>; rel=\"last\""); + public static IEndpointRouteBuilder UseLti13PlatformAssignmentGradeServices(this IEndpointRouteBuilder app, Func? configure = null) + { + ServiceEndpointsConfig config = new(); + config = configure?.Invoke(config) ?? config; - httpContext.Response.Headers.Link = new StringValues([.. links]); - } + app.MapGet(config.LineItemsUrl, + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string? resource_id, string? resource_link_id, string? tag, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; - return Results.Json(lineItemsResponse.Items.Select(i => new - { - Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId = i.Id }), - i.StartDateTime, - i.EndDateTime, - i.ScoreMaximum, - i.Label, - i.Tag, - i.ResourceId, - i.ResourceLinkId - }), contentType: ContentTypes.LineItemContainer); - }) - .WithName(RouteNames.GET_LINE_ITEMS) - .RequireAuthorization(policy => - { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.LineItem, ServiceScopes.LineItemReadOnly); - }); + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) + { + return Results.NotFound(); + } - app.MapPost(config.LineItemsUrl, - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, LineItemRequest request, CancellationToken cancellationToken) => + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) { - const string INVALID_CONTENT_TYPE = "Invalid Content-Type"; - const string CONTENT_TYPE_REQUIRED = "Content-Type must be 'application/vnd.ims.lis.v2.lineitem+json'"; - const string CONTENT_TYPE_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#creating-a-new-line-item"; - const string INVALID_LABEL = "Invalid Label"; - const string LABEL_REQUIRED = "Label is reuired"; - const string LABEL_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#label"; - const string INVALID_SCORE_MAXIMUM = "Invalid ScoreMaximum"; - const string SCORE_MAXIMUM_REQUIRED = "ScoreMaximum must be greater than 0"; - const string SCORE_MAXIUMUM_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#scoremaximum"; + return Results.NotFound(); + } - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) + { + return Results.NotFound(); + } - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + var lineItemsResponse = await coreDataService.GetLineItemsAsync(deploymentId, contextId, pageIndex, limit ?? int.MaxValue, resource_id, resource_link_id, tag, cancellationToken); - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) + if (lineItemsResponse.TotalItems > 0 && limit.HasValue) + { + var links = new Collection(); + if (pageIndex > 0) { - return Results.NotFound(); + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = pageIndex - 1 })}>; rel=\"prev\""); } - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) + if (lineItemsResponse.TotalItems > limit * (pageIndex + 1)) { - return Results.NotFound(); + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = pageIndex + 1 })}>; rel=\"next\""); } - if (!MediaTypeHeaderValue.TryParse(httpContext.Request.ContentType, out var headerValue) || headerValue.MediaType != ContentTypes.LineItem) - { - return Results.BadRequest(new { Error = INVALID_CONTENT_TYPE, Error_Description = CONTENT_TYPE_REQUIRED, Error_Uri = CONTENT_TYPE_SPEC_URI }); - } + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = 0 })}>; rel=\"first\""); - if (string.IsNullOrWhiteSpace(request.Label)) - { - return Results.BadRequest(new { Error = INVALID_LABEL, Error_Description = LABEL_REQUIRED, Error_Uri = LABEL_SPEC_URI }); - } + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEMS, new { deploymentId, contextId, resource_id, resource_link_id, tag, limit, pageIndex = Math.Ceiling(lineItemsResponse.TotalItems * 1.0 / limit.GetValueOrDefault()) - 1 })}>; rel=\"last\""); - if (request.ScoreMaximum <= 0) - { - return Results.BadRequest(new { Error = INVALID_SCORE_MAXIMUM, Error_Description = SCORE_MAXIMUM_REQUIRED, Error_Uri = SCORE_MAXIUMUM_SPEC_URI }); - } + httpContext.Response.Headers.Link = new StringValues([.. links]); + } - if (!string.IsNullOrWhiteSpace(request.ResourceLinkId)) - { - var resourceLink = await coreDataService.GetResourceLinkAsync(request.ResourceLinkId, cancellationToken); - if (resourceLink?.DeploymentId != deploymentId || resourceLink.ContextId != contextId) - { - return Results.NotFound(); - } - } + return Results.Json(lineItemsResponse.Items.Select(i => new + { + Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId = i.Id }), + i.StartDateTime, + i.EndDateTime, + i.ScoreMaximum, + i.Label, + i.Tag, + i.ResourceId, + i.ResourceLinkId + }), contentType: ContentTypes.LineItemContainer); + }) + .WithName(RouteNames.GET_LINE_ITEMS) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.LineItem, ServiceScopes.LineItemReadOnly); + }); + + app.MapPost(config.LineItemsUrl, + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, LineItemRequest request, CancellationToken cancellationToken) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) + { + return Results.NotFound(); + } - var lineItemId = await assignmentGradeDataService.SaveLineItemAsync(new LineItem - { - Id = string.Empty, - DeploymentId = deploymentId, - ContextId = contextId, - Label = request.Label, - ResourceId = request.ResourceId, - ResourceLinkId = request.ResourceLinkId, - ScoreMaximum = request.ScoreMaximum, - Tag = request.Tag, - GradesReleased = request.GradesReleased, - StartDateTime = request.StartDateTime?.UtcDateTime, - EndDateTime = request.EndDateTime?.UtcDateTime, - }, cancellationToken); - - var url = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId }); - return Results.Created(url, new - { - Id = url, - request.Label, - request.ResourceId, - request.ResourceLinkId, - request.ScoreMaximum, - request.Tag, - request.GradesReleased, - request.StartDateTime, - request.EndDateTime, - }); - }) - .RequireAuthorization(policy => + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.LineItem); - }) - .DisableAntiforgery(); + return Results.NotFound(); + } - app.MapGet(config.LineItemUrl, - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) => + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) { - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + return Results.NotFound(); + } - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + if (!MediaTypeHeaderValue.TryParse(httpContext.Request.ContentType, out var headerValue) || headerValue.MediaType != ContentTypes.LineItem) + { + return Results.BadRequest(new { Error = "Invalid Content-Type", Error_Description = $"Content-Type must be '{ContentTypes.LineItem}'", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#creating-a-new-line-item" }); + } - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) - { - return Results.NotFound(); - } + if (string.IsNullOrWhiteSpace(request.Label)) + { + return Results.BadRequest(new { Error = "Invalid Label", Error_Description = "Label is reuired", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#label" }); + } - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) - { - return Results.NotFound(); - } + if (request.ScoreMaximum <= 0) + { + return Results.BadRequest(new { Error = "Invalid ScoreMaximum", Error_Description = "ScoreMaximum must be greater than 0", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#scoremaximum" }); + } - var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); - if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + if (!string.IsNullOrWhiteSpace(request.ResourceLinkId)) + { + var resourceLink = await coreDataService.GetResourceLinkAsync(request.ResourceLinkId, cancellationToken); + if (resourceLink?.DeploymentId != deploymentId || resourceLink.ContextId != contextId) { return Results.NotFound(); } + } - return Results.Json(new - { - Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId }), - lineItem.Label, - lineItem.ResourceId, - lineItem.ResourceLinkId, - lineItem.ScoreMaximum, - lineItem.Tag, - lineItem.StartDateTime, - lineItem.EndDateTime, - }, contentType: ContentTypes.LineItem); - }) - .WithName(RouteNames.GET_LINE_ITEM) - .RequireAuthorization(policy => - { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.LineItem, ServiceScopes.LineItemReadOnly); + var lineItemId = await assignmentGradeDataService.SaveLineItemAsync(new LineItem + { + Id = string.Empty, + DeploymentId = deploymentId, + ContextId = contextId, + Label = request.Label, + ResourceId = request.ResourceId, + ResourceLinkId = request.ResourceLinkId, + ScoreMaximum = request.ScoreMaximum, + Tag = request.Tag, + GradesReleased = request.GradesReleased, + StartDateTime = request.StartDateTime?.UtcDateTime, + EndDateTime = request.EndDateTime?.UtcDateTime, + }, cancellationToken); + + var url = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId }); + return Results.Created(url, new + { + Id = url, + request.Label, + request.ResourceId, + request.ResourceLinkId, + request.ScoreMaximum, + request.Tag, + request.GradesReleased, + request.StartDateTime, + request.EndDateTime, }); + }) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.LineItem); + }) + .DisableAntiforgery(); + + app.MapGet(config.LineItemUrl, + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) + { + return Results.NotFound(); + } - app.MapPut(config.LineItemUrl, - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, LineItemRequest request, CancellationToken cancellationToken) => - { - const string INVALID_CONTENT_TYPE = "Invalid Content-Type"; - const string CONTENT_TYPE_REQUIRED = "Content-Type must be 'application/vnd.ims.lis.v2.lineitem+json'"; - const string CONTENT_TYPE_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#creating-a-new-line-item"; - const string INVALID_LABEL = "Invalid Label"; - const string LABEL_REQUIRED = "Label is reuired"; - const string LABEL_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#label"; - const string INVALID_SCORE_MAXIMUM = "Invalid ScoreMaximum"; - const string SCORE_MAXIMUM_REQUIRED = "ScoreMaximum must be greater than 0"; - const string SCORE_MAXIUMUM_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#scoremaximum"; - const string INVALID_RESOURCE_LINK_ID = "Invalid ResourceLinkId"; - const string RESOURCE_LINK_ID_MODIFIED = "ResourceLinkId may not change after creation"; - const string LINE_ITEM_UPDATE_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#updating-a-line-item"; - - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; - - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) + { + return Results.NotFound(); + } - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) - { - return Results.NotFound(); - } + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) + { + return Results.NotFound(); + } - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) - { - return Results.NotFound(); - } + var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); + if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + { + return Results.NotFound(); + } - var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); - if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) - { - return Results.NotFound(); - } + return Results.Json(new + { + Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId }), + lineItem.Label, + lineItem.ResourceId, + lineItem.ResourceLinkId, + lineItem.ScoreMaximum, + lineItem.Tag, + lineItem.StartDateTime, + lineItem.EndDateTime, + }, contentType: ContentTypes.LineItem); + }) + .WithName(RouteNames.GET_LINE_ITEM) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.LineItem, ServiceScopes.LineItemReadOnly); + }); + + app.MapPut(config.LineItemUrl, + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, LineItemRequest request, CancellationToken cancellationToken) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) + { + return Results.NotFound(); + } - if (!MediaTypeHeaderValue.TryParse(httpContext.Request.ContentType, out var headerValue) || headerValue.MediaType != ContentTypes.LineItem) - { - return Results.BadRequest(new { Error = INVALID_CONTENT_TYPE, Error_Description = CONTENT_TYPE_REQUIRED, Error_Uri = CONTENT_TYPE_SPEC_URI }); - } + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) + { + return Results.NotFound(); + } - if (string.IsNullOrWhiteSpace(request.Label)) - { - return Results.BadRequest(new { Error = INVALID_LABEL, Error_Description = LABEL_REQUIRED, Error_Uri = LABEL_SPEC_URI }); - } + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) + { + return Results.NotFound(); + } - if (request.ScoreMaximum <= 0) - { - return Results.BadRequest(new { Error = INVALID_SCORE_MAXIMUM, Error_Description = SCORE_MAXIMUM_REQUIRED, Error_Uri = SCORE_MAXIUMUM_SPEC_URI }); - } + var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); + if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + { + return Results.NotFound(); + } - if (!string.IsNullOrWhiteSpace(request.ResourceLinkId) && request.ResourceLinkId != lineItem.ResourceLinkId) - { - return Results.BadRequest(new { Error = INVALID_RESOURCE_LINK_ID, Error_Description = RESOURCE_LINK_ID_MODIFIED, Error_Uri = LINE_ITEM_UPDATE_SPEC_URI }); - } + if (!MediaTypeHeaderValue.TryParse(httpContext.Request.ContentType, out var headerValue) || headerValue.MediaType != ContentTypes.LineItem) + { + return Results.BadRequest(new { Error = "Invalid Content-Type", Error_Description = $"Content-Type must be '{ContentTypes.LineItem}'", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#creating-a-new-line-item" }); + } - lineItem.Label = request.Label; - lineItem.ResourceId = request.ResourceId; - lineItem.ResourceLinkId = request.ResourceLinkId; - lineItem.ScoreMaximum = request.ScoreMaximum; - lineItem.Tag = request.Tag; - lineItem.GradesReleased = request.GradesReleased; - lineItem.StartDateTime = request.StartDateTime?.UtcDateTime; - lineItem.EndDateTime = request.EndDateTime?.UtcDateTime; + if (string.IsNullOrWhiteSpace(request.Label)) + { + return Results.BadRequest(new { Error = "Invalid Label", Error_Description = "Label is reuired", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#label" }); + } - await assignmentGradeDataService.SaveLineItemAsync(lineItem, cancellationToken); + if (request.ScoreMaximum <= 0) + { + return Results.BadRequest(new { Error = "Invalid ScoreMaximum", Error_Description = "ScoreMaximum must be greater than 0", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#scoremaximum" }); + } - return Results.Json(new - { - Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId }), - lineItem.Label, - lineItem.ResourceId, - lineItem.ResourceLinkId, - lineItem.ScoreMaximum, - lineItem.Tag, - lineItem.GradesReleased, - lineItem.StartDateTime, - lineItem.EndDateTime, - }, contentType: ContentTypes.LineItem); - }) - .RequireAuthorization(policy => - { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.LineItem); - }) - .DisableAntiforgery(); - - app.MapDelete(config.LineItemUrl, - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) => - { - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; - - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + if (!string.IsNullOrWhiteSpace(request.ResourceLinkId) && request.ResourceLinkId != lineItem.ResourceLinkId) + { + return Results.BadRequest(new { Error = "Invalid ResourceLinkId", Error_Description = "ResourceLinkId may not change after creation", Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#updating-a-line-item" }); + } - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) - { - return Results.NotFound(); - } + lineItem.Label = request.Label; + lineItem.ResourceId = request.ResourceId; + lineItem.ResourceLinkId = request.ResourceLinkId; + lineItem.ScoreMaximum = request.ScoreMaximum; + lineItem.Tag = request.Tag; + lineItem.GradesReleased = request.GradesReleased; + lineItem.StartDateTime = request.StartDateTime?.UtcDateTime; + lineItem.EndDateTime = request.EndDateTime?.UtcDateTime; - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) - { - return Results.NotFound(); - } + await assignmentGradeDataService.SaveLineItemAsync(lineItem, cancellationToken); - var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); - if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) - { - return Results.NotFound(); - } + return Results.Json(new + { + Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, lineItemId }), + lineItem.Label, + lineItem.ResourceId, + lineItem.ResourceLinkId, + lineItem.ScoreMaximum, + lineItem.Tag, + lineItem.GradesReleased, + lineItem.StartDateTime, + lineItem.EndDateTime, + }, contentType: ContentTypes.LineItem); + }) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.LineItem); + }) + .DisableAntiforgery(); + + app.MapDelete(config.LineItemUrl, + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, CancellationToken cancellationToken) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) + { + return Results.NotFound(); + } - await assignmentGradeDataService.DeleteLineItemAsync(lineItemId, cancellationToken); + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) + { + return Results.NotFound(); + } - return Results.NoContent(); - }) - .RequireAuthorization(policy => + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.LineItem); - }) - .DisableAntiforgery(); + return Results.NotFound(); + } - app.MapGet($"{config.LineItemUrl}/results", - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, string? user_id, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) => + var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); + if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + { + return Results.NotFound(); + } + + await assignmentGradeDataService.DeleteLineItemAsync(lineItemId, cancellationToken); + + return Results.NoContent(); + }) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.LineItem); + }) + .DisableAntiforgery(); + + app.MapGet($"{config.LineItemUrl}/results", + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string lineItemId, string? user_id, int? limit, int pageIndex = 0, CancellationToken cancellationToken = default) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) { - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + return Results.NotFound(); + } - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) + { + return Results.NotFound(); + } - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) - { - return Results.NotFound(); - } + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) + { + return Results.NotFound(); + } - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) - { - return Results.NotFound(); - } + var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); + if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + { + return Results.NotFound(); + } + + var gradesResponse = await assignmentGradeDataService.GetGradesAsync(lineItemId, pageIndex, limit ?? int.MaxValue, user_id, cancellationToken); - var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); - if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + if (gradesResponse.TotalItems > 0 && limit.HasValue) + { + var links = new Collection(); + if (pageIndex > 0) { - return Results.NotFound(); + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = pageIndex - 1 })}>; rel=\"prev\""); } - var gradesResponse = await assignmentGradeDataService.GetGradesAsync(lineItemId, pageIndex, limit ?? int.MaxValue, user_id, cancellationToken); - - if (gradesResponse.TotalItems > 0 && limit.HasValue) + if (gradesResponse.TotalItems > limit * (pageIndex + 1)) { - var links = new Collection(); - if (pageIndex > 0) - { - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = pageIndex - 1 })}>; rel=\"prev\""); - } - - if (gradesResponse.TotalItems > limit * (pageIndex + 1)) - { - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = pageIndex + 1 })}>; rel=\"next\""); - } - - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = 0 })}>; rel=\"first\""); - - links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = Math.Ceiling(gradesResponse.TotalItems * 1.0 / limit.GetValueOrDefault()) - 1 })}>; rel=\"last\""); - - httpContext.Response.Headers.Link = new StringValues([.. links]); + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = pageIndex + 1 })}>; rel=\"next\""); } - return Results.Json(gradesResponse.Items.Select(i => new - { - Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, i.LineItemId, user_id = i.UserId }), - ScoreOf = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, i.LineItemId }), - i.UserId, - i.ResultScore, - ResultMaximum = i.ResultMaximum ?? 1, // https://www.imsglobal.org/spec/lti-ags/v2p0/#resultmaximum - i.ScoringUserId, - i.Comment - }), contentType: ContentTypes.ResultContainer); - }) - .WithName(RouteNames.GET_LINE_ITEM_RESULTS) - .RequireAuthorization(policy => - { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.ResultReadOnly); - }); + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = 0 })}>; rel=\"first\""); - app.MapPost($"{config.LineItemUrl}/scores", - async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, ScoreRequest request, CancellationToken cancellationToken) => - { - const string RESULT_TOO_EARLY = "startDateTime"; - const string RESULT_TOO_EARLY_DESCRIPTION = "lineItem startDateTime is in the future"; - const string RESULT_TOO_EARLY_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#startdatetime"; - const string RESULT_TOO_LATE = "endDateTime"; - const string RESULT_TOO_LATE_DESCRIPTION = "lineItem endDateTime is in the past"; - const string RESULT_TOO_LATE_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#enddatetime"; - const string OUT_OF_DATE = "timestamp"; - const string OUT_OF_DATE_DESCRIPTION = "timestamp must be after the current timestamp"; - const string OUT_OF_DATE_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0/#timestamp"; + links.Add($"<{linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, lineItemId, limit, pageIndex = Math.Ceiling(gradesResponse.TotalItems * 1.0 / limit.GetValueOrDefault()) - 1 })}>; rel=\"last\""); - var httpContext = httpContextAccessor.HttpContext!; - var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + httpContext.Response.Headers.Link = new StringValues([.. links]); + } - var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); - if (tool == null) - { - return Results.NotFound(); - } + return Results.Json(gradesResponse.Items.Select(i => new + { + Id = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM_RESULTS, new { deploymentId, contextId, i.LineItemId, user_id = i.UserId }), + ScoreOf = linkGenerator.GetUriByName(httpContext, RouteNames.GET_LINE_ITEM, new { deploymentId, contextId, i.LineItemId }), + i.UserId, + i.ResultScore, + ResultMaximum = i.ResultMaximum ?? 1, // https://www.imsglobal.org/spec/lti-ags/v2p0/#resultmaximum + i.ScoringUserId, + i.Comment + }), contentType: ContentTypes.ResultContainer); + }) + .WithName(RouteNames.GET_LINE_ITEM_RESULTS) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.ResultReadOnly); + }); + + app.MapPost($"{config.LineItemUrl}/scores", + async (IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13AssignmentGradeDataService assignmentGradeDataService, string deploymentId, string contextId, string lineItemId, ScoreRequest request, CancellationToken cancellationToken) => + { + var httpContext = httpContextAccessor.HttpContext!; + var clientId = httpContext.User.FindFirstValue(JwtRegisteredClaimNames.Sub)!; + + var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); + if (tool == null) + { + return Results.NotFound(); + } - var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); - if (deployment?.ToolId != tool.Id) - { - return Results.NotFound(); - } + var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); + if (deployment?.ToolId != tool.Id) + { + return Results.NotFound(); + } - var context = await coreDataService.GetContextAsync(contextId, cancellationToken); - if (context == null) - { - return Results.NotFound(); - } + var context = await coreDataService.GetContextAsync(contextId, cancellationToken); + if (context == null) + { + return Results.NotFound(); + } - var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); - if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) - { - return Results.NotFound(); - } + var lineItem = await assignmentGradeDataService.GetLineItemAsync(lineItemId, cancellationToken); + if (lineItem?.DeploymentId != deploymentId || lineItem.ContextId != contextId) + { + return Results.NotFound(); + } - if (DateTime.UtcNow < lineItem.StartDateTime) + if (DateTime.UtcNow < lineItem.StartDateTime) + { + return Results.Json(new { - return Results.Json(new - { - Error = RESULT_TOO_EARLY, - Error_Description = RESULT_TOO_EARLY_DESCRIPTION, - Error_Uri = RESULT_TOO_EARLY_URI - }, statusCode: (int)HttpStatusCode.Forbidden); - } + Error = "startDateTime", + Error_Description = "lineItem startDateTime is in the future", + Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#startdatetime" + }, statusCode: (int)HttpStatusCode.Forbidden); + } - if (DateTime.UtcNow > lineItem.EndDateTime) + if (DateTime.UtcNow > lineItem.EndDateTime) + { + return Results.Json(new { - return Results.Json(new - { - Error = RESULT_TOO_LATE, - Error_Description = RESULT_TOO_LATE_DESCRIPTION, - Error_Uri = RESULT_TOO_LATE_URI - }, statusCode: (int)HttpStatusCode.Forbidden); - } + Error = "endDateTime", + Error_Description = "lineItem endDateTime is in the past", + Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#enddatetime" + }, statusCode: (int)HttpStatusCode.Forbidden); + } - var isNew = false; - var grade = await coreDataService.GetGradeAsync(lineItemId, request.UserId, cancellationToken); - if (grade == null) - { - isNew = true; - grade = new Grade - { - LineItemId = lineItemId, - UserId = request.UserId - }; - } - else if (grade.Timestamp >= request.TimeStamp) + var isNew = false; + var grade = await coreDataService.GetGradeAsync(lineItemId, request.UserId, cancellationToken); + if (grade == null) + { + isNew = true; + grade = new Grade + { + LineItemId = lineItemId, + UserId = request.UserId + }; + } + else if (grade.Timestamp >= request.TimeStamp) + { + return Results.Conflict(new { - return Results.Conflict(new - { - Error = OUT_OF_DATE, - Error_Description = OUT_OF_DATE_DESCRIPTION, - Error_Uri = OUT_OF_DATE_URI - }); - } + Error = "timestamp", + Error_Description = "timestamp must be after the current timestamp", + Error_Uri = "https://www.imsglobal.org/spec/lti-ags/v2p0/#timestamp" + }); + } - grade.ResultScore = request.ScoreGiven; - grade.ResultMaximum = request.ScoreMaximum; - grade.Comment = request.Comment; - grade.ScoringUserId = request.ScoringUserId; - grade.Timestamp = request.TimeStamp.UtcDateTime; - grade.ActivityProgress = Enum.Parse(request.ActivityProgress); - grade.GradingProgress = Enum.Parse(request.GradingProgress); + grade.ResultScore = request.ScoreGiven; + grade.ResultMaximum = request.ScoreMaximum; + grade.Comment = request.Comment; + grade.ScoringUserId = request.ScoringUserId; + grade.Timestamp = request.TimeStamp.UtcDateTime; + grade.ActivityProgress = Enum.Parse(request.ActivityProgress); + grade.GradingProgress = Enum.Parse(request.GradingProgress); - if (request.Submission?.StartedAt != null) - { - grade.StartedAt = request.Submission.StartedAt?.UtcDateTime; - } - else if (grade.ActivityProgress == ActivityProgress.Initialized) - { - grade.StartedAt = null; - } - else if (grade.StartedAt == null && (grade.ActivityProgress == ActivityProgress.Started || grade.ActivityProgress == ActivityProgress.InProgress)) - { - grade.StartedAt = DateTime.UtcNow; - } + if (request.Submission?.StartedAt != null) + { + grade.StartedAt = request.Submission.StartedAt?.UtcDateTime; + } + else if (grade.ActivityProgress == ActivityProgress.Initialized) + { + grade.StartedAt = null; + } + else if (grade.StartedAt == null && (grade.ActivityProgress == ActivityProgress.Started || grade.ActivityProgress == ActivityProgress.InProgress)) + { + grade.StartedAt = DateTime.UtcNow; + } - if (request.Submission?.SubmittedAt != null) - { - grade.SubmittedAt = request.Submission.SubmittedAt?.UtcDateTime; - } - else if (grade.ActivityProgress == ActivityProgress.Initialized || grade.ActivityProgress == ActivityProgress.Started || grade.ActivityProgress == ActivityProgress.InProgress) - { - grade.SubmittedAt = null; - } - else if (grade.SubmittedAt == null && (grade.ActivityProgress == ActivityProgress.Submitted || grade.ActivityProgress == ActivityProgress.Completed)) - { - grade.SubmittedAt = DateTime.UtcNow; - } + if (request.Submission?.SubmittedAt != null) + { + grade.SubmittedAt = request.Submission.SubmittedAt?.UtcDateTime; + } + else if (grade.ActivityProgress == ActivityProgress.Initialized || grade.ActivityProgress == ActivityProgress.Started || grade.ActivityProgress == ActivityProgress.InProgress) + { + grade.SubmittedAt = null; + } + else if (grade.SubmittedAt == null && (grade.ActivityProgress == ActivityProgress.Submitted || grade.ActivityProgress == ActivityProgress.Completed)) + { + grade.SubmittedAt = DateTime.UtcNow; + } - await assignmentGradeDataService.SaveGradeAsync(grade, cancellationToken); + await assignmentGradeDataService.SaveGradeAsync(grade, cancellationToken); - return isNew ? Results.Created() : Results.NoContent(); - }) - .RequireAuthorization(policy => - { - policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); - policy.RequireRole(ServiceScopes.Score); - }) - .DisableAntiforgery(); + return isNew ? Results.Created() : Results.NoContent(); + }) + .RequireAuthorization(policy => + { + policy.AddAuthenticationSchemes(LtiServicesAuthHandler.SchemeName); + policy.RequireRole(ServiceScopes.Score); + }) + .DisableAntiforgery(); - return app; - } + return app; } +} - internal record LineItemRequest(decimal ScoreMaximum, string Label, string? ResourceLinkId, string? ResourceId, string? Tag, bool? GradesReleased, DateTimeOffset? StartDateTime, DateTimeOffset? EndDateTime); +internal record LineItemRequest(decimal ScoreMaximum, string Label, string? ResourceLinkId, string? ResourceId, string? Tag, bool? GradesReleased, DateTimeOffset? StartDateTime, DateTimeOffset? EndDateTime); - internal record ScoreRequest(string UserId, string ScoringUserId, decimal? ScoreGiven, decimal? ScoreMaximum, string Comment, ScoreSubmissionRequest? Submission, DateTimeOffset TimeStamp, string ActivityProgress, string GradingProgress); +internal record ScoreRequest(string UserId, string ScoringUserId, decimal? ScoreGiven, decimal? ScoreMaximum, string Comment, ScoreSubmissionRequest? Submission, DateTimeOffset TimeStamp, string ActivityProgress, string GradingProgress); - internal record ScoreSubmissionRequest(DateTimeOffset? StartedAt, DateTimeOffset? SubmittedAt); -} +internal record ScoreSubmissionRequest(DateTimeOffset? StartedAt, DateTimeOffset? SubmittedAt); diff --git a/NP.Lti13Platform.Core/Configs/Lti13PlatformCoreConfig.cs b/NP.Lti13Platform.Core/Configs/Lti13PlatformCoreConfig.cs index 8b5eb5d..8b2368d 100644 --- a/NP.Lti13Platform.Core/Configs/Lti13PlatformCoreConfig.cs +++ b/NP.Lti13Platform.Core/Configs/Lti13PlatformCoreConfig.cs @@ -1,44 +1,41 @@ -namespace NP.Lti13Platform.Core.Configs +namespace NP.Lti13Platform.Core.Configs; + +public class Lti13PlatformTokenConfig { - public class Lti13PlatformTokenConfig + private string _issuer = string.Empty; + /// + /// A case-sensitive URL using the HTTPS scheme that contains: scheme, host; and, optionally, port number, and path components; and, no query or fragment components. The issuer identifies the platform to the tools. + /// + public required string Issuer { - private const string INVALID_ISSUER = "Issuer must follow the guidelines in the LTI 1.3 security spec. https://www.imsglobal.org/spec/security/v1p0/#dfn-issuer-identifier"; - - private string _issuer = string.Empty; - /// - /// A case-sensitive URL using the HTTPS scheme that contains: scheme, host; and, optionally, port number, and path components; and, no query or fragment components. The issuer identifies the platform to the tools. - /// - public required string Issuer + get => _issuer; + set { - get => _issuer; - set + if (Uri.TryCreate(value, UriKind.Absolute, out var result)) { - if (Uri.TryCreate(value, UriKind.Absolute, out var result)) + if (result.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.InvariantCultureIgnoreCase) && string.IsNullOrWhiteSpace(result.Query) && string.IsNullOrWhiteSpace(result.Fragment)) { - if (result.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.InvariantCultureIgnoreCase) && string.IsNullOrWhiteSpace(result.Query) && string.IsNullOrWhiteSpace(result.Fragment)) - { - _issuer = value; - return; - } + _issuer = value; + return; } - - throw new UriFormatException(INVALID_ISSUER); } + + throw new UriFormatException("Issuer must follow the guidelines in the LTI 1.3 security spec. https://www.imsglobal.org/spec/security/v1p0/#dfn-issuer-identifier"); } + } - /// - /// The value used to validate a token request from a tool. This is used to compare against the 'aud' claim of that JWT token request. - /// - public string? TokenAudience { get; set; } + /// + /// The value used to validate a token request from a tool. This is used to compare against the 'aud' claim of that JWT token request. + /// + public string? TokenAudience { get; set; } - /// - /// - /// - public int MessageTokenExpirationSeconds { get; set; } = 300; + /// + /// + /// + public int MessageTokenExpirationSeconds { get; set; } = 300; - /// - /// - /// - public int AccessTokenExpirationSeconds { get; set; } = 3600; - } + /// + /// + /// + public int AccessTokenExpirationSeconds { get; set; } = 3600; } \ No newline at end of file diff --git a/NP.Lti13Platform.Core/Configs/Lti13PlatformEndpointsConfig.cs b/NP.Lti13Platform.Core/Configs/Lti13PlatformEndpointsConfig.cs index 9efa378..230a70b 100644 --- a/NP.Lti13Platform.Core/Configs/Lti13PlatformEndpointsConfig.cs +++ b/NP.Lti13Platform.Core/Configs/Lti13PlatformEndpointsConfig.cs @@ -1,23 +1,22 @@ -namespace NP.Lti13Platform.Core.Configs +namespace NP.Lti13Platform.Core.Configs; + +public class Lti13PlatformCoreEndpointsConfig { - public class Lti13PlatformCoreEndpointsConfig - { - /// - /// Endpoint for the authorization of LTI 1.3 requests. - /// - /// Default: /lti13/authorization - public string AuthorizationUrl { get; set; } = "/lti13/authorization"; + /// + /// Endpoint for the authorization of LTI 1.3 requests. + /// + /// Default: /lti13/authorization + public string AuthorizationUrl { get; set; } = "/lti13/authorization"; - /// - /// Endpoint for getting a set of public JWKs. - /// - /// Default: /lti13/jwks - public string JwksUrl { get; set; } = "/lti13/jwks"; + /// + /// Endpoint for getting a set of public JWKs. + /// + /// Default: /lti13/jwks + public string JwksUrl { get; set; } = "/lti13/jwks"; - /// - /// Endpoint used to get auth tokens used for service calls. - /// - /// Default: /lti13/token - public string TokenUrl { get; set; } = "/lti13/token"; - } + /// + /// Endpoint used to get auth tokens used for service calls. + /// + /// Default: /lti13/token + public string TokenUrl { get; set; } = "/lti13/token"; } \ No newline at end of file diff --git a/NP.Lti13Platform.Core/Constants/Lti13ContextTypes.cs b/NP.Lti13Platform.Core/Constants/Lti13ContextTypes.cs index a3195ef..49af5e7 100644 --- a/NP.Lti13Platform.Core/Constants/Lti13ContextTypes.cs +++ b/NP.Lti13Platform.Core/Constants/Lti13ContextTypes.cs @@ -1,10 +1,9 @@ -namespace NP.Lti13Platform.Core.Constants +namespace NP.Lti13Platform.Core.Constants; + +public static class Lti13ContextTypes { - public static class Lti13ContextTypes - { - public const string CourseTemplate = "http://purl.imsglobal.org/vocab/lis/v2/course#CourseTemplate"; - public const string CourseOffering = "http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering"; - public const string CourseSection = "http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection"; - public const string Group = "http://purl.imsglobal.org/vocab/lis/v2/course#Group"; - } + public static readonly string CourseTemplate = "http://purl.imsglobal.org/vocab/lis/v2/course#CourseTemplate"; + public static readonly string CourseOffering = "http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering"; + public static readonly string CourseSection = "http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection"; + public static readonly string Group = "http://purl.imsglobal.org/vocab/lis/v2/course#Group"; } diff --git a/NP.Lti13Platform.Core/Constants/Lti13CustomVariables.cs b/NP.Lti13Platform.Core/Constants/Lti13CustomVariables.cs index 22abf9a..9ecb119 100644 --- a/NP.Lti13Platform.Core/Constants/Lti13CustomVariables.cs +++ b/NP.Lti13Platform.Core/Constants/Lti13CustomVariables.cs @@ -1,830 +1,823 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace NP.Lti13Platform.Core.Constants; -namespace NP.Lti13Platform.Core.Constants +public static class Lti13UserVariables { - public static class Lti13UserVariables - { - /// - /// user.id message property value; this may not be their real ID if they are masquerading as another user. - /// - public const string Id = "$User.id"; - - /// - /// user.image message property value. - /// - public const string Image = "$User.image"; - - /// - /// Username by which the message sender knows the user (typically, the name a user logs in with). - /// - public const string Username = "$User.username"; - - /// - /// One or more URIs describing the user's organizational properties (for example, an ldap:// URI). - /// By best practice, message senders should separate multiple URIs by commas. - /// - public const string Org = "$User.org"; - - /// - /// role_scope_mentor message property value. - /// - public const string ScopeMentor = "$User.scope.mentor"; - - /// - /// A comma-separated list of grade(s) for which the user is enrolled. - /// The permitted vocabulary is from the 'grades' field utilized in OneRoster Users. - /// - public const string GradeLevelsOneRoster = "$User.gradeLevels.oneRoster"; - } - - public static class Lti13ActualUserVariables - { - /// - /// user.id message property value; this may not be their real ID if they are masquerading as another user. - /// - public const string Id = "$ActualUser.id"; - - /// - /// user.image message property value. - /// - public const string Image = "$ActualUser.image"; - - /// - /// Username by which the message sender knows the user (typically, the name a user logs in with). - /// - public const string Username = "$ActualUser.username"; - - /// - /// One or more URIs describing the user's organizational properties (for example, an ldap:// URI). - /// By best practice, message senders should separate multiple URIs by commas. - /// - public const string Org = "$ActualUser.org"; - - /// - /// role_scope_mentor message property value. - /// - public const string ScopeMentor = "$ActualUser.scope.mentor"; - - /// - /// A comma-separated list of grade(s) for which the user is enrolled. - /// The permitted vocabulary is from the 'grades' field utilized in OneRoster Users. - /// - public const string GradeLevelsOneRoster = "$ActualUser.gradeLevels.oneRoster"; - } - - public static class Lti13ContextVariables - { - /// - /// (Context.id property) - /// - public const string Id = "$Context.id"; - - /// - /// A URI describing the context's organizational properties; for example, an ldap:// URI. - /// By best practice, message senders should separate URIs using commas. - /// - public const string Org = "$Context.org"; - - /// - /// (context.type property) - /// - public const string Type = "$Context.type"; - - /// - /// (context.label property) - /// - public const string Label = "$Context.label"; - - /// - /// (context.title property) - /// - public const string Title = "$Context.title"; - - /// - /// The sourced ID of the context. - /// - public const string SourcedId = "$Context.sourcedId"; - - /// - /// A comma-separated list of URL-encoded context ID values representing previous copies of the context; - /// the ID of most recent copy should appear first in the list followed by any earlier IDs in reverse chronological order. - /// If the context was created from scratch, not as a copy of an existing context, then this variable should have an empty value. - /// - public const string IdHistory = "$Context.id.history"; - - /// - /// A comma-separated list of grade(s) for which the context is attended. - /// The permitted vocabulary is from the grades field utilized in OneRoster Classes. - /// - public const string GradeLevelsOneRoster = "$Context.gradeLevels.oneRoster"; - } - - public static class Lti13ResourceLinkVariables - { - /// - /// (ResourceLink.id property) - /// - public const string Id = "$ResourceLink.id"; - - /// - /// (ResourceLink.title property) - /// - public const string Title = "$ResourceLink.title"; - - /// - /// (ResourceLink.description property) - /// - public const string Description = "$ResourceLink.description"; - - /// - /// The ISO 8601 date and time when this resource is available for learners to access. - /// - public const string AvailableStartDateTime = "$ResourceLink.available.startDateTime"; - - /// - /// The ISO 8601 date and time when this resource is available for the current user to access. - /// This date overrides that of ResourceLink.available.startDateTime. - /// A value of an empty string indicates that the date for the resource should be used. - /// - public const string AvailableUserStartDateTime = "$ResourceLink.available.user.startDateTime"; - - /// - /// The ISO 8601 date and time when this resource ceases to be available for learners to access. - /// - public const string AvailableEndDateTime = "$ResourceLink.available.endDateTime"; - - /// - /// The ISO 8601 date and time when this resource ceases to be available for the current user to access. - /// This date overrides that of ResourceLink.available.endDateTime. - /// A value of an empty string indicates that the date for the resource should be used. - /// - public const string AvailableUserEndDateTime = "$ResourceLink.available.user.endDateTime"; - - /// - /// The ISO 8601 date and time when this resource can start receiving submissions. - /// - public const string SubmissionStartDateTime = "$ResourceLink.submission.startDateTime"; - - /// - /// The ISO 8601 date and time when the current user can submit to the resource. - /// This date overrides that of ResourceLink.submission.startDateTime. - /// A value of an empty string indicates that the date for the resource should be used. - /// - public const string SubmissionUserStartDateTime = "$ResourceLink.submission.user.startDateTime"; - - /// - /// The ISO 8601 date and time when this resource stops accepting submissions. - /// - public const string SubmissionEndDateTime = "$ResourceLink.submission.endDateTime"; - - /// - /// The ISO 8601 date and time when the current user stops being able to submit to the resource. - /// This date overrides that of ResourceLink.submission.endDateTime. - /// A value of an empty string indicates that the date for the resource should be used. - /// - public const string SubmissionUserEndDateTime = "$ResourceLink.submission.user.endDateTime"; - - /// - /// The ISO 8601 date and time set when the grades for the associated line item can be released to learner. - /// - public const string LineItemReleaseDateTime = "$ResourceLink.lineitem.releaseDateTime"; - - /// - /// The ISO 8601 date and time set when the current user's grade for the associated line item can be released to the user. - /// This date overrides that of ResourceLink.lineitem.releaseDateTime. - /// A value of an empty string indicates that the date for the resource should be used. - /// - public const string LineItemUserReleaseDateTime = "$ResourceLink.lineitem.user.releaseDateTime"; - - /// - /// A comma-separated list of URL-encoded resource link ID values representing the ID of the link from a previous copy of the context; - /// the most recent copy should appear first in the list followed by any earlier IDs in reverse chronological order. - /// If the link was first added to the current context then this variable should have an empty value. - /// - public const string IdHistory = "$ResourceLink.id.history"; - } - - public static class Lti13ToolPlatformVariables - { - /// - /// Corresponds to the tool_platform.product_family_code property. - /// - public const string ProductFamilyCode = "$ToolPlatform.productFamilyCode"; - - /// - /// Corresponds to the tool_platform.version property. - /// - public const string Version = "$ToolPlatform.version"; - - /// - /// Corresponds to the tool_platform.instance_guid property. - /// - public const string InstanceGuid = "$ToolPlatformInstance.guid"; - - /// - /// Corresponds to the tool_platform.instance_name property. - /// - public const string InstanceName = "$ToolPlatformInstance.name"; - - /// - /// Corresponds to the tool_platform.instance_description property. - /// - public const string InstanceDescription = "$ToolPlatformInstance.description"; - - /// - /// Corresponds to the tool_platform.instance_url property. - /// - public const string InstanceUrl = "$ToolPlatformInstance.url"; - - /// - /// Corresponds to the tool_platform.instance_contact_email property. - /// - public const string InstanceContactEmail = "$ToolPlatformInstance.contactEmail"; - } - - public static class LisPersonVariables - { - /// - /// XPath for value from LIS database: personRecord/sourcedId - /// (lis_person.sourcedid property) - /// - public const string SourcedId = "$Person.sourcedId"; - - /// - /// XPath for value from LIS database: personRecord/person/formname/[formnameType/instanceValue/text="Full"]/formattedName/text - /// (lis_person.name_full property) - /// - public const string NameFull = "$Person.name.full"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Family"]/instanceValue/text - /// (lis_person.name_family property) - /// - public const string NameFamily = "$Person.name.family"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Given"]/instanceValue/text - /// (lis_person.name_given property) - /// - public const string NameGiven = "$Person.name.given"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Middle"]/instanceValue/text - /// - public const string NameMiddle = "$Person.name.middle"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Prefix"]/instanceValue/text - /// - public const string NamePrefix = "$Person.name.prefix"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Suffix"]/instanceValue/text - /// - public const string NameSuffix = "$Person.name.suffix"; - - /// - /// XPath for value from LIS database: personRecord/person/demographics/gender/instanceValue/text - /// - public const string Gender = "$Person.gender"; - - /// - /// No XPath available (N/A) - /// - public const string GenderPronouns = "$Person.gender.pronouns"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress1"]/instanceValue/text - /// - public const string AddressStreet1 = "$Person.address.street1"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair[instanceName/text="NonFieldedStreetAddress2"]/instanceValue/text - /// - public const string AddressStreet2 = "$Person.address.street2"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress3"]/instanceValue/text - /// - public const string AddressStreet3 = "$Person.address.street3"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress4"]/instanceValue/ - /// - public const string AddressStreet4 = "$Person.address.street4"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Locality"]/instanceValue/text - /// - public const string AddressLocality = "$Person.address.locality"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred "]addressPart/nameValuePair/[instanceName/text="Statepr"]/instanceValue/text - /// - public const string AddressStatepr = "$Person.address.statepr"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Country"]/instanceValue/text - /// - public const string AddressCountry = "$Person.address.country"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Postcode"]/instanceValue/text - /// - public const string AddressPostcode = "$Person.address.postcode"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Timezone"]/instanceValue/text - /// - public const string AddressTimezone = "$Person.address.timezone"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Mobile"]/contactInfoValue/text - /// - public const string PhoneMobile = "$Person.phone.mobile"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Telephone_Primary"]/contactinfoValue/text - /// - public const string PhonePrimary = "$Person.phone.primary"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Home"]/contactinfoValue/text - /// - public const string PhoneHome = "$Person.phone.home"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Work"]/contactinfoValue /text - /// - public const string PhoneWork = "$Person.phone.work"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Email_Primary"]/contactinfoValue/text - /// (lis.person_contact_email_primary property) - /// - public const string EmailPrimary = "$Person.email.primary"; - - /// - /// XPath for value from LIS database: person/contactinfo[contactinfoType/instanceValue/text="Email_Personal"]/contactinfoValue/text - /// - public const string EmailPersonal = "$Person.email.personal"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Web-Address"]/contactinfoValue/text - /// - public const string Webaddress = "$Person.webaddress"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="SMS"]/contactinfoValue/text - /// - public const string Sms = "$Person.sms"; - } - - public static class LisActualPersonVariables - { - /// - /// XPath for value from LIS database: personRecord/sourcedId - /// (lis_person.sourcedid property) - /// - public const string SourcedId = "$ActualPerson.sourcedId"; - - /// - /// XPath for value from LIS database: personRecord/person/formname/[formnameType/instanceValue/text="Full"]/formattedName/text - /// (lis_person.name_full property) - /// - public const string NameFull = "$ActualPerson.name.full"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Family"]/instanceValue/text - /// (lis_person.name_family property) - /// - public const string NameFamily = "$ActualPerson.name.family"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Given"]/instanceValue/text - /// (lis_person.name_given property) - /// - public const string NameGiven = "$ActualPerson.name.given"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Middle"]/instanceValue/text - /// - public const string NameMiddle = "$ActualPerson.name.middle"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Prefix"]/instanceValue/text - /// - public const string NamePrefix = "$ActualPerson.name.prefix"; - - /// - /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Suffix"]/instanceValue/text - /// - public const string NameSuffix = "$ActualPerson.name.suffix"; - - /// - /// XPath for value from LIS database: personRecord/person/demographics/gender/instanceValue/text - /// - public const string Gender = "$ActualPerson.gender"; - - /// - /// No XPath available (N/A) - /// - public const string GenderPronouns = "$ActualPerson.gender.pronouns"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress1"]/instanceValue/text - /// - public const string AddressStreet1 = "$ActualPerson.address.street1"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair[instanceName/text="NonFieldedStreetAddress2"]/instanceValue/text - /// - public const string AddressStreet2 = "$ActualPerson.address.street2"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress3"]/instanceValue/text - /// - public const string AddressStreet3 = "$ActualPerson.address.street3"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress4"]/instanceValue/ - /// - public const string AddressStreet4 = "$ActualPerson.address.street4"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Locality"]/instanceValue/text - /// - public const string AddressLocality = "$ActualPerson.address.locality"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred "]addressPart/nameValuePair/[instanceName/text="Statepr"]/instanceValue/text - /// - public const string AddressStatepr = "$ActualPerson.address.statepr"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Country"]/instanceValue/text - /// - public const string AddressCountry = "$ActualPerson.address.country"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Postcode"]/instanceValue/text - /// - public const string AddressPostcode = "$ActualPerson.address.postcode"; - - /// - /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Timezone"]/instanceValue/text - /// - public const string AddressTimezone = "$ActualPerson.address.timezone"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Mobile"]/contactInfoValue/text - /// - public const string PhoneMobile = "$ActualPerson.phone.mobile"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Telephone_Primary"]/contactinfoValue/text - /// - public const string PhonePrimary = "$ActualPerson.phone.primary"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Home"]/contactinfoValue/text - /// - public const string PhoneHome = "$ActualPerson.phone.home"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Work"]/contactinfoValue /text - /// - public const string PhoneWork = "$ActualPerson.phone.work"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Email_Primary"]/contactinfoValue/text - /// (lis.person_contact_email_primary property) - /// - public const string EmailPrimary = "$ActualPerson.email.primary"; - - /// - /// XPath for value from LIS database: person/contactinfo[contactinfoType/instanceValue/text="Email_Personal"]/contactinfoValue/text - /// - public const string EmailPersonal = "$ActualPerson.email.personal"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Web-Address"]/contactinfoValue/text - /// - public const string Webaddress = "$ActualPerson.webaddress"; - - /// - /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="SMS"]/contactinfoValue/text - /// - public const string Sms = "$ActualPerson.sms"; - } - - public static class LisCourseVariables - { - /// - /// XPath for value from LIS database: courseTemplateRecord/sourcedId - /// - public const string SourcedId = "$CourseTemplate.sourcedId"; - - /// - /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/label/textString - /// - public const string Label = "$CourseTemplate.label"; - - /// - /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/title/textString - /// - public const string Title = "$CourseTemplate.title"; - - /// - /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/catalogDescription/shortDescription - /// - public const string ShortDescription = "$CourseTemplate.shortDescription"; - - /// - /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/catalogDescription/longDescription - /// - public const string LongDescription = "$CourseTemplate.longDescription"; - - /// - /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/courseNumber/textString - /// - public const string CourseNumber = "$CourseTemplate.courseNumber"; - - /// - /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/defaultCredits/textString - /// - public const string Credits = "$CourseTemplate.credits"; - } - - public static class LisCourseOfferingVariables - { - /// - /// XPath for value from LIS database: courseOfferingRecord/sourcedId - /// (lis_course_offering_sourcedid property) - /// - public const string SourcedId = "$CourseOffering.sourcedId"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/label - /// - public const string Label = "$CourseOffering.label"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/title - /// - public const string Title = "$CourseOffering.title"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/catalogDescription/shortDescription - /// - public const string ShortDescription = "$CourseOffering.shortDescription"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/catalogDescription/longDescription - /// - public const string LongDescription = "$CourseOffering.longDescription"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/courseNumber/textString - /// - public const string CourseNumber = "$CourseOffering.courseNumber"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/defaultCredits/textString - /// - public const string Credits = "$CourseOffering.credits"; - - /// - /// XPath for value from LIS database: courseOfferingRecord/courseOffering/defaultCredits/textString - /// - public const string AcademicSession = "$CourseOffering.academicSession"; - } - - public static class LisCourseSectionVariables - { - /// - /// XPath for value from LIS database: courseSection/sourcedId - /// (lis_course_section_sourcedid property) - /// - public const string SourcedId = "$CourseSection.sourcedId"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/label - /// - public const string Label = "$CourseSection.label"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/title - /// - public const string Title = "$CourseSection.title"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/catalogDescription/shortDescription - /// - public const string ShortDescription = "$CourseSection.shortDescription"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/catalogDescription/longDescription - /// - public const string LongDescription = "$CourseSection.longDescription"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/courseNumber/textString - /// - public const string CourseNumber = "$CourseSection.courseNumber"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/defaultCredits/textString - /// - public const string Credits = "$CourseSection.credits"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/maxNumberofStudents - /// - public const string MaxNumberOfStudents = "$CourseSection.maxNumberOfStudents"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/numberofStudents - /// - public const string NumberOfStudents = "$CourseSection.numberOfStudents"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/org[type/textString="Dept"]/orgName/textString - /// - public const string Dept = "$CourseSection.dept"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/timeFrame/begin - /// - public const string TimeFrameBegin = "$CourseSection.timeFrame.begin"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/timeFrame/end - /// - public const string TimeFrameEnd = "$CourseSection.timeFrame.end"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/enrollControl/enrollAccept - /// - public const string EnrollControlAccept = "$CourseSection.enrollControl.accept"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/enrollControl/enrollAllowed - /// - public const string EnrollControlAllowed = "$CourseSection.enrollControl.allowed"; - - /// - /// XPath for value from LIS database: courseSectionRecord/courseSection/dataSource - /// - public const string DataSource = "$CourseSection.dataSource"; - - /// - /// XPath for value from LIS database: createCourseSectionFromCourseSectionRequest/sourcedId - /// - public const string SourceSectionId = "$CourseSection.sourceSectionId"; - } - - public static class LisGroupVariables - { - /// - /// XPath for value from LIS database: groupRecord/sourcedId - /// - public const string SourcedId = "$Group.sourcedId"; - - /// - /// XPath for value from LIS database: groupRecord/group/groupType/scheme/textString - /// - public const string Scheme = "$Group.scheme"; - - /// - /// XPath for value from LIS database: groupRecord/group/groupType/typevalue/textString - /// - public const string Typevalue = "$Group.typevalue"; - - /// - /// XPath for value from LIS database: groupRecord/group/groupType/typevalue/level/textString - /// - public const string Level = "$Group.level"; - - /// - /// XPath for value from LIS database: groupRecord/group/email - /// - public const string Email = "$Group.email"; - - /// - /// XPath for value from LIS database: groupRecord/group/url - /// - public const string Url = "$Group.url"; - - /// - /// XPath for value from LIS database: groupRecord/group/timeframe/begin - /// - public const string TimeFrameBegin = "$Group.timeFrame.begin"; - - /// - /// XPath for value from LIS database: groupRecord/group/timeframe/end - /// - public const string TimeFrameEnd = "$Group.timeFrame.end"; - - /// - /// XPath for value from LIS database: groupRecord/group/enrollControl/enrollAccept - /// - public const string EnrollControlAccept = "$Group.enrollControl.accept"; - - /// - /// XPath for value from LIS database: groupRecord/group/enrollControl/enrollAllowed - /// - public const string EnrollControlEnd = "$Group.enrollControl.end"; - - /// - /// XPath for value from LIS database: groupRecord/group/description/shortDescription - /// - public const string ShortDescription = "$Group.shortDescription"; - - /// - /// XPath for value from LIS database: groupRecord/group/description/longDescription - /// - public const string LongDescription = "$Group.longDescription"; - - /// - /// XPath for value from LIS database: groupRecord/group/relationship[relation="Parent"]/sourcedId - /// - public const string ParentId = "$Group.parentId"; - } - - public static class LisMembershipVariables - { - /// - /// XPath for value from LIS database: membershipRecord/sourcedId - /// - public const string SourcedId = "$Membership.sourcedId"; - - /// - /// XPath for value from LIS database: membershipRecord/membership/collectionSourcedId - /// - public const string CollectionSourcedid = "$Membership.collectionSourcedid"; - - /// - /// XPath for value from LIS database: membershipRecord/membership/memnber/personSourcedId - /// - public const string PersonSourcedId = "$Membership.personSourcedId"; - - /// - /// XPath for value from LIS database: membershipRecord/membership/member/role/status - /// - public const string Status = "$Membership.status"; - - /// - /// XPath for value from LIS database: membershipRecord/membership/member/role/roleType - /// (roles property) - /// - public const string Role = "$Membership.role"; - - /// - /// XPath for value from LIS database: membershipRecord/membership/member/role/dateTime - /// - public const string CreatedTimestamp = "$Membership.createdTimestamp"; - - /// - /// XPath for value from LIS database: membershipRecord/membership/member/role/dataSource - /// - public const string DataSource = "$Membership.dataSource"; - - /// - /// Property: role_scope_mentor - /// - public const string RoleScopeMentor = "$Membership.role.scope.mentor"; - } - - public static class LisMessageVariables - { - /// - /// URL for returning the user to the platform (for example, the launch_presentation.return_url property). - /// - public const string ReturnUrl = "$Message.returnUrl"; - - /// - /// Corresponds to the launch_presentation.document_target property. - /// - public const string DocumentTarget = "$Message.documentTarget"; - - /// - /// Corresponds to the launch_presentation.height property. - /// - public const string Height = "$Message.height"; - - /// - /// Corresponds to the launch_presentation.width property. - /// - public const string Width = "$Message.width"; - - /// - /// Corresponds to the launch_presentation.locale property. - /// - public const string Locale = "$Message.locale"; - } + /// + /// user.id message property value; this may not be their real ID if they are masquerading as another user. + /// + public const string Id = "$User.id"; + + /// + /// user.image message property value. + /// + public const string Image = "$User.image"; + + /// + /// Username by which the message sender knows the user (typically, the name a user logs in with). + /// + public const string Username = "$User.username"; + + /// + /// One or more URIs describing the user's organizational properties (for example, an ldap:// URI). + /// By best practice, message senders should separate multiple URIs by commas. + /// + public const string Org = "$User.org"; + + /// + /// role_scope_mentor message property value. + /// + public const string ScopeMentor = "$User.scope.mentor"; + + /// + /// A comma-separated list of grade(s) for which the user is enrolled. + /// The permitted vocabulary is from the 'grades' field utilized in OneRoster Users. + /// + public const string GradeLevelsOneRoster = "$User.gradeLevels.oneRoster"; +} + +public static class Lti13ActualUserVariables +{ + /// + /// user.id message property value; this may not be their real ID if they are masquerading as another user. + /// + public const string Id = "$ActualUser.id"; + + /// + /// user.image message property value. + /// + public const string Image = "$ActualUser.image"; + + /// + /// Username by which the message sender knows the user (typically, the name a user logs in with). + /// + public const string Username = "$ActualUser.username"; + + /// + /// One or more URIs describing the user's organizational properties (for example, an ldap:// URI). + /// By best practice, message senders should separate multiple URIs by commas. + /// + public const string Org = "$ActualUser.org"; + + /// + /// role_scope_mentor message property value. + /// + public const string ScopeMentor = "$ActualUser.scope.mentor"; + + /// + /// A comma-separated list of grade(s) for which the user is enrolled. + /// The permitted vocabulary is from the 'grades' field utilized in OneRoster Users. + /// + public const string GradeLevelsOneRoster = "$ActualUser.gradeLevels.oneRoster"; +} + +public static class Lti13ContextVariables +{ + /// + /// (Context.id property) + /// + public const string Id = "$Context.id"; + + /// + /// A URI describing the context's organizational properties; for example, an ldap:// URI. + /// By best practice, message senders should separate URIs using commas. + /// + public const string Org = "$Context.org"; + + /// + /// (context.type property) + /// + public const string Type = "$Context.type"; + + /// + /// (context.label property) + /// + public const string Label = "$Context.label"; + + /// + /// (context.title property) + /// + public const string Title = "$Context.title"; + + /// + /// The sourced ID of the context. + /// + public const string SourcedId = "$Context.sourcedId"; + + /// + /// A comma-separated list of URL-encoded context ID values representing previous copies of the context; + /// the ID of most recent copy should appear first in the list followed by any earlier IDs in reverse chronological order. + /// If the context was created from scratch, not as a copy of an existing context, then this variable should have an empty value. + /// + public const string IdHistory = "$Context.id.history"; + + /// + /// A comma-separated list of grade(s) for which the context is attended. + /// The permitted vocabulary is from the grades field utilized in OneRoster Classes. + /// + public const string GradeLevelsOneRoster = "$Context.gradeLevels.oneRoster"; +} + +public static class Lti13ResourceLinkVariables +{ + /// + /// (ResourceLink.id property) + /// + public const string Id = "$ResourceLink.id"; + + /// + /// (ResourceLink.title property) + /// + public const string Title = "$ResourceLink.title"; + + /// + /// (ResourceLink.description property) + /// + public const string Description = "$ResourceLink.description"; + + /// + /// The ISO 8601 date and time when this resource is available for learners to access. + /// + public const string AvailableStartDateTime = "$ResourceLink.available.startDateTime"; + + /// + /// The ISO 8601 date and time when this resource is available for the current user to access. + /// This date overrides that of ResourceLink.available.startDateTime. + /// A value of an empty string indicates that the date for the resource should be used. + /// + public const string AvailableUserStartDateTime = "$ResourceLink.available.user.startDateTime"; + + /// + /// The ISO 8601 date and time when this resource ceases to be available for learners to access. + /// + public const string AvailableEndDateTime = "$ResourceLink.available.endDateTime"; + + /// + /// The ISO 8601 date and time when this resource ceases to be available for the current user to access. + /// This date overrides that of ResourceLink.available.endDateTime. + /// A value of an empty string indicates that the date for the resource should be used. + /// + public const string AvailableUserEndDateTime = "$ResourceLink.available.user.endDateTime"; + + /// + /// The ISO 8601 date and time when this resource can start receiving submissions. + /// + public const string SubmissionStartDateTime = "$ResourceLink.submission.startDateTime"; + + /// + /// The ISO 8601 date and time when the current user can submit to the resource. + /// This date overrides that of ResourceLink.submission.startDateTime. + /// A value of an empty string indicates that the date for the resource should be used. + /// + public const string SubmissionUserStartDateTime = "$ResourceLink.submission.user.startDateTime"; + + /// + /// The ISO 8601 date and time when this resource stops accepting submissions. + /// + public const string SubmissionEndDateTime = "$ResourceLink.submission.endDateTime"; + + /// + /// The ISO 8601 date and time when the current user stops being able to submit to the resource. + /// This date overrides that of ResourceLink.submission.endDateTime. + /// A value of an empty string indicates that the date for the resource should be used. + /// + public const string SubmissionUserEndDateTime = "$ResourceLink.submission.user.endDateTime"; + + /// + /// The ISO 8601 date and time set when the grades for the associated line item can be released to learner. + /// + public const string LineItemReleaseDateTime = "$ResourceLink.lineitem.releaseDateTime"; + + /// + /// The ISO 8601 date and time set when the current user's grade for the associated line item can be released to the user. + /// This date overrides that of ResourceLink.lineitem.releaseDateTime. + /// A value of an empty string indicates that the date for the resource should be used. + /// + public const string LineItemUserReleaseDateTime = "$ResourceLink.lineitem.user.releaseDateTime"; + + /// + /// A comma-separated list of URL-encoded resource link ID values representing the ID of the link from a previous copy of the context; + /// the most recent copy should appear first in the list followed by any earlier IDs in reverse chronological order. + /// If the link was first added to the current context then this variable should have an empty value. + /// + public const string IdHistory = "$ResourceLink.id.history"; +} + +public static class Lti13ToolPlatformVariables +{ + /// + /// Corresponds to the tool_platform.product_family_code property. + /// + public const string ProductFamilyCode = "$ToolPlatform.productFamilyCode"; + + /// + /// Corresponds to the tool_platform.version property. + /// + public const string Version = "$ToolPlatform.version"; + + /// + /// Corresponds to the tool_platform.instance_guid property. + /// + public const string InstanceGuid = "$ToolPlatformInstance.guid"; + + /// + /// Corresponds to the tool_platform.instance_name property. + /// + public const string InstanceName = "$ToolPlatformInstance.name"; + + /// + /// Corresponds to the tool_platform.instance_description property. + /// + public const string InstanceDescription = "$ToolPlatformInstance.description"; + + /// + /// Corresponds to the tool_platform.instance_url property. + /// + public const string InstanceUrl = "$ToolPlatformInstance.url"; + + /// + /// Corresponds to the tool_platform.instance_contact_email property. + /// + public const string InstanceContactEmail = "$ToolPlatformInstance.contactEmail"; +} + +public static class LisPersonVariables +{ + /// + /// XPath for value from LIS database: personRecord/sourcedId + /// (lis_person.sourcedid property) + /// + public const string SourcedId = "$Person.sourcedId"; + + /// + /// XPath for value from LIS database: personRecord/person/formname/[formnameType/instanceValue/text="Full"]/formattedName/text + /// (lis_person.name_full property) + /// + public const string NameFull = "$Person.name.full"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Family"]/instanceValue/text + /// (lis_person.name_family property) + /// + public const string NameFamily = "$Person.name.family"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Given"]/instanceValue/text + /// (lis_person.name_given property) + /// + public const string NameGiven = "$Person.name.given"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Middle"]/instanceValue/text + /// + public const string NameMiddle = "$Person.name.middle"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Prefix"]/instanceValue/text + /// + public const string NamePrefix = "$Person.name.prefix"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Suffix"]/instanceValue/text + /// + public const string NameSuffix = "$Person.name.suffix"; + + /// + /// XPath for value from LIS database: personRecord/person/demographics/gender/instanceValue/text + /// + public const string Gender = "$Person.gender"; + + /// + /// No XPath available (N/A) + /// + public const string GenderPronouns = "$Person.gender.pronouns"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress1"]/instanceValue/text + /// + public const string AddressStreet1 = "$Person.address.street1"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair[instanceName/text="NonFieldedStreetAddress2"]/instanceValue/text + /// + public const string AddressStreet2 = "$Person.address.street2"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress3"]/instanceValue/text + /// + public const string AddressStreet3 = "$Person.address.street3"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress4"]/instanceValue/ + /// + public const string AddressStreet4 = "$Person.address.street4"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Locality"]/instanceValue/text + /// + public const string AddressLocality = "$Person.address.locality"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred "]addressPart/nameValuePair/[instanceName/text="Statepr"]/instanceValue/text + /// + public const string AddressStatepr = "$Person.address.statepr"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Country"]/instanceValue/text + /// + public const string AddressCountry = "$Person.address.country"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Postcode"]/instanceValue/text + /// + public const string AddressPostcode = "$Person.address.postcode"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Timezone"]/instanceValue/text + /// + public const string AddressTimezone = "$Person.address.timezone"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Mobile"]/contactInfoValue/text + /// + public const string PhoneMobile = "$Person.phone.mobile"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Telephone_Primary"]/contactinfoValue/text + /// + public const string PhonePrimary = "$Person.phone.primary"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Home"]/contactinfoValue/text + /// + public const string PhoneHome = "$Person.phone.home"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Work"]/contactinfoValue /text + /// + public const string PhoneWork = "$Person.phone.work"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Email_Primary"]/contactinfoValue/text + /// (lis.person_contact_email_primary property) + /// + public const string EmailPrimary = "$Person.email.primary"; + + /// + /// XPath for value from LIS database: person/contactinfo[contactinfoType/instanceValue/text="Email_Personal"]/contactinfoValue/text + /// + public const string EmailPersonal = "$Person.email.personal"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Web-Address"]/contactinfoValue/text + /// + public const string Webaddress = "$Person.webaddress"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="SMS"]/contactinfoValue/text + /// + public const string Sms = "$Person.sms"; +} + +public static class LisActualPersonVariables +{ + /// + /// XPath for value from LIS database: personRecord/sourcedId + /// (lis_person.sourcedid property) + /// + public const string SourcedId = "$ActualPerson.sourcedId"; + + /// + /// XPath for value from LIS database: personRecord/person/formname/[formnameType/instanceValue/text="Full"]/formattedName/text + /// (lis_person.name_full property) + /// + public const string NameFull = "$ActualPerson.name.full"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Family"]/instanceValue/text + /// (lis_person.name_family property) + /// + public const string NameFamily = "$ActualPerson.name.family"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Given"]/instanceValue/text + /// (lis_person.name_given property) + /// + public const string NameGiven = "$ActualPerson.name.given"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Middle"]/instanceValue/text + /// + public const string NameMiddle = "$ActualPerson.name.middle"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Prefix"]/instanceValue/text + /// + public const string NamePrefix = "$ActualPerson.name.prefix"; + + /// + /// XPath for value from LIS database: personRecord/person/name/partName[instanceName/text="Suffix"]/instanceValue/text + /// + public const string NameSuffix = "$ActualPerson.name.suffix"; + + /// + /// XPath for value from LIS database: personRecord/person/demographics/gender/instanceValue/text + /// + public const string Gender = "$ActualPerson.gender"; + + /// + /// No XPath available (N/A) + /// + public const string GenderPronouns = "$ActualPerson.gender.pronouns"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress1"]/instanceValue/text + /// + public const string AddressStreet1 = "$ActualPerson.address.street1"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]/addressPart/nameValuePair[instanceName/text="NonFieldedStreetAddress2"]/instanceValue/text + /// + public const string AddressStreet2 = "$ActualPerson.address.street2"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress3"]/instanceValue/text + /// + public const string AddressStreet3 = "$ActualPerson.address.street3"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="NonFieldedStreetAddress4"]/instanceValue/ + /// + public const string AddressStreet4 = "$ActualPerson.address.street4"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Locality"]/instanceValue/text + /// + public const string AddressLocality = "$ActualPerson.address.locality"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred "]addressPart/nameValuePair/[instanceName/text="Statepr"]/instanceValue/text + /// + public const string AddressStatepr = "$ActualPerson.address.statepr"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Country"]/instanceValue/text + /// + public const string AddressCountry = "$ActualPerson.address.country"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Postcode"]/instanceValue/text + /// + public const string AddressPostcode = "$ActualPerson.address.postcode"; + + /// + /// XPath for value from LIS database: personRecord/person/address/[addressType/instanceValue/text="Preferred"]addressPart/nameValuePair/[instanceName/text="Timezone"]/instanceValue/text + /// + public const string AddressTimezone = "$ActualPerson.address.timezone"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Mobile"]/contactInfoValue/text + /// + public const string PhoneMobile = "$ActualPerson.phone.mobile"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Telephone_Primary"]/contactinfoValue/text + /// + public const string PhonePrimary = "$ActualPerson.phone.primary"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Home"]/contactinfoValue/text + /// + public const string PhoneHome = "$ActualPerson.phone.home"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo [contactinfoType/instanceValue/text="Telephone_Work"]/contactinfoValue /text + /// + public const string PhoneWork = "$ActualPerson.phone.work"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Email_Primary"]/contactinfoValue/text + /// (lis.person_contact_email_primary property) + /// + public const string EmailPrimary = "$ActualPerson.email.primary"; + + /// + /// XPath for value from LIS database: person/contactinfo[contactinfoType/instanceValue/text="Email_Personal"]/contactinfoValue/text + /// + public const string EmailPersonal = "$ActualPerson.email.personal"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="Web-Address"]/contactinfoValue/text + /// + public const string Webaddress = "$ActualPerson.webaddress"; + + /// + /// XPath for value from LIS database: personRecord/person/contactinfo[contactinfoType/instanceValue/text="SMS"]/contactinfoValue/text + /// + public const string Sms = "$ActualPerson.sms"; +} + +public static class LisCourseVariables +{ + /// + /// XPath for value from LIS database: courseTemplateRecord/sourcedId + /// + public const string SourcedId = "$CourseTemplate.sourcedId"; + + /// + /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/label/textString + /// + public const string Label = "$CourseTemplate.label"; + + /// + /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/title/textString + /// + public const string Title = "$CourseTemplate.title"; + + /// + /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/catalogDescription/shortDescription + /// + public const string ShortDescription = "$CourseTemplate.shortDescription"; + + /// + /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/catalogDescription/longDescription + /// + public const string LongDescription = "$CourseTemplate.longDescription"; + + /// + /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/courseNumber/textString + /// + public const string CourseNumber = "$CourseTemplate.courseNumber"; + + /// + /// XPath for value from LIS database: courseTemplateRecord/courseTemplate/defaultCredits/textString + /// + public const string Credits = "$CourseTemplate.credits"; +} + +public static class LisCourseOfferingVariables +{ + /// + /// XPath for value from LIS database: courseOfferingRecord/sourcedId + /// (lis_course_offering_sourcedid property) + /// + public const string SourcedId = "$CourseOffering.sourcedId"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/label + /// + public const string Label = "$CourseOffering.label"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/title + /// + public const string Title = "$CourseOffering.title"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/catalogDescription/shortDescription + /// + public const string ShortDescription = "$CourseOffering.shortDescription"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/catalogDescription/longDescription + /// + public const string LongDescription = "$CourseOffering.longDescription"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/courseNumber/textString + /// + public const string CourseNumber = "$CourseOffering.courseNumber"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/defaultCredits/textString + /// + public const string Credits = "$CourseOffering.credits"; + + /// + /// XPath for value from LIS database: courseOfferingRecord/courseOffering/defaultCredits/textString + /// + public const string AcademicSession = "$CourseOffering.academicSession"; +} + +public static class LisCourseSectionVariables +{ + /// + /// XPath for value from LIS database: courseSection/sourcedId + /// (lis_course_section_sourcedid property) + /// + public const string SourcedId = "$CourseSection.sourcedId"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/label + /// + public const string Label = "$CourseSection.label"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/title + /// + public const string Title = "$CourseSection.title"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/catalogDescription/shortDescription + /// + public const string ShortDescription = "$CourseSection.shortDescription"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/catalogDescription/longDescription + /// + public const string LongDescription = "$CourseSection.longDescription"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/courseNumber/textString + /// + public const string CourseNumber = "$CourseSection.courseNumber"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/defaultCredits/textString + /// + public const string Credits = "$CourseSection.credits"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/maxNumberofStudents + /// + public const string MaxNumberOfStudents = "$CourseSection.maxNumberOfStudents"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/numberofStudents + /// + public const string NumberOfStudents = "$CourseSection.numberOfStudents"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/org[type/textString="Dept"]/orgName/textString + /// + public const string Dept = "$CourseSection.dept"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/timeFrame/begin + /// + public const string TimeFrameBegin = "$CourseSection.timeFrame.begin"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/timeFrame/end + /// + public const string TimeFrameEnd = "$CourseSection.timeFrame.end"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/enrollControl/enrollAccept + /// + public const string EnrollControlAccept = "$CourseSection.enrollControl.accept"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/enrollControl/enrollAllowed + /// + public const string EnrollControlAllowed = "$CourseSection.enrollControl.allowed"; + + /// + /// XPath for value from LIS database: courseSectionRecord/courseSection/dataSource + /// + public const string DataSource = "$CourseSection.dataSource"; + + /// + /// XPath for value from LIS database: createCourseSectionFromCourseSectionRequest/sourcedId + /// + public const string SourceSectionId = "$CourseSection.sourceSectionId"; +} + +public static class LisGroupVariables +{ + /// + /// XPath for value from LIS database: groupRecord/sourcedId + /// + public const string SourcedId = "$Group.sourcedId"; + + /// + /// XPath for value from LIS database: groupRecord/group/groupType/scheme/textString + /// + public const string Scheme = "$Group.scheme"; + + /// + /// XPath for value from LIS database: groupRecord/group/groupType/typevalue/textString + /// + public const string Typevalue = "$Group.typevalue"; + + /// + /// XPath for value from LIS database: groupRecord/group/groupType/typevalue/level/textString + /// + public const string Level = "$Group.level"; + + /// + /// XPath for value from LIS database: groupRecord/group/email + /// + public const string Email = "$Group.email"; + + /// + /// XPath for value from LIS database: groupRecord/group/url + /// + public const string Url = "$Group.url"; + + /// + /// XPath for value from LIS database: groupRecord/group/timeframe/begin + /// + public const string TimeFrameBegin = "$Group.timeFrame.begin"; + + /// + /// XPath for value from LIS database: groupRecord/group/timeframe/end + /// + public const string TimeFrameEnd = "$Group.timeFrame.end"; + + /// + /// XPath for value from LIS database: groupRecord/group/enrollControl/enrollAccept + /// + public const string EnrollControlAccept = "$Group.enrollControl.accept"; + + /// + /// XPath for value from LIS database: groupRecord/group/enrollControl/enrollAllowed + /// + public const string EnrollControlEnd = "$Group.enrollControl.end"; + + /// + /// XPath for value from LIS database: groupRecord/group/description/shortDescription + /// + public const string ShortDescription = "$Group.shortDescription"; + + /// + /// XPath for value from LIS database: groupRecord/group/description/longDescription + /// + public const string LongDescription = "$Group.longDescription"; + + /// + /// XPath for value from LIS database: groupRecord/group/relationship[relation="Parent"]/sourcedId + /// + public const string ParentId = "$Group.parentId"; +} + +public static class LisMembershipVariables +{ + /// + /// XPath for value from LIS database: membershipRecord/sourcedId + /// + public const string SourcedId = "$Membership.sourcedId"; + + /// + /// XPath for value from LIS database: membershipRecord/membership/collectionSourcedId + /// + public const string CollectionSourcedid = "$Membership.collectionSourcedid"; + + /// + /// XPath for value from LIS database: membershipRecord/membership/memnber/personSourcedId + /// + public const string PersonSourcedId = "$Membership.personSourcedId"; + + /// + /// XPath for value from LIS database: membershipRecord/membership/member/role/status + /// + public const string Status = "$Membership.status"; + + /// + /// XPath for value from LIS database: membershipRecord/membership/member/role/roleType + /// (roles property) + /// + public const string Role = "$Membership.role"; + + /// + /// XPath for value from LIS database: membershipRecord/membership/member/role/dateTime + /// + public const string CreatedTimestamp = "$Membership.createdTimestamp"; + + /// + /// XPath for value from LIS database: membershipRecord/membership/member/role/dataSource + /// + public const string DataSource = "$Membership.dataSource"; + + /// + /// Property: role_scope_mentor + /// + public const string RoleScopeMentor = "$Membership.role.scope.mentor"; +} + +public static class LisMessageVariables +{ + /// + /// URL for returning the user to the platform (for example, the launch_presentation.return_url property). + /// + public const string ReturnUrl = "$Message.returnUrl"; + + /// + /// Corresponds to the launch_presentation.document_target property. + /// + public const string DocumentTarget = "$Message.documentTarget"; + + /// + /// Corresponds to the launch_presentation.height property. + /// + public const string Height = "$Message.height"; + + /// + /// Corresponds to the launch_presentation.width property. + /// + public const string Width = "$Message.width"; + + /// + /// Corresponds to the launch_presentation.locale property. + /// + public const string Locale = "$Message.locale"; } diff --git a/NP.Lti13Platform.Core/Constants/Lti13MessageType.cs b/NP.Lti13Platform.Core/Constants/Lti13MessageType.cs index d8098bb..7ec8f9e 100644 --- a/NP.Lti13Platform.Core/Constants/Lti13MessageType.cs +++ b/NP.Lti13Platform.Core/Constants/Lti13MessageType.cs @@ -1,7 +1,6 @@ -namespace NP.Lti13Platform.Core.Constants +namespace NP.Lti13Platform.Core.Constants; + +public static class Lti13MessageType { - public static class Lti13MessageType - { - public const string LtiResourceLinkRequest = "LtiResourceLinkRequest"; - } + public static readonly string LtiResourceLinkRequest = "LtiResourceLinkRequest"; } diff --git a/NP.Lti13Platform.Core/Constants/Lti13PresentationTargetDocuments.cs b/NP.Lti13Platform.Core/Constants/Lti13PresentationTargetDocuments.cs index 02a83ba..fa9ef90 100644 --- a/NP.Lti13Platform.Core/Constants/Lti13PresentationTargetDocuments.cs +++ b/NP.Lti13Platform.Core/Constants/Lti13PresentationTargetDocuments.cs @@ -1,10 +1,8 @@ -namespace NP.Lti13Platform.Core.Constants -{ - public static class Lti13PresentationTargetDocuments - { - public const string Embed = "embed"; - public const string Window = "window"; - public const string Iframe = "iframe"; - } +namespace NP.Lti13Platform.Core.Constants; +public static class Lti13PresentationTargetDocuments +{ + public static readonly string Embed = "embed"; + public static readonly string Window = "window"; + public static readonly string Iframe = "iframe"; } diff --git a/NP.Lti13Platform.Core/Constants/Lti13Roles.cs b/NP.Lti13Platform.Core/Constants/Lti13Roles.cs index 716236e..e558611 100644 --- a/NP.Lti13Platform.Core/Constants/Lti13Roles.cs +++ b/NP.Lti13Platform.Core/Constants/Lti13Roles.cs @@ -1,121 +1,120 @@ -namespace NP.Lti13Platform.Core.Constants +namespace NP.Lti13Platform.Core.Constants; + +public static class Lti13SystemRoles { - public static class Lti13SystemRoles - { - // Core Roles - public const string Administrator = "http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator"; - public const string None = "http://purl.imsglobal.org/vocab/lis/v2/system/person#None"; + // Core Roles + public static readonly string Administrator = "http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator"; + public static readonly string None = "http://purl.imsglobal.org/vocab/lis/v2/system/person#None"; - // Non-Core Roles - public const string AccountAdmin = "http://purl.imsglobal.org/vocab/lis/v2/system/person#AccountAdmin"; - public const string Creator = "http://purl.imsglobal.org/vocab/lis/v2/system/person#Creator"; - public const string SysAdmin = "http://purl.imsglobal.org/vocab/lis/v2/system/person#SysAdmin"; - public const string SysSupport = "http://purl.imsglobal.org/vocab/lis/v2/system/person#SysSupport"; - public const string User = "http://purl.imsglobal.org/vocab/lis/v2/system/person#User"; + // Non-Core Roles + public static readonly string AccountAdmin = "http://purl.imsglobal.org/vocab/lis/v2/system/person#AccountAdmin"; + public static readonly string Creator = "http://purl.imsglobal.org/vocab/lis/v2/system/person#Creator"; + public static readonly string SysAdmin = "http://purl.imsglobal.org/vocab/lis/v2/system/person#SysAdmin"; + public static readonly string SysSupport = "http://purl.imsglobal.org/vocab/lis/v2/system/person#SysSupport"; + public static readonly string User = "http://purl.imsglobal.org/vocab/lis/v2/system/person#User"; - // LTI Launch Only - public const string TestUser = "http://purl.imsglobal.org/vocab/lti/system/person#TestUser"; - } + // LTI Launch Only + public static readonly string TestUser = "http://purl.imsglobal.org/vocab/lti/system/person#TestUser"; +} - public static class Lti13InstitutionRoles - { - // Core Roles - public const string Administrator = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator"; - public const string Faculty = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Faculty"; - public const string Guest = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Guest"; - public const string None = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#None"; - public const string Other = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Other"; - public const string Staff = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Staff"; - public const string Student = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student"; +public static class Lti13InstitutionRoles +{ + // Core Roles + public static readonly string Administrator = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator"; + public static readonly string Faculty = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Faculty"; + public static readonly string Guest = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Guest"; + public static readonly string None = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#None"; + public static readonly string Other = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Other"; + public static readonly string Staff = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Staff"; + public static readonly string Student = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student"; - // Non-Core Roles - public const string Alumni = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Alumni"; - public const string Instructor = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Instructor"; - public const string Learner = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Learner"; - public const string Member = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Member"; - public const string Mentor = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Mentor"; - public const string Observer = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Observer"; - public const string ProspectiveStudent = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#ProspectiveStudent"; - } + // Non-Core Roles + public static readonly string Alumni = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Alumni"; + public static readonly string Instructor = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Instructor"; + public static readonly string Learner = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Learner"; + public static readonly string Member = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Member"; + public static readonly string Mentor = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Mentor"; + public static readonly string Observer = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Observer"; + public static readonly string ProspectiveStudent = "http://purl.imsglobal.org/vocab/lis/v2/institution/person#ProspectiveStudent"; +} - public static class Lti13ContextRoles - { - // Core Roles - public const string Administrator = "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"; - public const string ContentDeveloper = "http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper"; - public const string Instructor = "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"; - public const string Learner = "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"; - public const string Mentor = "http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor"; +public static class Lti13ContextRoles +{ + // Core Roles + public static readonly string Administrator = "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"; + public static readonly string ContentDeveloper = "http://purl.imsglobal.org/vocab/lis/v2/membership#ContentDeveloper"; + public static readonly string Instructor = "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"; + public static readonly string Learner = "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"; + public static readonly string Mentor = "http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor"; - // Non-Core Roles - public const string Manager = "http://purl.imsglobal.org/vocab/lis/v2/membership#Manager"; - public const string Member = "http://purl.imsglobal.org/vocab/lis/v2/membership#Member"; - public const string Officer = "http://purl.imsglobal.org/vocab/lis/v2/membership#Officer"; - } + // Non-Core Roles + public static readonly string Manager = "http://purl.imsglobal.org/vocab/lis/v2/membership#Manager"; + public static readonly string Member = "http://purl.imsglobal.org/vocab/lis/v2/membership#Member"; + public static readonly string Officer = "http://purl.imsglobal.org/vocab/lis/v2/membership#Officer"; +} - /// - /// Whenever a platform specifies a sub-role, by best practice it should also include the associated principal role. - /// - /// - public static class Lti13ContextSubRoles - { - public const string Administrator_Administrator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Administrator"; - public const string Administrator_Developer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Developer"; - public const string Administrator_ExternalDeveloper = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalDeveloper"; - public const string Administrator_ExternalSupport = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSupport"; - public const string Administrator_ExternalSystemAdministrator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSystemAdministrator"; - public const string Administrator_Support = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Support"; - public const string Administrator_SystemAdministrator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#SystemAdministrator"; +/// +/// Whenever a platform specifies a sub-role, by best practice it should also include the associated principal role. +/// +/// +public static class Lti13ContextSubRoles +{ + public static readonly string Administrator_Administrator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Administrator"; + public static readonly string Administrator_Developer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Developer"; + public static readonly string Administrator_ExternalDeveloper = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalDeveloper"; + public static readonly string Administrator_ExternalSupport = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSupport"; + public static readonly string Administrator_ExternalSystemAdministrator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#ExternalSystemAdministrator"; + public static readonly string Administrator_Support = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#Support"; + public static readonly string Administrator_SystemAdministrator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Administrator#SystemAdministrator"; - public const string ContentDeveloper_ContentDeveloper = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentDeveloper"; - public const string ContentDeveloper_ContentExpert = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentExpert"; - public const string ContentDeveloper_ExternalContentExpert = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ExternalContentExpert"; - public const string ContentDeveloper_Librarian = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#Librarian"; + public static readonly string ContentDeveloper_ContentDeveloper = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentDeveloper"; + public static readonly string ContentDeveloper_ContentExpert = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ContentExpert"; + public static readonly string ContentDeveloper_ExternalContentExpert = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#ExternalContentExpert"; + public static readonly string ContentDeveloper_Librarian = "http://purl.imsglobal.org/vocab/lis/v2/membership/ContentDeveloper#Librarian"; - public const string Instructor_ExternalInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#ExternalInstructor"; - public const string Instructor_Grader = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Grader"; - public const string Instructor_GuestInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#GuestInstructor"; - public const string Instructor_Lecturer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Lecturer"; - public const string Instructor_PrimaryInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#PrimaryInstructor"; - public const string Instructor_SecondaryInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#SecondaryInstructor"; - public const string Instructor_TeachingAssistant = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant"; - public const string Instructor_TeachingAssistantGroup = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantGroup"; - public const string Instructor_TeachingAssistantOffering = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantOffering"; - public const string Instructor_TeachingAssistantSection = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSection"; - public const string Instructor_TeachingAssistantSectionAssociation = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSectionAssociation"; - public const string Instructor_TeachingAssistantTemplate = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantTemplate"; + public static readonly string Instructor_ExternalInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#ExternalInstructor"; + public static readonly string Instructor_Grader = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Grader"; + public static readonly string Instructor_GuestInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#GuestInstructor"; + public static readonly string Instructor_Lecturer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#Lecturer"; + public static readonly string Instructor_PrimaryInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#PrimaryInstructor"; + public static readonly string Instructor_SecondaryInstructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#SecondaryInstructor"; + public static readonly string Instructor_TeachingAssistant = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant"; + public static readonly string Instructor_TeachingAssistantGroup = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantGroup"; + public static readonly string Instructor_TeachingAssistantOffering = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantOffering"; + public static readonly string Instructor_TeachingAssistantSection = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSection"; + public static readonly string Instructor_TeachingAssistantSectionAssociation = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantSectionAssociation"; + public static readonly string Instructor_TeachingAssistantTemplate = "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistantTemplate"; - public const string Learner_ExternalLearner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#ExternalLearner"; - public const string Learner_GuestLearner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#GuestLearner"; - public const string Learner_Instructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Instructor"; - public const string Learner_Learner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Learner"; - public const string Learner_NonCreditLearner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#NonCreditLearner"; + public static readonly string Learner_ExternalLearner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#ExternalLearner"; + public static readonly string Learner_GuestLearner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#GuestLearner"; + public static readonly string Learner_Instructor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Instructor"; + public static readonly string Learner_Learner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#Learner"; + public static readonly string Learner_NonCreditLearner = "http://purl.imsglobal.org/vocab/lis/v2/membership/Learner#NonCreditLearner"; - public const string Mentor_Advisor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Advisor"; - public const string Mentor_Auditor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Auditor"; - public const string Mentor_ExternalAdvisor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAdvisor"; - public const string Mentor_ExternalAuditor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAuditor"; - public const string Mentor_ExternalLearningFacilitator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalLearningFacilitator"; - public const string Mentor_ExternalMentor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalMentor"; - public const string Mentor_ExternalReviewer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalReviewer"; - public const string Mentor_ExternalTutor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalTutor"; - public const string Mentor_LearningFacilitator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#LearningFacilitator"; - public const string Mentor_Mentor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Mentor"; - public const string Mentor_Reviewer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Reviewer"; - public const string Mentor_Tutor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Tutor"; + public static readonly string Mentor_Advisor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Advisor"; + public static readonly string Mentor_Auditor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Auditor"; + public static readonly string Mentor_ExternalAdvisor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAdvisor"; + public static readonly string Mentor_ExternalAuditor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalAuditor"; + public static readonly string Mentor_ExternalLearningFacilitator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalLearningFacilitator"; + public static readonly string Mentor_ExternalMentor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalMentor"; + public static readonly string Mentor_ExternalReviewer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalReviewer"; + public static readonly string Mentor_ExternalTutor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#ExternalTutor"; + public static readonly string Mentor_LearningFacilitator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#LearningFacilitator"; + public static readonly string Mentor_Mentor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Mentor"; + public static readonly string Mentor_Reviewer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Reviewer"; + public static readonly string Mentor_Tutor = "http://purl.imsglobal.org/vocab/lis/v2/membership/Mentor#Tutor"; - public const string Manager_AreaManager = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#AreaManager"; - public const string Manager_CourseCoordinator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#CourseCoordinator"; - public const string Manager_ExternalObserver = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#ExternalObserver"; - public const string Manager_Manager = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Manager"; - public const string Manager_Observer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Observer"; + public static readonly string Manager_AreaManager = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#AreaManager"; + public static readonly string Manager_CourseCoordinator = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#CourseCoordinator"; + public static readonly string Manager_ExternalObserver = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#ExternalObserver"; + public static readonly string Manager_Manager = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Manager"; + public static readonly string Manager_Observer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Manager#Observer"; - public const string Member_Member = "http://purl.imsglobal.org/vocab/lis/v2/membership/Member#Member"; + public static readonly string Member_Member = "http://purl.imsglobal.org/vocab/lis/v2/membership/Member#Member"; - public const string Officer_Chair = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Chair"; - public const string Officer_Communications = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Communications"; - public const string Officer_Secretary = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Secretary"; - public const string Officer_Treasurer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Treasurer"; - public const string Officer_ViceChair = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Vice-Chair"; - } + public static readonly string Officer_Chair = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Chair"; + public static readonly string Officer_Communications = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Communications"; + public static readonly string Officer_Secretary = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Secretary"; + public static readonly string Officer_Treasurer = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Treasurer"; + public static readonly string Officer_ViceChair = "http://purl.imsglobal.org/vocab/lis/v2/membership/Officer#Vice-Chair"; } diff --git a/NP.Lti13Platform.Core/Constants/RouteNames.cs b/NP.Lti13Platform.Core/Constants/RouteNames.cs index d7eab0b..541d898 100644 --- a/NP.Lti13Platform.Core/Constants/RouteNames.cs +++ b/NP.Lti13Platform.Core/Constants/RouteNames.cs @@ -1,7 +1,6 @@ -namespace NP.Lti13Platform.Core.Constants +namespace NP.Lti13Platform.Core.Constants; + +internal static class RouteNames { - internal static class RouteNames - { - public const string TOKEN = "TOKEN"; - } + public static readonly string TOKEN = "TOKEN"; } diff --git a/NP.Lti13Platform.Core/LtiMessageTypeResolver.cs b/NP.Lti13Platform.Core/LtiMessageTypeResolver.cs index 75a1759..477d728 100644 --- a/NP.Lti13Platform.Core/LtiMessageTypeResolver.cs +++ b/NP.Lti13Platform.Core/LtiMessageTypeResolver.cs @@ -2,36 +2,35 @@ using System.Text.Json.Serialization.Metadata; using NP.Lti13Platform.Core.Populators; -namespace NP.Lti13Platform.Core +namespace NP.Lti13Platform.Core; + +internal class LtiMessageTypeResolver : DefaultJsonTypeInfoResolver { - internal class LtiMessageTypeResolver : DefaultJsonTypeInfoResolver + private static readonly HashSet derivedTypes = []; + + public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { - private static readonly HashSet derivedTypes = []; + var jsonTypeInfo = base.GetTypeInfo(type, options); - public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) + var baseType = typeof(LtiMessage); + if (jsonTypeInfo.Type == baseType) { - var jsonTypeInfo = base.GetTypeInfo(type, options); - - var baseType = typeof(LtiMessage); - if (jsonTypeInfo.Type == baseType) + jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { - jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions - { - IgnoreUnrecognizedTypeDiscriminators = true, - }; + IgnoreUnrecognizedTypeDiscriminators = true, + }; - foreach (var derivedType in derivedTypes) - { - jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derivedType)); - } + foreach (var derivedType in derivedTypes) + { + jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derivedType)); } - - return jsonTypeInfo; } - public static void AddDerivedType(Type type) - { - derivedTypes.Add(type); - } + return jsonTypeInfo; + } + + public static void AddDerivedType(Type type) + { + derivedTypes.Add(type); } } diff --git a/NP.Lti13Platform.Core/LtiServicesAuthHandler.cs b/NP.Lti13Platform.Core/LtiServicesAuthHandler.cs index 35f7ccc..89da0c1 100644 --- a/NP.Lti13Platform.Core/LtiServicesAuthHandler.cs +++ b/NP.Lti13Platform.Core/LtiServicesAuthHandler.cs @@ -7,36 +7,35 @@ using System.Security.Claims; using System.Text.Encodings.Web; -namespace NP.Lti13Platform.Core +namespace NP.Lti13Platform.Core; + +public class LtiServicesAuthHandler(ILti13CoreDataService dataService, ILti13TokenConfigService tokenService, IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + : AuthenticationHandler(options, logger, encoder) { - public class LtiServicesAuthHandler(ILti13CoreDataService dataService, ILti13TokenConfigService tokenService, IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) - : AuthenticationHandler(options, logger, encoder) + public static readonly string SchemeName = "NP.Lti13Platform.Services"; + + protected override async Task HandleAuthenticateAsync() { - public const string SchemeName = "NP.Lti13Platform.Services"; + var authHeaderParts = Context.Request.Headers.Authorization.ToString().Trim().Split(' '); - protected override async Task HandleAuthenticateAsync() + if (authHeaderParts.Length != 2 || authHeaderParts[0] != "Bearer") { - var authHeaderParts = Context.Request.Headers.Authorization.ToString().Trim().Split(' '); - - if (authHeaderParts.Length != 2 || authHeaderParts[0] != "Bearer") - { - return AuthenticateResult.NoResult(); - } + return AuthenticateResult.NoResult(); + } - var publicKeys = await dataService.GetPublicKeysAsync(CancellationToken.None); + var publicKeys = await dataService.GetPublicKeysAsync(CancellationToken.None); - var jwt = new JsonWebToken(authHeaderParts[1]); + var jwt = new JsonWebToken(authHeaderParts[1]); - var tokenConfig = await tokenService.GetTokenConfigAsync(jwt.Subject, CancellationToken.None); + var tokenConfig = await tokenService.GetTokenConfigAsync(jwt.Subject, CancellationToken.None); - var validatedToken = await new JsonWebTokenHandler().ValidateTokenAsync(authHeaderParts[1], new TokenValidationParameters - { - IssuerSigningKeys = publicKeys, - ValidAudience = tokenConfig.Issuer, - ValidIssuer = tokenConfig.Issuer - }); + var validatedToken = await new JsonWebTokenHandler().ValidateTokenAsync(authHeaderParts[1], new TokenValidationParameters + { + IssuerSigningKeys = publicKeys, + ValidAudience = tokenConfig.Issuer, + ValidIssuer = tokenConfig.Issuer + }); - return validatedToken.IsValid ? AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal([validatedToken.ClaimsIdentity]), SchemeName)) : AuthenticateResult.NoResult(); - } + return validatedToken.IsValid ? AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal([validatedToken.ClaimsIdentity]), SchemeName)) : AuthenticateResult.NoResult(); } } diff --git a/NP.Lti13Platform.Core/Models/Attempt.cs b/NP.Lti13Platform.Core/Models/Attempt.cs index 6f9a241..5905989 100644 --- a/NP.Lti13Platform.Core/Models/Attempt.cs +++ b/NP.Lti13Platform.Core/Models/Attempt.cs @@ -1,17 +1,16 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Attempt { - public class Attempt - { - public required string ResourceLinkId { get; set; } + public required string ResourceLinkId { get; set; } - public required string UserId { get; set; } + public required string UserId { get; set; } - public DateTime? AvailableStartDateTime { get; set; } + public DateTime? AvailableStartDateTime { get; set; } - public DateTime? AvailableEndDateTime { get; set; } + public DateTime? AvailableEndDateTime { get; set; } - public DateTime? SubmisstionStartDateTime { get; set; } + public DateTime? SubmisstionStartDateTime { get; set; } - public DateTime? SubmissionEndDateTime { get; set; } - } + public DateTime? SubmissionEndDateTime { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/Context.cs b/NP.Lti13Platform.Core/Models/Context.cs index 0e30b07..e1594b4 100644 --- a/NP.Lti13Platform.Core/Models/Context.cs +++ b/NP.Lti13Platform.Core/Models/Context.cs @@ -1,25 +1,24 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Context { - public class Context - { - /// - /// Max Length 255 characters - /// Case sensitive - /// - public required string Id { get; set; } + /// + /// Max Length 255 characters + /// Case sensitive + /// + public required string Id { get; set; } - public string? Label { get; set; } + public string? Label { get; set; } - public string? Title { get; set; } + public string? Title { get; set; } - public string? SourcedId { get; set; } + public string? SourcedId { get; set; } - public IEnumerable Types { get; set; } = []; + public IEnumerable Types { get; set; } = []; - public IEnumerable Orgs { get; set; } = []; + public IEnumerable Orgs { get; set; } = []; - public IEnumerable ClonedIdHistory { get; set; } = []; + public IEnumerable ClonedIdHistory { get; set; } = []; - public IEnumerable OneRosterGrades { get; set; } = []; - } + public IEnumerable OneRosterGrades { get; set; } = []; } diff --git a/NP.Lti13Platform.Core/Models/CustomPermissions.cs b/NP.Lti13Platform.Core/Models/CustomPermissions.cs index 6ae895f..e9e8741 100644 --- a/NP.Lti13Platform.Core/Models/CustomPermissions.cs +++ b/NP.Lti13Platform.Core/Models/CustomPermissions.cs @@ -1,51 +1,50 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class CustomPermissions { - public class CustomPermissions - { - public bool UserId { get; set; } - public bool UserImage { get; set; } - public bool UserUsername { get; set; } - public bool UserOrg { get; set; } - public bool UserScopeMentor { get; set; } - public bool UserGradeLevelsOneRoster { get; set; } + public bool UserId { get; set; } + public bool UserImage { get; set; } + public bool UserUsername { get; set; } + public bool UserOrg { get; set; } + public bool UserScopeMentor { get; set; } + public bool UserGradeLevelsOneRoster { get; set; } - public bool ActualUserId { get; set; } - public bool ActualUserImage { get; set; } - public bool ActualUserUsername { get; set; } - public bool ActualUserOrg { get; set; } - public bool ActualUserScopeMentor { get; set; } - public bool ActualUserGradeLevelsOneRoster { get; set; } + public bool ActualUserId { get; set; } + public bool ActualUserImage { get; set; } + public bool ActualUserUsername { get; set; } + public bool ActualUserOrg { get; set; } + public bool ActualUserScopeMentor { get; set; } + public bool ActualUserGradeLevelsOneRoster { get; set; } - public bool ContextId { get; set; } - public bool ContextOrg { get; set; } - public bool ContextType { get; set; } - public bool ContextLabel { get; set; } - public bool ContextTitle { get; set; } - public bool ContextSourcedId { get; set; } - public bool ContextIdHistory { get; set; } - public bool ContextGradeLevelsOneRoster { get; set; } + public bool ContextId { get; set; } + public bool ContextOrg { get; set; } + public bool ContextType { get; set; } + public bool ContextLabel { get; set; } + public bool ContextTitle { get; set; } + public bool ContextSourcedId { get; set; } + public bool ContextIdHistory { get; set; } + public bool ContextGradeLevelsOneRoster { get; set; } - public bool ResourceLinkId { get; set; } - public bool ResourceLinkTitle { get; set; } - public bool ResourceLinkDescription { get; set; } - public bool ResourceLinkAvailableStartDateTime { get; set; } - public bool ResourceLinkAvailableUserStartDateTime { get; set; } - public bool ResourceLinkAvailableEndDateTime { get; set; } - public bool ResourceLinkAvailableUserEndDateTime { get; set; } - public bool ResourceLinkSubmissionStartDateTime { get; set; } - public bool ResourceLinkSubmissionUserStartDateTime { get; set; } - public bool ResourceLinkSubmissionEndDateTime { get; set; } - public bool ResourceLinkSubmissionUserEndDateTime { get; set; } - public bool ResourceLinkLineItemReleaseDateTime { get; set; } - public bool ResourceLinkLineItemUserReleaseDateTime { get; set; } - public bool ResourceLinkIdHistory { get; set; } + public bool ResourceLinkId { get; set; } + public bool ResourceLinkTitle { get; set; } + public bool ResourceLinkDescription { get; set; } + public bool ResourceLinkAvailableStartDateTime { get; set; } + public bool ResourceLinkAvailableUserStartDateTime { get; set; } + public bool ResourceLinkAvailableEndDateTime { get; set; } + public bool ResourceLinkAvailableUserEndDateTime { get; set; } + public bool ResourceLinkSubmissionStartDateTime { get; set; } + public bool ResourceLinkSubmissionUserStartDateTime { get; set; } + public bool ResourceLinkSubmissionEndDateTime { get; set; } + public bool ResourceLinkSubmissionUserEndDateTime { get; set; } + public bool ResourceLinkLineItemReleaseDateTime { get; set; } + public bool ResourceLinkLineItemUserReleaseDateTime { get; set; } + public bool ResourceLinkIdHistory { get; set; } - public bool ToolPlatformProductFamilyCode { get; set; } - public bool ToolPlatformProductVersion { get; set; } - public bool ToolPlatformProductInstanceGuid { get; set; } - public bool ToolPlatformProductInstanceName { get; set; } - public bool ToolPlatformProductInstanceDescription { get; set; } - public bool ToolPlatformProductInstanceUrl { get; set; } - public bool ToolPlatformProductInstanceContactEmail { get; set; } - } + public bool ToolPlatformProductFamilyCode { get; set; } + public bool ToolPlatformProductVersion { get; set; } + public bool ToolPlatformProductInstanceGuid { get; set; } + public bool ToolPlatformProductInstanceName { get; set; } + public bool ToolPlatformProductInstanceDescription { get; set; } + public bool ToolPlatformProductInstanceUrl { get; set; } + public bool ToolPlatformProductInstanceContactEmail { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/Deployment.cs b/NP.Lti13Platform.Core/Models/Deployment.cs index cf68980..06ab839 100644 --- a/NP.Lti13Platform.Core/Models/Deployment.cs +++ b/NP.Lti13Platform.Core/Models/Deployment.cs @@ -1,11 +1,10 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Deployment { - public class Deployment - { - public required string Id { get; set; } + public required string Id { get; set; } - public required string ToolId { get; set; } + public required string ToolId { get; set; } - public IDictionary? Custom { get; set; } - } + public IDictionary? Custom { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/Grade.cs b/NP.Lti13Platform.Core/Models/Grade.cs index c2b1419..0d7be4b 100644 --- a/NP.Lti13Platform.Core/Models/Grade.cs +++ b/NP.Lti13Platform.Core/Models/Grade.cs @@ -1,46 +1,46 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Grade { - public class Grade - { - public required string LineItemId { get; set; } + public required string LineItemId { get; set; } + + public required string UserId { get; set; } - public required string UserId { get; set; } + public string? ScoringUserId { get; set; } - public string? ScoringUserId { get; set; } + public decimal? ResultScore { get; set; } - public decimal? ResultScore { get; set; } + public decimal? ResultMaximum { get; set; } - public decimal? ResultMaximum { get; set; } + public string? Comment { get; set; } - public string? Comment { get; set; } + public DateTime Timestamp { get; set; } - public DateTime Timestamp { get; set; } + public DateTime? ReleaseDateTime { get; set; } - public DateTime? ReleaseDateTime { get; set; } + public ActivityProgress ActivityProgress { get; set; } - public ActivityProgress ActivityProgress { get; set; } + public GradingProgress GradingProgress { get; set; } - public GradingProgress GradingProgress { get; set; } + public DateTime? StartedAt { get; set; } - public DateTime? StartedAt { get; set; } + public DateTime? SubmittedAt { get; set; } +} - public DateTime? SubmittedAt { get; set; } - } +public enum ActivityProgress +{ + Initialized, + Started, + InProgress, + Submitted, + Completed +} - public enum ActivityProgress - { - Initialized, - Started, - InProgress, - Submitted, - Completed - } - public enum GradingProgress - { - FullyGraded, - Pending, - PendingManual, - Failed, - NotReady - } +public enum GradingProgress +{ + FullyGraded, + Pending, + PendingManual, + Failed, + NotReady } diff --git a/NP.Lti13Platform.Core/Models/Jwks.cs b/NP.Lti13Platform.Core/Models/Jwks.cs index bee42db..9b1fda4 100644 --- a/NP.Lti13Platform.Core/Models/Jwks.cs +++ b/NP.Lti13Platform.Core/Models/Jwks.cs @@ -1,51 +1,50 @@ using Microsoft.IdentityModel.Tokens; using System.Net.Http.Json; -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public abstract class Jwks +{ + /// + /// Create an instance of Jwks using the provided key or uri. + /// + /// The public key or JWKS uri to use. + /// An instance of Jwks depending on the type of string provided. + static Jwks Create(string keyOrUri) => Uri.IsWellFormedUriString(keyOrUri, UriKind.Absolute) ? + new JwksUri { Uri = keyOrUri } : + new JwtPublicKey { PublicKey = keyOrUri }; + + public abstract Task> GetKeysAsync(CancellationToken cancellationToken = default); + + public static implicit operator Jwks(string keyOrUri) => Create(keyOrUri); +} + +public class JwtPublicKey : Jwks { - public abstract class Jwks + public required string PublicKey { get; set; } + + public override Task> GetKeysAsync(CancellationToken cancellationToken = default) { - /// - /// Create an instance of Jwks using the provided key or uri. - /// - /// The public key or JWKS uri to use. - /// An instance of Jwks depending on the type of string provided. - static Jwks Create(string keyOrUri) => Uri.IsWellFormedUriString(keyOrUri, UriKind.Absolute) ? - new JwksUri { Uri = keyOrUri } : - new JwtPublicKey { PublicKey = keyOrUri }; - - public abstract Task> GetKeysAsync(CancellationToken cancellationToken = default); - - public static implicit operator Jwks(string keyOrUri) => Create(keyOrUri); + return Task.FromResult>([new JsonWebKey(PublicKey)]); } +} - public class JwtPublicKey : Jwks - { - public required string PublicKey { get; set; } +public class JwksUri : Jwks +{ + private static readonly HttpClient httpClient = new(); - public override Task> GetKeysAsync(CancellationToken cancellationToken = default) - { - return Task.FromResult>([new JsonWebKey(PublicKey)]); - } - } + public required string Uri { get; set; } - public class JwksUri : Jwks + public override async Task> GetKeysAsync(CancellationToken cancellationToken = default) { - private static readonly HttpClient httpClient = new(); - - public required string Uri { get; set; } + var httpResponse = await httpClient.GetAsync(Uri, cancellationToken); + var result = await httpResponse.Content.ReadFromJsonAsync(cancellationToken); - public override async Task> GetKeysAsync(CancellationToken cancellationToken = default) + if (result != null) { - var httpResponse = await httpClient.GetAsync(Uri, cancellationToken); - var result = await httpResponse.Content.ReadFromJsonAsync(cancellationToken); - - if (result != null) - { - return result.Keys; - } - - return []; + return result.Keys; } + + return []; } } diff --git a/NP.Lti13Platform.Core/Models/LineItem.cs b/NP.Lti13Platform.Core/Models/LineItem.cs index eb3f31f..95947da 100644 --- a/NP.Lti13Platform.Core/Models/LineItem.cs +++ b/NP.Lti13Platform.Core/Models/LineItem.cs @@ -1,29 +1,28 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class LineItem { - public class LineItem - { - public required string Id { get; set; } + public required string Id { get; set; } - public required string DeploymentId { get; set; } + public required string DeploymentId { get; set; } - public required string ContextId { get; set; } + public required string ContextId { get; set; } - public required decimal ScoreMaximum { get; set; } + public required decimal ScoreMaximum { get; set; } - public required string Label { get; set; } + public required string Label { get; set; } - public string? ResourceLinkId { get; set; } + public string? ResourceLinkId { get; set; } - public string? ResourceId { get; set; } + public string? ResourceId { get; set; } - public string? Tag { get; set; } + public string? Tag { get; set; } - public bool? GradesReleased { get; set; } + public bool? GradesReleased { get; set; } - public DateTime? GradesReleasedDateTime { get; set; } + public DateTime? GradesReleasedDateTime { get; set; } - public DateTime? StartDateTime { get; set; } + public DateTime? StartDateTime { get; set; } - public DateTime? EndDateTime { get; set; } - } + public DateTime? EndDateTime { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/Membership.cs b/NP.Lti13Platform.Core/Models/Membership.cs index bbbf0e1..cf72189 100644 --- a/NP.Lti13Platform.Core/Models/Membership.cs +++ b/NP.Lti13Platform.Core/Models/Membership.cs @@ -1,21 +1,20 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Membership { - public class Membership - { - public required string ContextId { get; set; } + public required string ContextId { get; set; } - public required string UserId { get; set; } + public required string UserId { get; set; } - public required MembershipStatus Status { get; set; } + public required MembershipStatus Status { get; set; } - public required IEnumerable Roles { get; set; } + public required IEnumerable Roles { get; set; } - public required IEnumerable MentoredUserIds { get; set; } - } + public required IEnumerable MentoredUserIds { get; set; } +} - public enum MembershipStatus - { - Active, - Inactive - } +public enum MembershipStatus +{ + Active, + Inactive } diff --git a/NP.Lti13Platform.Core/Models/PartialList.cs b/NP.Lti13Platform.Core/Models/PartialList.cs index f009ba0..94db5fe 100644 --- a/NP.Lti13Platform.Core/Models/PartialList.cs +++ b/NP.Lti13Platform.Core/Models/PartialList.cs @@ -1,11 +1,9 @@ -namespace NP.Lti13Platform.Core.Models -{ - public class PartialList - { - public required IEnumerable Items { get; set; } - public int TotalItems { get; set; } +namespace NP.Lti13Platform.Core.Models; - public static PartialList Empty => new() { Items = [], TotalItems = 0 }; - } +public class PartialList +{ + public required IEnumerable Items { get; set; } + public int TotalItems { get; set; } + public static PartialList Empty => new() { Items = [], TotalItems = 0 }; } diff --git a/NP.Lti13Platform.Core/Models/Platform.cs b/NP.Lti13Platform.Core/Models/Platform.cs index f314fb9..e56a181 100644 --- a/NP.Lti13Platform.Core/Models/Platform.cs +++ b/NP.Lti13Platform.Core/Models/Platform.cs @@ -1,19 +1,18 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Platform { - public class Platform - { - public required string Guid { get; set; } + public required string Guid { get; set; } - public string? ContactEmail { get; set; } + public string? ContactEmail { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - public string? Url { get; set; } + public string? Url { get; set; } - public string? ProductFamilyCode { get; set; } + public string? ProductFamilyCode { get; set; } - public string? Version { get; set; } - } + public string? Version { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/ResourceLink.cs b/NP.Lti13Platform.Core/Models/ResourceLink.cs index 834974c..aaf9d37 100644 --- a/NP.Lti13Platform.Core/Models/ResourceLink.cs +++ b/NP.Lti13Platform.Core/Models/ResourceLink.cs @@ -1,29 +1,28 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class ResourceLink { - public class ResourceLink - { - public required string Id { get; set; } + public required string Id { get; set; } - public required string DeploymentId { get; set; } + public required string DeploymentId { get; set; } - public required string ContextId { get; set; } + public required string ContextId { get; set; } - public string? Url { get; set; } + public string? Url { get; set; } - public string? Title { get; set; } + public string? Title { get; set; } - public string? Text { get; set; } + public string? Text { get; set; } - public DateTime? AvailableStartDateTime { get; set; } + public DateTime? AvailableStartDateTime { get; set; } - public DateTime? AvailableEndDateTime { get; set; } + public DateTime? AvailableEndDateTime { get; set; } - public DateTime? SubmissionStartDateTime { get; set; } + public DateTime? SubmissionStartDateTime { get; set; } - public DateTime? SubmissionEndDateTime { get; set; } + public DateTime? SubmissionEndDateTime { get; set; } - public IEnumerable? ClonedIdHistory { get; set; } + public IEnumerable? ClonedIdHistory { get; set; } - public IDictionary? Custom { get; set; } - } + public IDictionary? Custom { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/ServiceToken.cs b/NP.Lti13Platform.Core/Models/ServiceToken.cs index 25e795d..78e10e2 100644 --- a/NP.Lti13Platform.Core/Models/ServiceToken.cs +++ b/NP.Lti13Platform.Core/Models/ServiceToken.cs @@ -1,11 +1,10 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class ServiceToken { - public class ServiceToken - { - public required string Id { get; set; } + public required string Id { get; set; } - public required string ToolId { get; set; } + public required string ToolId { get; set; } - public required DateTime Expiration { get; set; } - } + public required DateTime Expiration { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/Tool.cs b/NP.Lti13Platform.Core/Models/Tool.cs index 794f9cd..cc28a9c 100644 --- a/NP.Lti13Platform.Core/Models/Tool.cs +++ b/NP.Lti13Platform.Core/Models/Tool.cs @@ -1,23 +1,22 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class Tool { - public class Tool - { - public required string Id { get; set; } + public required string Id { get; set; } - public required string ClientId { get; set; } + public required string ClientId { get; set; } - public required string OidcInitiationUrl { get; set; } + public required string OidcInitiationUrl { get; set; } - public required string DeepLinkUrl { get; set; } + public required string DeepLinkUrl { get; set; } - public required string LaunchUrl { get; set; } + public required string LaunchUrl { get; set; } - public IEnumerable RedirectUrls => new[] { DeepLinkUrl, LaunchUrl }.Where(x => x != null).Select(x => x!); + public IEnumerable RedirectUrls => new[] { DeepLinkUrl, LaunchUrl }.Where(x => x != null).Select(x => x!); - public Jwks? Jwks { get; set; } + public Jwks? Jwks { get; set; } - public IDictionary? Custom { get; set; } + public IDictionary? Custom { get; set; } - public IEnumerable ServiceScopes { get; set; } = []; - } + public IEnumerable ServiceScopes { get; set; } = []; } diff --git a/NP.Lti13Platform.Core/Models/User.cs b/NP.Lti13Platform.Core/Models/User.cs index 816e170..5a49c41 100644 --- a/NP.Lti13Platform.Core/Models/User.cs +++ b/NP.Lti13Platform.Core/Models/User.cs @@ -1,68 +1,67 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class User { - public class User - { - public required string Id { get; set; } + public required string Id { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - public string? GivenName { get; set; } + public string? GivenName { get; set; } - public string? FamilyName { get; set; } + public string? FamilyName { get; set; } - public string? MiddleName { get; set; } + public string? MiddleName { get; set; } - public string? Nickname { get; set; } + public string? Nickname { get; set; } - public string? PreferredUsername { get; set; } + public string? PreferredUsername { get; set; } - public string? Profile { get; set; } + public string? Profile { get; set; } - public string? Picture { get; set; } + public string? Picture { get; set; } - public string? Website { get; set; } + public string? Website { get; set; } - public string? Email { get; set; } + public string? Email { get; set; } - public bool? EmailVerified { get; set; } + public bool? EmailVerified { get; set; } - public string? Gender { get; set; } + public string? Gender { get; set; } - public DateOnly? Birthdate { get; set; } + public DateOnly? Birthdate { get; set; } - public string? TimeZone { get; set; } + public string? TimeZone { get; set; } - public string? Locale { get; set; } + public string? Locale { get; set; } - public string? PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } - public bool? PhoneNumberVerified { get; set; } + public bool? PhoneNumberVerified { get; set; } - public DateTime? UpdatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } - public Address? Address { get; set; } + public Address? Address { get; set; } - public string? ImageUrl { get; set; } + public string? ImageUrl { get; set; } - public string? Username { get; set; } + public string? Username { get; set; } - public IEnumerable Orgs { get; set; } = []; + public IEnumerable Orgs { get; set; } = []; - public IEnumerable OneRosterGrades { get; set; } = []; - } + public IEnumerable OneRosterGrades { get; set; } = []; +} - public class Address - { - public string? Formatted { get; set; } +public class Address +{ + public string? Formatted { get; set; } - public string? StreetAddress { get; set; } + public string? StreetAddress { get; set; } - public string? Locality { get; set; } + public string? Locality { get; set; } - public string? Region { get; set; } + public string? Region { get; set; } - public string? PostalCode { get; set; } + public string? PostalCode { get; set; } - public string? Country { get; set; } - } + public string? Country { get; set; } } diff --git a/NP.Lti13Platform.Core/Models/UserPermissions.cs b/NP.Lti13Platform.Core/Models/UserPermissions.cs index b19c031..832e78c 100644 --- a/NP.Lti13Platform.Core/Models/UserPermissions.cs +++ b/NP.Lti13Platform.Core/Models/UserPermissions.cs @@ -1,31 +1,32 @@ -namespace NP.Lti13Platform.Core.Models +namespace NP.Lti13Platform.Core.Models; + +public class UserPermissions { - public class UserPermissions - { - public bool Address { get; set; } - public bool AddressCountry { get; set; } - public bool AddressFormatted { get; set; } - public bool AddressLocality { get; set; } - public bool AddressPostalCode { get; set; } - public bool AddressRegion { get; set; } - public bool AddressStreetAddress { get; set; } - public bool Birthdate { get; set; } - public bool Email { get; set; } - public bool EmailVerified { get; set; } - public bool FamilyName { get; set; } - public bool Gender { get; set; } - public bool GivenName { get; set; } - public bool Locale { get; set; } - public bool MiddleName { get; set; } - public bool Name { get; set; } - public bool Nickname { get; set; } - public bool PhoneNumber { get; set; } - public bool PhoneNumberVerified { get; set; } - public bool Picture { get; set; } - public bool PreferredUsername { get; set; } - public bool Profile { get; set; } - public bool UpdatedAt { get; set; } - public bool Website { get; set; } - public bool TimeZone { get; set; } - } + public required string UserId { get; set; } + + public bool Address { get; set; } + public bool AddressCountry { get; set; } + public bool AddressFormatted { get; set; } + public bool AddressLocality { get; set; } + public bool AddressPostalCode { get; set; } + public bool AddressRegion { get; set; } + public bool AddressStreetAddress { get; set; } + public bool Birthdate { get; set; } + public bool Email { get; set; } + public bool EmailVerified { get; set; } + public bool FamilyName { get; set; } + public bool Gender { get; set; } + public bool GivenName { get; set; } + public bool Locale { get; set; } + public bool MiddleName { get; set; } + public bool Name { get; set; } + public bool Nickname { get; set; } + public bool PhoneNumber { get; set; } + public bool PhoneNumberVerified { get; set; } + public bool Picture { get; set; } + public bool PreferredUsername { get; set; } + public bool Profile { get; set; } + public bool UpdatedAt { get; set; } + public bool Website { get; set; } + public bool TimeZone { get; set; } } diff --git a/NP.Lti13Platform.Core/Populators/ContextPopulator.cs b/NP.Lti13Platform.Core/Populators/ContextPopulator.cs index 365a269..a7ad960 100644 --- a/NP.Lti13Platform.Core/Populators/ContextPopulator.cs +++ b/NP.Lti13Platform.Core/Populators/ContextPopulator.cs @@ -1,44 +1,43 @@ using System.Text.Json.Serialization; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public interface IContextMessage { - public interface IContextMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/context")] - public MessageContext? Context { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/context")] + public MessageContext? Context { get; set; } - public class MessageContext - { - [JsonPropertyName("id")] - public required string Id { get; set; } + public class MessageContext + { + [JsonPropertyName("id")] + public required string Id { get; set; } - [JsonPropertyName("label")] - public string? Label { get; set; } + [JsonPropertyName("label")] + public string? Label { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("type")] - public IEnumerable Types { get; set; } = []; - } + [JsonPropertyName("type")] + public IEnumerable Types { get; set; } = []; } +} - public class ContextPopulator() : Populator +public class ContextPopulator() : Populator +{ + public override async Task PopulateAsync(IContextMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IContextMessage obj, MessageScope scope, CancellationToken cancellationToken = default) + if (scope.Context != null) { - if (scope.Context != null) + obj.Context = new IContextMessage.MessageContext { - obj.Context = new IContextMessage.MessageContext - { - Id = scope.Context.Id, - Label = scope.Context.Label, - Title = scope.Context.Title, - Types = scope.Context.Types.ToArray() - }; - } - - await Task.CompletedTask; + Id = scope.Context.Id, + Label = scope.Context.Label, + Title = scope.Context.Title, + Types = scope.Context.Types.ToArray() + }; } + + await Task.CompletedTask; } } diff --git a/NP.Lti13Platform.Core/Populators/CustomPopulator.cs b/NP.Lti13Platform.Core/Populators/CustomPopulator.cs index 599bade..967c739 100644 --- a/NP.Lti13Platform.Core/Populators/CustomPopulator.cs +++ b/NP.Lti13Platform.Core/Populators/CustomPopulator.cs @@ -4,132 +4,131 @@ using NP.Lti13Platform.Core.Services; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public interface ICustomMessage { - public interface ICustomMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/custom")] - public IDictionary? Custom { get; set; } - } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/custom")] + public IDictionary? Custom { get; set; } +} - public class CustomPopulator(ILti13PlatformService platformService, ILti13CoreDataService dataService) : Populator +public class CustomPopulator(ILti13PlatformService platformService, ILti13CoreDataService dataService) : Populator +{ + private static readonly IEnumerable LineItemAttemptGradeVariables = [ + Lti13ResourceLinkVariables.AvailableUserStartDateTime, + Lti13ResourceLinkVariables.AvailableUserEndDateTime, + Lti13ResourceLinkVariables.SubmissionUserStartDateTime, + Lti13ResourceLinkVariables.SubmissionUserEndDateTime, + Lti13ResourceLinkVariables.LineItemReleaseDateTime, + Lti13ResourceLinkVariables.LineItemUserReleaseDateTime]; + + public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - private static readonly IEnumerable LineItemAttemptGradeVariables = [ - Lti13ResourceLinkVariables.AvailableUserStartDateTime, - Lti13ResourceLinkVariables.AvailableUserEndDateTime, - Lti13ResourceLinkVariables.SubmissionUserStartDateTime, - Lti13ResourceLinkVariables.SubmissionUserEndDateTime, - Lti13ResourceLinkVariables.LineItemReleaseDateTime, - Lti13ResourceLinkVariables.LineItemUserReleaseDateTime]; - - public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope, CancellationToken cancellationToken = default) - { - var customDictionary = scope.Tool.Custom.Merge(scope.Deployment.Custom).Merge(scope.ResourceLink?.Custom); + var customDictionary = scope.Tool.Custom.Merge(scope.Deployment.Custom).Merge(scope.ResourceLink?.Custom); - if (customDictionary == null) - { - return; - } + if (customDictionary == null) + { + return; + } - Platform? platform = null; - if (customDictionary.Values.Any(v => v.StartsWith(Lti13ToolPlatformVariables.Version.Split('.')[0])) == true) - { - platform = await platformService.GetPlatformAsync(scope.Tool.ClientId, cancellationToken); - } + Platform? platform = null; + if (customDictionary.Values.Any(v => v.StartsWith(Lti13ToolPlatformVariables.Version.Split('.')[0])) == true) + { + platform = await platformService.GetPlatformAsync(scope.Tool.ClientId, cancellationToken); + } - IEnumerable mentoredUserIds = []; - if (customDictionary.Values.Any(v => v == Lti13UserVariables.ScopeMentor) && scope.Context != null) + IEnumerable mentoredUserIds = []; + if (customDictionary.Values.Any(v => v == Lti13UserVariables.ScopeMentor) && scope.Context != null) + { + var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.User.Id, cancellationToken); + if (membership != null && membership.Roles.Contains(Lti13ContextRoles.Mentor)) { - var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.User.Id, cancellationToken); - if (membership != null && membership.Roles.Contains(Lti13ContextRoles.Mentor)) - { - mentoredUserIds = membership.MentoredUserIds; - } + mentoredUserIds = membership.MentoredUserIds; } + } - IEnumerable actualUserMentoredUserIds = []; - if (customDictionary.Values.Any(v => v == Lti13ActualUserVariables.ScopeMentor) && scope.Context != null && scope.UserScope.ActualUser != null) + IEnumerable actualUserMentoredUserIds = []; + if (customDictionary.Values.Any(v => v == Lti13ActualUserVariables.ScopeMentor) && scope.Context != null && scope.UserScope.ActualUser != null) + { + var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.ActualUser.Id, cancellationToken); + if (membership != null && membership.Roles.Contains(Lti13ContextRoles.Mentor)) { - var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.ActualUser.Id, cancellationToken); - if (membership != null && membership.Roles.Contains(Lti13ContextRoles.Mentor)) - { - actualUserMentoredUserIds = membership.MentoredUserIds; - } + actualUserMentoredUserIds = membership.MentoredUserIds; } + } - LineItem? lineItem = null; - Attempt? attempt = null; - Grade? grade = null; - if (customDictionary.Values.Any(v => LineItemAttemptGradeVariables.Contains(v)) && scope.Context != null && scope.ResourceLink != null) + LineItem? lineItem = null; + Attempt? attempt = null; + Grade? grade = null; + if (customDictionary.Values.Any(v => LineItemAttemptGradeVariables.Contains(v)) && scope.Context != null && scope.ResourceLink != null) + { + var lineItems = await dataService.GetLineItemsAsync(scope.Deployment.Id, scope.Context.Id, 0, 1, null, scope.ResourceLink.Id, null, cancellationToken); + if (lineItems.TotalItems == 1) { - var lineItems = await dataService.GetLineItemsAsync(scope.Deployment.Id, scope.Context.Id, 0, 1, null, scope.ResourceLink.Id, null, cancellationToken); - if (lineItems.TotalItems == 1) - { - lineItem = lineItems.Items.Single(); - - grade = await dataService.GetGradeAsync(lineItem.Id, scope.UserScope.User.Id, cancellationToken); - } + lineItem = lineItems.Items.Single(); - attempt = await dataService.GetAttemptAsync(scope.ResourceLink.Id, scope.UserScope.User.Id, cancellationToken); + grade = await dataService.GetGradeAsync(lineItem.Id, scope.UserScope.User.Id, cancellationToken); } - var customPermissions = await dataService.GetCustomPermissions(scope.Deployment.Id, cancellationToken); + attempt = await dataService.GetAttemptAsync(scope.ResourceLink.Id, scope.UserScope.User.Id, cancellationToken); + } - foreach (var kvp in customDictionary.Where(kvp => kvp.Value.StartsWith('$'))) - { - // TODO: LIS variables - customDictionary[kvp.Key] = kvp.Value switch - { - Lti13UserVariables.Id when customPermissions.UserId && !scope.UserScope.IsAnonymous => scope.UserScope.User.Id, - Lti13UserVariables.Image when customPermissions.UserImage && !scope.UserScope.IsAnonymous => scope.UserScope.User.ImageUrl, - Lti13UserVariables.Username when customPermissions.UserUsername && !scope.UserScope.IsAnonymous => scope.UserScope.User.Username, - Lti13UserVariables.Org when customPermissions.UserOrg && !scope.UserScope.IsAnonymous => string.Join(',', scope.UserScope.User.Orgs), - Lti13UserVariables.ScopeMentor when customPermissions.UserScopeMentor && !scope.UserScope.IsAnonymous => string.Join(',', mentoredUserIds), - Lti13UserVariables.GradeLevelsOneRoster when customPermissions.UserGradeLevelsOneRoster && !scope.UserScope.IsAnonymous => string.Join(',', scope.UserScope.User.OneRosterGrades), - - Lti13ActualUserVariables.Id when customPermissions.ActualUserId && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.Id, - Lti13ActualUserVariables.Image when customPermissions.ActualUserImage && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.ImageUrl, - Lti13ActualUserVariables.Username when customPermissions.ActualUserUsername && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.Username, - Lti13ActualUserVariables.Org when customPermissions.ActualUserOrg && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser != null ? string.Join(',', scope.UserScope.ActualUser.Orgs) : string.Empty, - Lti13ActualUserVariables.ScopeMentor when customPermissions.ActualUserScopeMentor && !scope.UserScope.IsAnonymous => string.Join(',', actualUserMentoredUserIds), - Lti13ActualUserVariables.GradeLevelsOneRoster when customPermissions.ActualUserGradeLevelsOneRoster && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser != null ? string.Join(',', scope.UserScope.ActualUser.OneRosterGrades) : string.Empty, - - Lti13ContextVariables.Id when customPermissions.ContextId => scope.Context?.Id, - Lti13ContextVariables.Org when customPermissions.ContextOrg => scope.Context != null ? string.Join(',', scope.Context.Orgs) : string.Empty, - Lti13ContextVariables.Type when customPermissions.ContextType => scope.Context != null ? string.Join(',', scope.Context.Types) : string.Empty, - Lti13ContextVariables.Label when customPermissions.ContextLabel => scope.Context?.Label, - Lti13ContextVariables.Title when customPermissions.ContextTitle => scope.Context?.Title, - Lti13ContextVariables.SourcedId when customPermissions.ContextSourcedId => scope.Context?.SourcedId, - Lti13ContextVariables.IdHistory when customPermissions.ContextIdHistory => scope.Context != null ? string.Join(',', scope.Context.ClonedIdHistory) : string.Empty, - Lti13ContextVariables.GradeLevelsOneRoster when customPermissions.ContextGradeLevelsOneRoster => scope.Context != null ? string.Join(',', scope.Context.OneRosterGrades) : string.Empty, - - Lti13ResourceLinkVariables.Id when customPermissions.ResourceLinkId => scope.ResourceLink?.Id, - Lti13ResourceLinkVariables.Title when customPermissions.ResourceLinkTitle => scope.ResourceLink?.Title, - Lti13ResourceLinkVariables.Description when customPermissions.ResourceLinkDescription => scope.ResourceLink?.Text, - Lti13ResourceLinkVariables.AvailableStartDateTime when customPermissions.ResourceLinkAvailableStartDateTime => scope.ResourceLink?.AvailableStartDateTime?.ToString("O"), - Lti13ResourceLinkVariables.AvailableUserStartDateTime when customPermissions.ResourceLinkAvailableUserStartDateTime => attempt?.AvailableStartDateTime?.ToString("O"), - Lti13ResourceLinkVariables.AvailableEndDateTime when customPermissions.ResourceLinkAvailableEndDateTime => scope.ResourceLink?.AvailableEndDateTime?.ToString("O"), - Lti13ResourceLinkVariables.AvailableUserEndDateTime when customPermissions.ResourceLinkAvailableUserEndDateTime => attempt?.AvailableEndDateTime?.ToString("O"), - Lti13ResourceLinkVariables.SubmissionStartDateTime when customPermissions.ResourceLinkSubmissionStartDateTime => scope.ResourceLink?.SubmissionStartDateTime?.ToString("O"), - Lti13ResourceLinkVariables.SubmissionUserStartDateTime when customPermissions.ResourceLinkSubmissionUserStartDateTime => attempt?.SubmisstionStartDateTime?.ToString("O"), - Lti13ResourceLinkVariables.SubmissionEndDateTime when customPermissions.ResourceLinkSubmissionEndDateTime => scope.ResourceLink?.SubmissionEndDateTime?.ToString("O"), - Lti13ResourceLinkVariables.SubmissionUserEndDateTime when customPermissions.ResourceLinkSubmissionUserEndDateTime => attempt?.SubmissionEndDateTime?.ToString("O"), - Lti13ResourceLinkVariables.LineItemReleaseDateTime when customPermissions.ResourceLinkLineItemReleaseDateTime => lineItem?.GradesReleasedDateTime?.ToString("O"), - Lti13ResourceLinkVariables.LineItemUserReleaseDateTime when customPermissions.ResourceLinkLineItemUserReleaseDateTime => grade?.ReleaseDateTime?.ToString("O"), - Lti13ResourceLinkVariables.IdHistory when customPermissions.ResourceLinkIdHistory => scope.ResourceLink?.ClonedIdHistory != null ? string.Join(',', scope.ResourceLink.ClonedIdHistory) : string.Empty, - - Lti13ToolPlatformVariables.ProductFamilyCode when customPermissions.ToolPlatformProductFamilyCode => platform?.ProductFamilyCode, - Lti13ToolPlatformVariables.Version when customPermissions.ToolPlatformProductVersion => platform?.Version, - Lti13ToolPlatformVariables.InstanceGuid when customPermissions.ToolPlatformProductInstanceGuid => platform?.Guid, - Lti13ToolPlatformVariables.InstanceName when customPermissions.ToolPlatformProductInstanceName => platform?.Name, - Lti13ToolPlatformVariables.InstanceDescription when customPermissions.ToolPlatformProductInstanceDescription => platform?.Description, - Lti13ToolPlatformVariables.InstanceUrl when customPermissions.ToolPlatformProductInstanceUrl => platform?.Url, - Lti13ToolPlatformVariables.InstanceContactEmail when customPermissions.ToolPlatformProductInstanceContactEmail => platform?.ContactEmail, - _ => kvp.Value - } ?? string.Empty; - } + var customPermissions = await dataService.GetCustomPermissions(scope.Deployment.Id, cancellationToken); - obj.Custom = obj.Custom.Merge(customDictionary); + foreach (var kvp in customDictionary.Where(kvp => kvp.Value.StartsWith('$'))) + { + // TODO: LIS variables + customDictionary[kvp.Key] = kvp.Value switch + { + Lti13UserVariables.Id when customPermissions.UserId && !scope.UserScope.IsAnonymous => scope.UserScope.User.Id, + Lti13UserVariables.Image when customPermissions.UserImage && !scope.UserScope.IsAnonymous => scope.UserScope.User.ImageUrl, + Lti13UserVariables.Username when customPermissions.UserUsername && !scope.UserScope.IsAnonymous => scope.UserScope.User.Username, + Lti13UserVariables.Org when customPermissions.UserOrg && !scope.UserScope.IsAnonymous => string.Join(',', scope.UserScope.User.Orgs), + Lti13UserVariables.ScopeMentor when customPermissions.UserScopeMentor && !scope.UserScope.IsAnonymous => string.Join(',', mentoredUserIds), + Lti13UserVariables.GradeLevelsOneRoster when customPermissions.UserGradeLevelsOneRoster && !scope.UserScope.IsAnonymous => string.Join(',', scope.UserScope.User.OneRosterGrades), + + Lti13ActualUserVariables.Id when customPermissions.ActualUserId && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.Id, + Lti13ActualUserVariables.Image when customPermissions.ActualUserImage && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.ImageUrl, + Lti13ActualUserVariables.Username when customPermissions.ActualUserUsername && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.Username, + Lti13ActualUserVariables.Org when customPermissions.ActualUserOrg && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser != null ? string.Join(',', scope.UserScope.ActualUser.Orgs) : string.Empty, + Lti13ActualUserVariables.ScopeMentor when customPermissions.ActualUserScopeMentor && !scope.UserScope.IsAnonymous => string.Join(',', actualUserMentoredUserIds), + Lti13ActualUserVariables.GradeLevelsOneRoster when customPermissions.ActualUserGradeLevelsOneRoster && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser != null ? string.Join(',', scope.UserScope.ActualUser.OneRosterGrades) : string.Empty, + + Lti13ContextVariables.Id when customPermissions.ContextId => scope.Context?.Id, + Lti13ContextVariables.Org when customPermissions.ContextOrg => scope.Context != null ? string.Join(',', scope.Context.Orgs) : string.Empty, + Lti13ContextVariables.Type when customPermissions.ContextType => scope.Context != null ? string.Join(',', scope.Context.Types) : string.Empty, + Lti13ContextVariables.Label when customPermissions.ContextLabel => scope.Context?.Label, + Lti13ContextVariables.Title when customPermissions.ContextTitle => scope.Context?.Title, + Lti13ContextVariables.SourcedId when customPermissions.ContextSourcedId => scope.Context?.SourcedId, + Lti13ContextVariables.IdHistory when customPermissions.ContextIdHistory => scope.Context != null ? string.Join(',', scope.Context.ClonedIdHistory) : string.Empty, + Lti13ContextVariables.GradeLevelsOneRoster when customPermissions.ContextGradeLevelsOneRoster => scope.Context != null ? string.Join(',', scope.Context.OneRosterGrades) : string.Empty, + + Lti13ResourceLinkVariables.Id when customPermissions.ResourceLinkId => scope.ResourceLink?.Id, + Lti13ResourceLinkVariables.Title when customPermissions.ResourceLinkTitle => scope.ResourceLink?.Title, + Lti13ResourceLinkVariables.Description when customPermissions.ResourceLinkDescription => scope.ResourceLink?.Text, + Lti13ResourceLinkVariables.AvailableStartDateTime when customPermissions.ResourceLinkAvailableStartDateTime => scope.ResourceLink?.AvailableStartDateTime?.ToString("O"), + Lti13ResourceLinkVariables.AvailableUserStartDateTime when customPermissions.ResourceLinkAvailableUserStartDateTime => attempt?.AvailableStartDateTime?.ToString("O"), + Lti13ResourceLinkVariables.AvailableEndDateTime when customPermissions.ResourceLinkAvailableEndDateTime => scope.ResourceLink?.AvailableEndDateTime?.ToString("O"), + Lti13ResourceLinkVariables.AvailableUserEndDateTime when customPermissions.ResourceLinkAvailableUserEndDateTime => attempt?.AvailableEndDateTime?.ToString("O"), + Lti13ResourceLinkVariables.SubmissionStartDateTime when customPermissions.ResourceLinkSubmissionStartDateTime => scope.ResourceLink?.SubmissionStartDateTime?.ToString("O"), + Lti13ResourceLinkVariables.SubmissionUserStartDateTime when customPermissions.ResourceLinkSubmissionUserStartDateTime => attempt?.SubmisstionStartDateTime?.ToString("O"), + Lti13ResourceLinkVariables.SubmissionEndDateTime when customPermissions.ResourceLinkSubmissionEndDateTime => scope.ResourceLink?.SubmissionEndDateTime?.ToString("O"), + Lti13ResourceLinkVariables.SubmissionUserEndDateTime when customPermissions.ResourceLinkSubmissionUserEndDateTime => attempt?.SubmissionEndDateTime?.ToString("O"), + Lti13ResourceLinkVariables.LineItemReleaseDateTime when customPermissions.ResourceLinkLineItemReleaseDateTime => lineItem?.GradesReleasedDateTime?.ToString("O"), + Lti13ResourceLinkVariables.LineItemUserReleaseDateTime when customPermissions.ResourceLinkLineItemUserReleaseDateTime => grade?.ReleaseDateTime?.ToString("O"), + Lti13ResourceLinkVariables.IdHistory when customPermissions.ResourceLinkIdHistory => scope.ResourceLink?.ClonedIdHistory != null ? string.Join(',', scope.ResourceLink.ClonedIdHistory) : string.Empty, + + Lti13ToolPlatformVariables.ProductFamilyCode when customPermissions.ToolPlatformProductFamilyCode => platform?.ProductFamilyCode, + Lti13ToolPlatformVariables.Version when customPermissions.ToolPlatformProductVersion => platform?.Version, + Lti13ToolPlatformVariables.InstanceGuid when customPermissions.ToolPlatformProductInstanceGuid => platform?.Guid, + Lti13ToolPlatformVariables.InstanceName when customPermissions.ToolPlatformProductInstanceName => platform?.Name, + Lti13ToolPlatformVariables.InstanceDescription when customPermissions.ToolPlatformProductInstanceDescription => platform?.Description, + Lti13ToolPlatformVariables.InstanceUrl when customPermissions.ToolPlatformProductInstanceUrl => platform?.Url, + Lti13ToolPlatformVariables.InstanceContactEmail when customPermissions.ToolPlatformProductInstanceContactEmail => platform?.ContactEmail, + _ => kvp.Value + } ?? string.Empty; } + + obj.Custom = obj.Custom.Merge(customDictionary); } } diff --git a/NP.Lti13Platform.Core/Populators/ILaunchPresentationMessage.cs b/NP.Lti13Platform.Core/Populators/ILaunchPresentationMessage.cs index 18f2bc9..0958301 100644 --- a/NP.Lti13Platform.Core/Populators/ILaunchPresentationMessage.cs +++ b/NP.Lti13Platform.Core/Populators/ILaunchPresentationMessage.cs @@ -1,32 +1,31 @@ using System.Text.Json.Serialization; using NP.Lti13Platform.Core.Constants; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public interface ILaunchPresentationMessage { - public interface ILaunchPresentationMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/launch_presentation")] - public LaunchPresentationDefinition? LaunchPresentation { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/launch_presentation")] + public LaunchPresentationDefinition? LaunchPresentation { get; set; } - public class LaunchPresentationDefinition - { - /// - /// has the list of possible values. - /// - [JsonPropertyName("document_target")] - public string? DocumentTarget { get; set; } + public class LaunchPresentationDefinition + { + /// + /// has the list of possible values. + /// + [JsonPropertyName("document_target")] + public string? DocumentTarget { get; set; } - [JsonPropertyName("height")] - public double? Height { get; set; } + [JsonPropertyName("height")] + public double? Height { get; set; } - [JsonPropertyName("width")] - public double? Width { get; set; } + [JsonPropertyName("width")] + public double? Width { get; set; } - [JsonPropertyName("return_url")] - public string? ReturnUrl { get; set; } + [JsonPropertyName("return_url")] + public string? ReturnUrl { get; set; } - [JsonPropertyName("locale")] - public string? Locale { get; set; } - } + [JsonPropertyName("locale")] + public string? Locale { get; set; } } } diff --git a/NP.Lti13Platform.Core/Populators/LtiMessage.cs b/NP.Lti13Platform.Core/Populators/LtiMessage.cs index 4de5d6f..c6d2342 100644 --- a/NP.Lti13Platform.Core/Populators/LtiMessage.cs +++ b/NP.Lti13Platform.Core/Populators/LtiMessage.cs @@ -1,116 +1,115 @@ using System.Text.Json.Serialization; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public class LtiMessage { - public class LtiMessage - { - [JsonPropertyName("iss")] - public required string Issuer { get; set; } + [JsonPropertyName("iss")] + public required string Issuer { get; set; } - [JsonPropertyName("aud")] - public required string Audience { get; set; } + [JsonPropertyName("aud")] + public required string Audience { get; set; } - [JsonPropertyName("exp")] - public long ExpirationDateUnix => new DateTimeOffset(IssuedDate).ToUnixTimeSeconds(); + [JsonPropertyName("exp")] + public long ExpirationDateUnix => new DateTimeOffset(IssuedDate).ToUnixTimeSeconds(); - [JsonIgnore] - public DateTime ExpirationDate { get; set; } = DateTime.UtcNow; + [JsonIgnore] + public DateTime ExpirationDate { get; set; } = DateTime.UtcNow; - [JsonPropertyName("iat")] - public long IssuedDateUnix => new DateTimeOffset(IssuedDate).ToUnixTimeSeconds(); + [JsonPropertyName("iat")] + public long IssuedDateUnix => new DateTimeOffset(IssuedDate).ToUnixTimeSeconds(); - [JsonIgnore] - public DateTime IssuedDate { get; set; } = DateTime.UtcNow; + [JsonIgnore] + public DateTime IssuedDate { get; set; } = DateTime.UtcNow; - [JsonPropertyName("nonce")] - public required string Nonce { get; set; } + [JsonPropertyName("nonce")] + public required string Nonce { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/message_type")] - public required string MessageType { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/message_type")] + public required string MessageType { get; set; } - [JsonPropertyName("sub")] - public string? Subject { get; set; } + [JsonPropertyName("sub")] + public string? Subject { get; set; } - [JsonPropertyName("name")] - public string? Name { get; set; } + [JsonPropertyName("name")] + public string? Name { get; set; } - [JsonPropertyName("given_name")] - public string? GivenName { get; set; } + [JsonPropertyName("given_name")] + public string? GivenName { get; set; } - [JsonPropertyName("family_name")] - public string? FamilyName { get; set; } + [JsonPropertyName("family_name")] + public string? FamilyName { get; set; } - [JsonPropertyName("middle_name")] - public string? MiddleName { get; set; } + [JsonPropertyName("middle_name")] + public string? MiddleName { get; set; } - [JsonPropertyName("nickname")] - public string? Nickname { get; set; } + [JsonPropertyName("nickname")] + public string? Nickname { get; set; } - [JsonPropertyName("preferred_username")] - public string? PreferredUsername { get; set; } + [JsonPropertyName("preferred_username")] + public string? PreferredUsername { get; set; } - [JsonPropertyName("profile")] - public string? Profile { get; set; } + [JsonPropertyName("profile")] + public string? Profile { get; set; } - [JsonPropertyName("picture")] - public string? Picture { get; set; } + [JsonPropertyName("picture")] + public string? Picture { get; set; } - [JsonPropertyName("website")] - public string? Website { get; set; } + [JsonPropertyName("website")] + public string? Website { get; set; } - [JsonPropertyName("email")] - public string? Email { get; set; } + [JsonPropertyName("email")] + public string? Email { get; set; } - [JsonPropertyName("email_verified")] - public bool? EmailVerified { get; set; } + [JsonPropertyName("email_verified")] + public bool? EmailVerified { get; set; } - [JsonPropertyName("gender")] - public string? Gender { get; set; } + [JsonPropertyName("gender")] + public string? Gender { get; set; } - [JsonPropertyName("birthdate")] - public DateOnly? Birthdate { get; set; } + [JsonPropertyName("birthdate")] + public DateOnly? Birthdate { get; set; } - [JsonPropertyName("zoneinfo")] - public string? TimeZone { get; set; } + [JsonPropertyName("zoneinfo")] + public string? TimeZone { get; set; } - [JsonPropertyName("locale")] - public string? Locale { get; set; } + [JsonPropertyName("locale")] + public string? Locale { get; set; } - [JsonPropertyName("phone_number")] - public string? PhoneNumber { get; set; } + [JsonPropertyName("phone_number")] + public string? PhoneNumber { get; set; } - [JsonPropertyName("phone_number_verified")] - public bool? PhoneNumberVerified { get; set; } + [JsonPropertyName("phone_number_verified")] + public bool? PhoneNumberVerified { get; set; } - [JsonPropertyName("address")] - public AddressClaim? Address { get; set; } + [JsonPropertyName("address")] + public AddressClaim? Address { get; set; } - [JsonIgnore] - public DateTime? UpdatedAt { get; set; } + [JsonIgnore] + public DateTime? UpdatedAt { get; set; } - [JsonPropertyName("updated_at")] - public long? UpdatedAtUnix => !UpdatedAt.HasValue ? null : new DateTimeOffset(UpdatedAt.GetValueOrDefault()).ToUnixTimeSeconds(); - } + [JsonPropertyName("updated_at")] + public long? UpdatedAtUnix => !UpdatedAt.HasValue ? null : new DateTimeOffset(UpdatedAt.GetValueOrDefault()).ToUnixTimeSeconds(); +} - public class AddressClaim - { - [JsonPropertyName("formatted")] - public string? Formatted { get; set; } +public class AddressClaim +{ + [JsonPropertyName("formatted")] + public string? Formatted { get; set; } - [JsonPropertyName("street_address")] - public string? StreetAddress { get; set; } + [JsonPropertyName("street_address")] + public string? StreetAddress { get; set; } - [JsonPropertyName("locality")] - public string? Locality { get; set; } + [JsonPropertyName("locality")] + public string? Locality { get; set; } - [JsonPropertyName("region")] - public string? Region { get; set; } + [JsonPropertyName("region")] + public string? Region { get; set; } - [JsonPropertyName("postal_code")] - public string? PostalCode { get; set; } + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } - [JsonPropertyName("country")] - public string? Country { get; set; } - } + [JsonPropertyName("country")] + public string? Country { get; set; } } diff --git a/NP.Lti13Platform.Core/Populators/PlatformPopulator.cs b/NP.Lti13Platform.Core/Populators/PlatformPopulator.cs index 6e60d36..c537dfb 100644 --- a/NP.Lti13Platform.Core/Populators/PlatformPopulator.cs +++ b/NP.Lti13Platform.Core/Populators/PlatformPopulator.cs @@ -1,57 +1,56 @@ using System.Text.Json.Serialization; using NP.Lti13Platform.Core.Services; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public interface IPlatformMessage { - public interface IPlatformMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/tool_platform")] - public ToolPlatform? Platform { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/tool_platform")] + public ToolPlatform? Platform { get; set; } - public class ToolPlatform - { - [JsonPropertyName("guid")] - public required string Guid { get; set; } + public class ToolPlatform + { + [JsonPropertyName("guid")] + public required string Guid { get; set; } - [JsonPropertyName("contact_email")] - public string? ContactEmail { get; set; } + [JsonPropertyName("contact_email")] + public string? ContactEmail { get; set; } - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonPropertyName("name")] - public string? Name { get; set; } + [JsonPropertyName("name")] + public string? Name { get; set; } - [JsonPropertyName("url")] - public string? Url { get; set; } + [JsonPropertyName("url")] + public string? Url { get; set; } - [JsonPropertyName("product_family_code")] - public string? ProductFamilyCode { get; set; } + [JsonPropertyName("product_family_code")] + public string? ProductFamilyCode { get; set; } - [JsonPropertyName("version")] - public string? Version { get; set; } - } + [JsonPropertyName("version")] + public string? Version { get; set; } } +} - public class PlatformPopulator(ILti13PlatformService platformService) : Populator +public class PlatformPopulator(ILti13PlatformService platformService) : Populator +{ + public override async Task PopulateAsync(IPlatformMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IPlatformMessage obj, MessageScope scope, CancellationToken cancellationToken = default) - { - var platform = await platformService.GetPlatformAsync(scope.Tool.ClientId, cancellationToken); + var platform = await platformService.GetPlatformAsync(scope.Tool.ClientId, cancellationToken); - if (platform != null) + if (platform != null) + { + obj.Platform = new IPlatformMessage.ToolPlatform { - obj.Platform = new IPlatformMessage.ToolPlatform - { - Guid = platform.Guid, - ContactEmail = platform.ContactEmail, - Description = platform.Description, - Name = platform.Name, - ProductFamilyCode = platform.ProductFamilyCode, - Url = platform.Url, - Version = platform.Version, - }; - } + Guid = platform.Guid, + ContactEmail = platform.ContactEmail, + Description = platform.Description, + Name = platform.Name, + ProductFamilyCode = platform.ProductFamilyCode, + Url = platform.Url, + Version = platform.Version, + }; } } } diff --git a/NP.Lti13Platform.Core/Populators/ResourceLinkPopulator.cs b/NP.Lti13Platform.Core/Populators/ResourceLinkPopulator.cs index 0041f56..f6d0c63 100644 --- a/NP.Lti13Platform.Core/Populators/ResourceLinkPopulator.cs +++ b/NP.Lti13Platform.Core/Populators/ResourceLinkPopulator.cs @@ -2,74 +2,72 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public interface IResourceLinkMessage : ILaunchPresentationMessage { - public interface IResourceLinkMessage : ILaunchPresentationMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/version")] - public string LtiVersion { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/version")] + public string LtiVersion { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/deployment_id")] - public string DeploymentId { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/deployment_id")] + public string DeploymentId { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/target_link_uri")] - public string TargetLinkUri { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/target_link_uri")] + public string TargetLinkUri { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/resource_link")] - public ResourceLinkMessage ResourceLink { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/resource_link")] + public ResourceLinkMessage ResourceLink { get; set; } - public class ResourceLinkMessage - { - [JsonPropertyName("id")] - public required string Id { get; set; } + public class ResourceLinkMessage + { + [JsonPropertyName("id")] + public required string Id { get; set; } - [JsonPropertyName("description")] - public string? Description { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } - } + [JsonPropertyName("title")] + public string? Title { get; set; } } +} - public class ResourceLinkPopulator() : Populator +public class ResourceLinkPopulator() : Populator +{ + public override async Task PopulateAsync(IResourceLinkMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IResourceLinkMessage obj, MessageScope scope, CancellationToken cancellationToken = default) + if (scope.ResourceLink == null) { - if (scope.ResourceLink == null) - { - const string PARAM_NAME = $"{nameof(scope)}.{nameof(scope.ResourceLink)}"; - throw new ArgumentNullException(PARAM_NAME); - } + throw new ArgumentNullException($"{nameof(scope)}.{nameof(scope.ResourceLink)}"); + } - obj.LtiVersion = "1.3.0"; - obj.DeploymentId = scope.Deployment.Id; + obj.LtiVersion = "1.3.0"; + obj.DeploymentId = scope.Deployment.Id; - obj.TargetLinkUri = scope.ResourceLink.Url ?? scope.Tool.LaunchUrl; - obj.ResourceLink = new IResourceLinkMessage.ResourceLinkMessage - { - Id = scope.ResourceLink.Id, - Description = scope.ResourceLink.Text, - Title = scope.ResourceLink.Title - }; + obj.TargetLinkUri = scope.ResourceLink.Url ?? scope.Tool.LaunchUrl; + obj.ResourceLink = new IResourceLinkMessage.ResourceLinkMessage + { + Id = scope.ResourceLink.Id, + Description = scope.ResourceLink.Text, + Title = scope.ResourceLink.Title + }; - if (!string.IsNullOrWhiteSpace(scope.MessageHint)) - { - var launchPresentation = JsonSerializer.Deserialize(Encoding.UTF8.GetString(Convert.FromBase64String(scope.MessageHint))); + if (!string.IsNullOrWhiteSpace(scope.MessageHint)) + { + var launchPresentation = JsonSerializer.Deserialize(Encoding.UTF8.GetString(Convert.FromBase64String(scope.MessageHint))); - if (launchPresentation != null) + if (launchPresentation != null) + { + obj.LaunchPresentation = new ILaunchPresentationMessage.LaunchPresentationDefinition { - obj.LaunchPresentation = new ILaunchPresentationMessage.LaunchPresentationDefinition - { - DocumentTarget = launchPresentation.DocumentTarget, - Height = launchPresentation.Height, - Locale = launchPresentation.Locale, - ReturnUrl = launchPresentation.ReturnUrl, - Width = launchPresentation.Width, - }; - } + DocumentTarget = launchPresentation.DocumentTarget, + Height = launchPresentation.Height, + Locale = launchPresentation.Locale, + ReturnUrl = launchPresentation.ReturnUrl, + Width = launchPresentation.Width, + }; } - - await Task.CompletedTask; } + + await Task.CompletedTask; } } diff --git a/NP.Lti13Platform.Core/Populators/RolesPopulator.cs b/NP.Lti13Platform.Core/Populators/RolesPopulator.cs index 10b6c4b..ea53952 100644 --- a/NP.Lti13Platform.Core/Populators/RolesPopulator.cs +++ b/NP.Lti13Platform.Core/Populators/RolesPopulator.cs @@ -2,32 +2,31 @@ using NP.Lti13Platform.Core.Constants; using NP.Lti13Platform.Core.Services; -namespace NP.Lti13Platform.Core.Populators +namespace NP.Lti13Platform.Core.Populators; + +public interface IRolesMessage { - public interface IRolesMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/roles")] - public IEnumerable Roles { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/roles")] + public IEnumerable Roles { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/role_scope_mentor")] - public IEnumerable? RoleScopeMentor { get; set; } - } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/role_scope_mentor")] + public IEnumerable? RoleScopeMentor { get; set; } +} - public class RolesPopulator(ILti13CoreDataService dataService) : Populator +public class RolesPopulator(ILti13CoreDataService dataService) : Populator +{ + public override async Task PopulateAsync(IRolesMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IRolesMessage obj, MessageScope scope, CancellationToken cancellationToken = default) + if (scope.Context != null) { - if (scope.Context != null) + var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.User.Id, cancellationToken); + if (membership != null) { - var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.User.Id, cancellationToken); - if (membership != null) - { - obj.Roles = membership.Roles; + obj.Roles = membership.Roles; - if (obj.Roles.Contains(Lti13ContextRoles.Mentor)) - { - obj.RoleScopeMentor = membership.MentoredUserIds; - } + if (obj.Roles.Contains(Lti13ContextRoles.Mentor)) + { + obj.RoleScopeMentor = membership.MentoredUserIds; } } } diff --git a/NP.Lti13Platform.Core/Services/DefaultPlatformService.cs b/NP.Lti13Platform.Core/Services/DefaultPlatformService.cs index 56812a7..2d9d154 100644 --- a/NP.Lti13Platform.Core/Services/DefaultPlatformService.cs +++ b/NP.Lti13Platform.Core/Services/DefaultPlatformService.cs @@ -1,10 +1,9 @@ using Microsoft.Extensions.Options; using NP.Lti13Platform.Core.Models; -namespace NP.Lti13Platform.Core.Services +namespace NP.Lti13Platform.Core.Services; + +internal class DefaultPlatformService(IOptionsMonitor config) : ILti13PlatformService { - internal class DefaultPlatformService(IOptionsMonitor config) : ILti13PlatformService - { - public async Task GetPlatformAsync(string clientId, CancellationToken cancellationToken = default) => await Task.FromResult(!string.IsNullOrWhiteSpace(config.CurrentValue.Guid) ? config.CurrentValue : null); - } + public async Task GetPlatformAsync(string clientId, CancellationToken cancellationToken = default) => await Task.FromResult(!string.IsNullOrWhiteSpace(config.CurrentValue.Guid) ? config.CurrentValue : null); } diff --git a/NP.Lti13Platform.Core/Services/DefaultTokenConfigService.cs b/NP.Lti13Platform.Core/Services/DefaultTokenConfigService.cs index 6f3a8ed..b3edd91 100644 --- a/NP.Lti13Platform.Core/Services/DefaultTokenConfigService.cs +++ b/NP.Lti13Platform.Core/Services/DefaultTokenConfigService.cs @@ -1,10 +1,9 @@ using Microsoft.Extensions.Options; using NP.Lti13Platform.Core.Configs; -namespace NP.Lti13Platform.Core.Services +namespace NP.Lti13Platform.Core.Services; + +internal class DefaultTokenConfigService(IOptionsMonitor config) : ILti13TokenConfigService { - internal class DefaultTokenConfigService(IOptionsMonitor config) : ILti13TokenConfigService - { - public async Task GetTokenConfigAsync(string clientId, CancellationToken cancellationToken = default) => await Task.FromResult(config.CurrentValue); - } + public async Task GetTokenConfigAsync(string clientId, CancellationToken cancellationToken = default) => await Task.FromResult(config.CurrentValue); } diff --git a/NP.Lti13Platform.Core/Services/ILti13CoreDataService.cs b/NP.Lti13Platform.Core/Services/ILti13CoreDataService.cs index e7f80fa..9143baa 100644 --- a/NP.Lti13Platform.Core/Services/ILti13CoreDataService.cs +++ b/NP.Lti13Platform.Core/Services/ILti13CoreDataService.cs @@ -18,8 +18,8 @@ public interface ILti13CoreDataService Task GetGradeAsync(string lineItemId, string userId, CancellationToken cancellationToken = default); - Task GetServiceTokenRequestAsync(string toolId, string id, CancellationToken cancellationToken = default); - Task SaveServiceTokenRequestAsync(ServiceToken serviceToken, CancellationToken cancellationToken = default); + Task GetServiceTokenAsync(string toolId, string id, CancellationToken cancellationToken = default); + Task SaveServiceTokenAsync(ServiceToken serviceToken, CancellationToken cancellationToken = default); Task> GetPublicKeysAsync(CancellationToken cancellationToken = default); Task GetPrivateKeyAsync(CancellationToken cancellationToken = default); diff --git a/NP.Lti13Platform.Core/Services/UrlServiceHelper.cs b/NP.Lti13Platform.Core/Services/UrlServiceHelper.cs index 40b0832..2cd9406 100644 --- a/NP.Lti13Platform.Core/Services/UrlServiceHelper.cs +++ b/NP.Lti13Platform.Core/Services/UrlServiceHelper.cs @@ -4,77 +4,76 @@ using System.Text.Json; using System.Web; -namespace NP.Lti13Platform.Core.Services +namespace NP.Lti13Platform.Core.Services; + +public interface IUrlServiceHelper { - public interface IUrlServiceHelper - { - Task GetResourceLinkInitiationUrlAsync(Tool tool, string deploymentId, string contextId, ResourceLink resourceLink, string userId, bool isAnonymous, string? actualUserId = null, LaunchPresentationOverride? launchPresentation = null, CancellationToken cancellationToken = default); - Task GetUrlAsync(string messageType, Tool tool, string deploymentId, string targetLinkUri, string userId, bool isAnonymous, string? actualUserId = null, string? contextId = null, string? resourceLinkId = null, string? messageHint = null, CancellationToken cancellationToken = default); + Task GetResourceLinkInitiationUrlAsync(Tool tool, string deploymentId, string contextId, ResourceLink resourceLink, string userId, bool isAnonymous, string? actualUserId = null, LaunchPresentationOverride? launchPresentation = null, CancellationToken cancellationToken = default); + Task GetUrlAsync(string messageType, Tool tool, string deploymentId, string targetLinkUri, string userId, bool isAnonymous, string? actualUserId = null, string? contextId = null, string? resourceLinkId = null, string? messageHint = null, CancellationToken cancellationToken = default); - Task GetLoginHintAsync(string userId, string? actualUserId, bool isAnonymous, CancellationToken cancellationToken = default); - Task<(string UserId, string? ActualUserId, bool IsAnonymous)> ParseLoginHintAsync(string loginHint, CancellationToken cancellationToken = default); + Task GetLoginHintAsync(string userId, string? actualUserId, bool isAnonymous, CancellationToken cancellationToken = default); + Task<(string UserId, string? ActualUserId, bool IsAnonymous)> ParseLoginHintAsync(string loginHint, CancellationToken cancellationToken = default); - Task GetLtiMessageHintAsync(string MessageType, string DeploymentId, string? ContextId, string? ResourceLinkId, string? messageHint, CancellationToken cancellationToken = default); - Task<(string MessageType, string DeploymentId, string? ContextId, string? ResourceLinkId, string? MessageHint)> ParseLtiMessageHintAsync(string messageHint, CancellationToken cancellationToken = default); - } + Task GetLtiMessageHintAsync(string MessageType, string DeploymentId, string? ContextId, string? ResourceLinkId, string? messageHint, CancellationToken cancellationToken = default); + Task<(string MessageType, string DeploymentId, string? ContextId, string? ResourceLinkId, string? MessageHint)> ParseLtiMessageHintAsync(string messageHint, CancellationToken cancellationToken = default); +} - public class UrlServiceHelper(ILti13TokenConfigService tokenService) : IUrlServiceHelper - { - public async Task GetResourceLinkInitiationUrlAsync(Tool tool, string deploymentId, string contextId, ResourceLink resourceLink, string userId, bool isAnonymous, string? actualUserId = null, LaunchPresentationOverride? launchPresentation = null, CancellationToken cancellationToken = default) - => await GetUrlAsync( - Lti13MessageType.LtiResourceLinkRequest, - tool, - deploymentId, - string.IsNullOrWhiteSpace(resourceLink.Url) ? tool.LaunchUrl : resourceLink.Url, - userId, - isAnonymous, - actualUserId, - contextId, - resourceLink.Id, - Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(launchPresentation))), - cancellationToken); +public class UrlServiceHelper(ILti13TokenConfigService tokenService) : IUrlServiceHelper +{ + public async Task GetResourceLinkInitiationUrlAsync(Tool tool, string deploymentId, string contextId, ResourceLink resourceLink, string userId, bool isAnonymous, string? actualUserId = null, LaunchPresentationOverride? launchPresentation = null, CancellationToken cancellationToken = default) + => await GetUrlAsync( + Lti13MessageType.LtiResourceLinkRequest, + tool, + deploymentId, + string.IsNullOrWhiteSpace(resourceLink.Url) ? tool.LaunchUrl : resourceLink.Url, + userId, + isAnonymous, + actualUserId, + contextId, + resourceLink.Id, + Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(launchPresentation))), + cancellationToken); - public async Task GetUrlAsync( - string messageType, - Tool tool, - string deploymentId, - string targetLinkUri, - string userId, - bool isAnonymous, - string? actualUserId = null, - string? contextId = null, - string? resourceLinkId = null, - string? messageHint = null, - CancellationToken cancellationToken = default) - { - var builder = new UriBuilder(tool.OidcInitiationUrl); + public async Task GetUrlAsync( + string messageType, + Tool tool, + string deploymentId, + string targetLinkUri, + string userId, + bool isAnonymous, + string? actualUserId = null, + string? contextId = null, + string? resourceLinkId = null, + string? messageHint = null, + CancellationToken cancellationToken = default) + { + var builder = new UriBuilder(tool.OidcInitiationUrl); - var query = HttpUtility.ParseQueryString(builder.Query); - query.Add("iss", (await tokenService.GetTokenConfigAsync(tool.ClientId, cancellationToken)).Issuer); - query.Add("login_hint", await GetLoginHintAsync(userId, actualUserId, isAnonymous, cancellationToken)); - query.Add("target_link_uri", targetLinkUri); - query.Add("client_id", tool.ClientId.ToString()); - query.Add("lti_message_hint", await GetLtiMessageHintAsync(messageType, deploymentId, contextId, resourceLinkId, messageHint, cancellationToken)); - query.Add("lti_deployment_id", deploymentId); - builder.Query = query.ToString(); + var query = HttpUtility.ParseQueryString(builder.Query); + query.Add("iss", (await tokenService.GetTokenConfigAsync(tool.ClientId, cancellationToken)).Issuer); + query.Add("login_hint", await GetLoginHintAsync(userId, actualUserId, isAnonymous, cancellationToken)); + query.Add("target_link_uri", targetLinkUri); + query.Add("client_id", tool.ClientId.ToString()); + query.Add("lti_message_hint", await GetLtiMessageHintAsync(messageType, deploymentId, contextId, resourceLinkId, messageHint, cancellationToken)); + query.Add("lti_deployment_id", deploymentId); + builder.Query = query.ToString(); - return builder.Uri; - } + return builder.Uri; + } - public async Task GetLoginHintAsync(string userId, string? actualUserId, bool isAnonymous, CancellationToken cancellationToken = default) => - await Task.FromResult($"{userId}|{(isAnonymous ? "1" : string.Empty)}|{actualUserId}"); + public async Task GetLoginHintAsync(string userId, string? actualUserId, bool isAnonymous, CancellationToken cancellationToken = default) => + await Task.FromResult($"{userId}|{(isAnonymous ? "1" : string.Empty)}|{actualUserId}"); - public async Task<(string UserId, string? ActualUserId, bool IsAnonymous)> ParseLoginHintAsync(string loginHint, CancellationToken cancellationToken = default) => - await Task.FromResult(loginHint.Split('|', 3) is [var userId, var isAnonymousString, var actualUserId] ? - (userId, actualUserId, !string.IsNullOrWhiteSpace(isAnonymousString)) : - (string.Empty, null, false)); + public async Task<(string UserId, string? ActualUserId, bool IsAnonymous)> ParseLoginHintAsync(string loginHint, CancellationToken cancellationToken = default) => + await Task.FromResult(loginHint.Split('|', 3) is [var userId, var isAnonymousString, var actualUserId] ? + (userId, actualUserId, !string.IsNullOrWhiteSpace(isAnonymousString)) : + (string.Empty, null, false)); - public async Task GetLtiMessageHintAsync(string messageType, string deploymentId, string? contextId, string? resourceLinkId, string? messageHint, CancellationToken cancellationToken = default) => - await Task.FromResult($"{messageType}|{deploymentId}|{contextId}|{resourceLinkId}|{messageHint}"); + public async Task GetLtiMessageHintAsync(string messageType, string deploymentId, string? contextId, string? resourceLinkId, string? messageHint, CancellationToken cancellationToken = default) => + await Task.FromResult($"{messageType}|{deploymentId}|{contextId}|{resourceLinkId}|{messageHint}"); - public async Task<(string MessageType, string DeploymentId, string? ContextId, string? ResourceLinkId, string? MessageHint)> ParseLtiMessageHintAsync(string messageHint, CancellationToken cancellationToken = default) => - await Task.FromResult(messageHint.Split('|', 5) is [var messageType, var deploymentId, var contextId, var resourceLinkId, var messageHintString] ? - (messageType, deploymentId, contextId, resourceLinkId, messageHintString) : - (string.Empty, string.Empty, null, null, null)); - } + public async Task<(string MessageType, string DeploymentId, string? ContextId, string? ResourceLinkId, string? MessageHint)> ParseLtiMessageHintAsync(string messageHint, CancellationToken cancellationToken = default) => + await Task.FromResult(messageHint.Split('|', 5) is [var messageType, var deploymentId, var contextId, var resourceLinkId, var messageHintString] ? + (messageType, deploymentId, contextId, resourceLinkId, messageHintString) : + (string.Empty, string.Empty, null, null, null)); } diff --git a/NP.Lti13Platform.Core/Startup.cs b/NP.Lti13Platform.Core/Startup.cs index aa8f0cf..b612274 100644 --- a/NP.Lti13Platform.Core/Startup.cs +++ b/NP.Lti13Platform.Core/Startup.cs @@ -131,90 +131,72 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil routeBuilder.Map(config.AuthorizationUrl, async ([AsParameters] AuthenticationRequest queryString, [FromForm] AuthenticationRequest form, IServiceProvider serviceProvider, ILti13TokenConfigService tokenService, ILti13CoreDataService dataService, IUrlServiceHelper urlServiceHelper, CancellationToken cancellationToken) => { - const string OPENID = "openid"; - const string ID_TOKEN = "id_token"; - const string FORM_POST = "form_post"; - const string NONE = "none"; - const string INVALID_SCOPE = "invalid_scope"; const string INVALID_REQUEST = "invalid_request"; const string INVALID_CLIENT = "invalid_client"; - const string INVALID_GRANT = "invalid_grant"; const string UNAUTHORIZED_CLIENT = "unauthorized_client"; const string AUTH_SPEC_URI = "https://www.imsglobal.org/spec/security/v1p0/#step-2-authentication-request"; - const string LTI_SPEC_URI = "https://www.imsglobal.org/spec/lti/v1p3/#lti_message_hint-login-parameter"; - const string SCOPE_REQUIRED = "scope must be 'openid'."; - const string RESPONSE_TYPE_REQUIRED = "response_type must be 'id_token'."; - const string RESPONSE_MODE_REQUIRED = "response_mode must be 'form_post'."; - const string PROMPT_REQUIRED = "prompt must be 'none'."; - const string NONCE_REQUIRED = "nonce is required."; - const string CLIENT_ID_REQUIRED = "client_id is required."; - const string UNKNOWN_CLIENT_ID = "client_id is unknown"; - const string UNKNOWN_REDIRECT_URI = "redirect_uri is unknown"; - const string LTI_MESSAGE_HINT_INVALID = "lti_message_hint is invalid"; - const string LOGIN_HINT_REQUIRED = "login_hint is required"; const string USER_CLIENT_MISMATCH = "client is not authorized for user"; - const string DEPLOYMENT_CLIENT_MISMATCH = "deployment is not for client"; var request = form ?? queryString; /* https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 */ /* https://www.imsglobal.org/spec/security/v1p0/#step-2-authentication-request */ - if (request.Scope != OPENID) + if (request.Scope != "openid") { return Results.BadRequest(new { - Error = INVALID_SCOPE, - Error_Description = SCOPE_REQUIRED, + Error = "invalid_scope", + Error_Description = "scope must be 'openid'.", Error_Uri = AUTH_SPEC_URI }); } - if (request.Response_Type != ID_TOKEN) + if (request.Response_Type != "id_token") { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = RESPONSE_TYPE_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "response_type must be 'id_token'.", Error_Uri = AUTH_SPEC_URI }); } - if (request.Response_Mode != FORM_POST) + if (request.Response_Mode != "form_post") { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = RESPONSE_MODE_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "response_mode must be 'form_post'.", Error_Uri = AUTH_SPEC_URI }); } - if (request.Prompt != NONE) + if (request.Prompt != "none") { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = PROMPT_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "prompt must be 'none'.", Error_Uri = AUTH_SPEC_URI }); } if (string.IsNullOrWhiteSpace(request.Nonce)) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = NONCE_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "nonce is required.", Error_Uri = AUTH_SPEC_URI }); } if (string.IsNullOrWhiteSpace(request.Login_Hint)) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = LOGIN_HINT_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "login_hint is required", Error_Uri = AUTH_SPEC_URI }); } if (string.IsNullOrWhiteSpace(request.Client_Id)) { - return Results.BadRequest(new { Error = INVALID_CLIENT, Error_Description = CLIENT_ID_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_CLIENT, Error_Description = "client_id is required.", Error_Uri = AUTH_SPEC_URI }); } var tool = await dataService.GetToolAsync(request.Client_Id, cancellationToken); if (tool == null) { - return Results.BadRequest(new { Error = INVALID_CLIENT, Error_Description = UNKNOWN_CLIENT_ID, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_CLIENT, Error_Description = "client_id is unknown", Error_Uri = AUTH_SPEC_URI }); } if (!tool.RedirectUrls.Contains(request.Redirect_Uri)) { - return Results.BadRequest(new { Error = INVALID_GRANT, Error_Description = UNKNOWN_REDIRECT_URI, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = "invalid_grant", Error_Description = "redirect_uri is unknown", Error_Uri = AUTH_SPEC_URI }); } if (string.IsNullOrWhiteSpace(request.Lti_Message_Hint)) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = LTI_MESSAGE_HINT_INVALID, Error_Uri = LTI_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "lti_message_hint is invalid", Error_Uri = "https://www.imsglobal.org/spec/lti/v1p3/#lti_message_hint-login-parameter" }); } var (messageTypeString, deploymentId, contextId, resourceLinkId, messageHintString) = await urlServiceHelper.ParseLtiMessageHintAsync(request.Lti_Message_Hint, cancellationToken); @@ -222,7 +204,7 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil var deployment = await dataService.GetDeploymentAsync(deploymentId, cancellationToken); if (deployment?.ToolId != tool.Id) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = DEPLOYMENT_CLIENT_MISMATCH, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "deployment is not for client", Error_Uri = AUTH_SPEC_URI }); } var (userId, actualUserId, isAnonymous) = await urlServiceHelper.ParseLoginHintAsync(request.Login_Hint, cancellationToken); @@ -305,7 +287,7 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil messageHintString); var services = serviceProvider.GetKeyedServices(messageTypeString); - foreach (var service in services) + foreach (var service in services) // TODO: await in list { await service.PopulateAsync(ltiMessage, scope, cancellationToken); } @@ -338,33 +320,26 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil const string AUTH_SPEC_URI = "https://www.imsglobal.org/spec/security/v1p0/#using-json-web-tokens-with-oauth-2-0-client-credentials-grant"; const string SCOPE_SPEC_URI = "https://www.imsglobal.org/spec/lti-ags/v2p0"; const string TOKEN_SPEC_URI = "https://www.imsglobal.org/spec/lti/v1p3/#token-endpoint-claim-and-services"; - const string UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"; const string INVALID_GRANT = "invalid_grant"; - const string CLIENT_CREDENTIALS = "client_credentials"; - const string GRANT_REQUIRED = "grant_type must be 'client_credentials'"; - const string CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; - const string CLIENT_ASSERTION_TYPE_REQUIRED = "client_assertion_type must be 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'"; const string INVALID_SCOPE = "invalid_scope"; const string SCOPE_REQUIRED = "scope must be a valid value"; const string CLIENT_ASSERTION_INVALID = "client_assertion must be a valid jwt"; const string INVALID_REQUEST = "invalid_request"; - const string JTI_REUSE = "jti has already been used and is not expired"; - const string BODY_MISSING = "request body is missing"; var httpContext = httpContextAccessor.HttpContext!; if (request == null) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = BODY_MISSING, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "request body is missing", Error_Uri = AUTH_SPEC_URI }); } - if (request.Grant_Type != CLIENT_CREDENTIALS) + if (request.Grant_Type != "client_credentials") { - return Results.BadRequest(new { Error = UNSUPPORTED_GRANT_TYPE, Error_Description = GRANT_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = "unsupported_grant_type", Error_Description = "grant_type must be 'client_credentials'", Error_Uri = AUTH_SPEC_URI }); } - if (request.Client_Assertion_Type != CLIENT_ASSERTION_TYPE) + if (request.Client_Assertion_Type != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") { - return Results.BadRequest(new { Error = INVALID_GRANT, Error_Description = CLIENT_ASSERTION_TYPE_REQUIRED, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_GRANT, Error_Description = "client_assertion_type must be 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'", Error_Uri = AUTH_SPEC_URI }); } if (string.IsNullOrWhiteSpace(request.Scope)) @@ -415,13 +390,13 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil } else { - var serviceToken = await dataService.GetServiceTokenRequestAsync(tool.Id, validatedToken.SecurityToken.Id, cancellationToken); + var serviceToken = await dataService.GetServiceTokenAsync(tool.Id, validatedToken.SecurityToken.Id, cancellationToken); if (serviceToken?.Expiration > DateTime.UtcNow) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = JTI_REUSE, Error_Uri = AUTH_SPEC_URI }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "jti has already been used and is not expired", Error_Uri = AUTH_SPEC_URI }); } - await dataService.SaveServiceTokenRequestAsync(new ServiceToken { Id = validatedToken.SecurityToken.Id, ToolId = tool.Id, Expiration = validatedToken.SecurityToken.ValidTo }, cancellationToken); + await dataService.SaveServiceTokenAsync(new ServiceToken { Id = validatedToken.SecurityToken.Id, ToolId = tool.Id, Expiration = validatedToken.SecurityToken.ValidTo }, cancellationToken); } var privateKey = await dataService.GetPrivateKeyAsync(cancellationToken); diff --git a/NP.Lti13Platform.CourseGroupsService/Class1.cs b/NP.Lti13Platform.CourseGroupsService/Class1.cs deleted file mode 100644 index a748703..0000000 --- a/NP.Lti13Platform.CourseGroupsService/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NP.Lti13Platform.CourseGroupsService -{ - public class Class1 - { - - } -} diff --git a/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingConfig.cs b/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingConfig.cs index 826d4c2..e9e3903 100644 --- a/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingConfig.cs +++ b/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingConfig.cs @@ -1,16 +1,14 @@ using NP.Lti13Platform.Core.Constants; using NP.Lti13Platform.DeepLinking.Models; +using System.Net.Mime; namespace NP.Lti13Platform.DeepLinking.Configs { public record DeepLinkingConfig { - private const string MEDIA_TYPE_IMAGE = "image/*"; - private const string MEDIA_TYPE_TEXT_HTML = "text/html"; - public IEnumerable AcceptPresentationDocumentTargets { get; set; } = [Lti13PresentationTargetDocuments.Embed, Lti13PresentationTargetDocuments.Iframe, Lti13PresentationTargetDocuments.Window]; public IEnumerable AcceptTypes { get; set; } = [Lti13DeepLinkingTypes.File, Lti13DeepLinkingTypes.Html, Lti13DeepLinkingTypes.Image, Lti13DeepLinkingTypes.Link, Lti13DeepLinkingTypes.LtiResourceLink]; - public IEnumerable AcceptMediaTypes { get; set; } = [MEDIA_TYPE_IMAGE, MEDIA_TYPE_TEXT_HTML]; + public IEnumerable AcceptMediaTypes { get; set; } = ["image/*", MediaTypeNames.Text.Html]; /// /// Whether the platform in the context of that deep linking request supports or ignores line items included in LTI Resource Link items. False indicates line items will be ignored. True indicates the platform will create a line item when creating the resource link. If the field is not present, no assumption that can be made about the support of line items. diff --git a/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingEndpointsConfig.cs b/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingEndpointsConfig.cs index 2d1b2c2..4fe6ecd 100644 --- a/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingEndpointsConfig.cs +++ b/NP.Lti13Platform.DeepLinking/Configs/DeepLinkingEndpointsConfig.cs @@ -1,12 +1,11 @@ -namespace NP.Lti13Platform.DeepLinking.Configs +namespace NP.Lti13Platform.DeepLinking.Configs; + +public class DeepLinkingEndpointsConfig { - public class DeepLinkingEndpointsConfig - { - /// - /// Endpoint for the response of LTI 1.3 deep link messages. - /// Must include route parameter for {contextId?}. - /// - /// Default: /lti13/deeplinking/{contextId?} - public string DeepLinkingResponseUrl { get; set; } = "/lti13/deeplinking/{contextId?}"; - } + /// + /// Endpoint for the response of LTI 1.3 deep link messages. + /// Must include route parameter for {contextId?}. + /// + /// Default: /lti13/deeplinking/{contextId?} + public string DeepLinkingResponseUrl { get; set; } = "/lti13/deeplinking/{contextId?}"; } diff --git a/NP.Lti13Platform.DeepLinking/Constants.cs b/NP.Lti13Platform.DeepLinking/Constants.cs index 5ba506f..8aba812 100644 --- a/NP.Lti13Platform.DeepLinking/Constants.cs +++ b/NP.Lti13Platform.DeepLinking/Constants.cs @@ -2,12 +2,12 @@ { internal static class RouteNames { - internal const string DEEP_LINKING_RESPONSE = "DEEP_LINKING_RESPONSE"; + internal static readonly string DEEP_LINKING_RESPONSE = "DEEP_LINKING_RESPONSE"; } public static class Lti13MessageType { - public const string LtiDeepLinkingRequest = "LtiDeepLinkingRequest"; + public static readonly string LtiDeepLinkingRequest = "LtiDeepLinkingRequest"; } /// @@ -15,10 +15,10 @@ public static class Lti13MessageType /// public static class Lti13DeepLinkingTypes { - public const string Link = "link"; - public const string File = "file"; - public const string Html = "html"; - public const string LtiResourceLink = "ltiResourceLink"; - public const string Image = "image"; + public static readonly string Link = "link"; + public static readonly string File = "file"; + public static readonly string Html = "html"; + public static readonly string LtiResourceLink = "ltiResourceLink"; + public static readonly string Image = "image"; } } diff --git a/NP.Lti13Platform.DeepLinking/DeepLinkResponse.cs b/NP.Lti13Platform.DeepLinking/DeepLinkResponse.cs index 05a48fa..3c2d16e 100644 --- a/NP.Lti13Platform.DeepLinking/DeepLinkResponse.cs +++ b/NP.Lti13Platform.DeepLinking/DeepLinkResponse.cs @@ -1,16 +1,15 @@ using NP.Lti13Platform.DeepLinking.Models; -namespace NP.Lti13Platform.DeepLinking +namespace NP.Lti13Platform.DeepLinking; + +public class DeepLinkResponse { - public class DeepLinkResponse - { - public string? Data { get; set; } + public string? Data { get; set; } - public string? Message { get; set; } - public string? Log { get; set; } - public string? ErrorMessage { get; set; } - public string? ErrorLog { get; set; } + public string? Message { get; set; } + public string? Log { get; set; } + public string? ErrorMessage { get; set; } + public string? ErrorLog { get; set; } - public IEnumerable ContentItems { get; set; } = []; - } + public IEnumerable ContentItems { get; set; } = []; } \ No newline at end of file diff --git a/NP.Lti13Platform.DeepLinking/Models/ContentItem.cs b/NP.Lti13Platform.DeepLinking/Models/ContentItem.cs index f9e426b..c0bff2c 100644 --- a/NP.Lti13Platform.DeepLinking/Models/ContentItem.cs +++ b/NP.Lti13Platform.DeepLinking/Models/ContentItem.cs @@ -4,377 +4,376 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.DeepLinking.Models +namespace NP.Lti13Platform.DeepLinking.Models; + +public class ContentItemDictionary() : IDictionary<(string? ToolId, string ContentItemType), Type> { - public class ContentItemDictionary() : IDictionary<(string? ToolId, string ContentItemType), Type> - { - private readonly IDictionary<(string?, string), Type> _items = new Dictionary<(string?, string), Type>(); + private readonly IDictionary<(string?, string), Type> _items = new Dictionary<(string?, string), Type>(); - public Type this[(string? ToolId, string ContentItemType) key] - { - get => _items.TryGetValue(key, out Type? value) ? value : key.ToolId != null && _items.TryGetValue((null, key.ContentItemType), out value) ? value : typeof(DefaultContentItem); - set => _items[key] = value; - } + public Type this[(string? ToolId, string ContentItemType) key] + { + get => _items.TryGetValue(key, out Type? value) ? value : key.ToolId != null && _items.TryGetValue((null, key.ContentItemType), out value) ? value : typeof(DefaultContentItem); + set => _items[key] = value; + } - public ICollection<(string?, string)> Keys => _items.Keys; + public ICollection<(string?, string)> Keys => _items.Keys; - public ICollection Values => _items.Values; + public ICollection Values => _items.Values; - public int Count => _items.Count; + public int Count => _items.Count; - public bool IsReadOnly => _items.IsReadOnly; + public bool IsReadOnly => _items.IsReadOnly; - public void Add((string?, string) key, Type value) => _items[key] = value; + public void Add((string?, string) key, Type value) => _items[key] = value; - public void Add(KeyValuePair<(string?, string), Type> item) => _items[item.Key] = item.Value; + public void Add(KeyValuePair<(string?, string), Type> item) => _items[item.Key] = item.Value; - public void Clear() => _items.Clear(); + public void Clear() => _items.Clear(); - public bool Contains(KeyValuePair<(string?, string), Type> item) => _items.Contains(item); + public bool Contains(KeyValuePair<(string?, string), Type> item) => _items.Contains(item); - public bool ContainsKey((string?, string) key) => _items.ContainsKey(key); + public bool ContainsKey((string?, string) key) => _items.ContainsKey(key); - public void CopyTo(KeyValuePair<(string?, string), Type>[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); + public void CopyTo(KeyValuePair<(string?, string), Type>[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); - public IEnumerator> GetEnumerator() => _items.GetEnumerator(); + public IEnumerator> GetEnumerator() => _items.GetEnumerator(); - public bool Remove((string?, string) key) => _items.Remove(key); + public bool Remove((string?, string) key) => _items.Remove(key); - public bool Remove(KeyValuePair<(string?, string), Type> item) => _items.Remove(item.Key); + public bool Remove(KeyValuePair<(string?, string), Type> item) => _items.Remove(item.Key); - public bool TryGetValue((string?, string) key, [MaybeNullWhen(false)] out Type value) - { - value = this[key]; - return true; - } - - IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); - } - - public static class ContentItemType + public bool TryGetValue((string?, string) key, [MaybeNullWhen(false)] out Type value) { - public const string Html = "html"; - public const string Link = "link"; - public const string LtiResourceLink = "ltiResourceLink"; - public const string File = "file"; - public const string Image = "image"; + value = this[key]; + return true; } - [JsonDerivedType(typeof(LinkContentItem))] - [JsonDerivedType(typeof(LtiResourceLinkContentItem))] - [JsonDerivedType(typeof(FileContentItem))] - [JsonDerivedType(typeof(HtmlContentItem))] - [JsonDerivedType(typeof(ImageContentItem))] - [JsonDerivedType(typeof(DefaultContentItem))] - public abstract partial class ContentItem - { - [JsonPropertyName("type")] - public required string Type { get; set; } - } + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); +} - public class LinkContentItem : ContentItem - { - [JsonPropertyName("url")] - public required string Url { get; set; } +public static class ContentItemType +{ + public static readonly string Html = "html"; + public static readonly string Link = "link"; + public static readonly string LtiResourceLink = "ltiResourceLink"; + public static readonly string File = "file"; + public static readonly string Image = "image"; +} + +[JsonDerivedType(typeof(LinkContentItem))] +[JsonDerivedType(typeof(LtiResourceLinkContentItem))] +[JsonDerivedType(typeof(FileContentItem))] +[JsonDerivedType(typeof(HtmlContentItem))] +[JsonDerivedType(typeof(ImageContentItem))] +[JsonDerivedType(typeof(DefaultContentItem))] +public abstract partial class ContentItem +{ + [JsonPropertyName("type")] + public required string Type { get; set; } +} - [JsonPropertyName("title")] - public string? Title { get; set; } +public class LinkContentItem : ContentItem +{ + [JsonPropertyName("url")] + public required string Url { get; set; } - [JsonPropertyName("text")] - public string? Text { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("icon")] - public ContentItemIcon? Icon { get; set; } + [JsonPropertyName("text")] + public string? Text { get; set; } - [JsonPropertyName("thumbnail")] - public ContentItemThumbnail? Thumbnail { get; set; } + [JsonPropertyName("icon")] + public ContentItemIcon? Icon { get; set; } - [JsonPropertyName("window")] - public ContentItemWindow? Window { get; set; } + [JsonPropertyName("thumbnail")] + public ContentItemThumbnail? Thumbnail { get; set; } - [JsonPropertyName("iframe")] - public LinkIframe? Iframe { get; set; } + [JsonPropertyName("window")] + public ContentItemWindow? Window { get; set; } - [JsonPropertyName("embed")] - public LinkEmbed? Embed { get; set; } + [JsonPropertyName("iframe")] + public LinkIframe? Iframe { get; set; } - public class LinkIframe - { - [JsonPropertyName("width")] - public int? Width { get; set; } + [JsonPropertyName("embed")] + public LinkEmbed? Embed { get; set; } - [JsonPropertyName("height")] - public int? Height { get; set; } + public class LinkIframe + { + [JsonPropertyName("width")] + public int? Width { get; set; } - [JsonPropertyName("src")] - public string? Src { get; set; } - } + [JsonPropertyName("height")] + public int? Height { get; set; } - public class LinkEmbed - { - [JsonPropertyName("html")] - public required string Html { get; set; } - } + [JsonPropertyName("src")] + public string? Src { get; set; } } - public class LtiResourceLinkContentItem : ContentItem + public class LinkEmbed { - [JsonPropertyName("url")] - public string? Url { get; set; } - - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("html")] + public required string Html { get; set; } + } +} - [JsonPropertyName("text")] - public string? Text { get; set; } +public class LtiResourceLinkContentItem : ContentItem +{ + [JsonPropertyName("url")] + public string? Url { get; set; } - [JsonPropertyName("icon")] - public ContentItemIcon? Icon { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("thumbnail")] - public ContentItemThumbnail? Thumbnail { get; set; } + [JsonPropertyName("text")] + public string? Text { get; set; } - [JsonPropertyName("window")] - public ContentItemWindow? Window { get; set; } + [JsonPropertyName("icon")] + public ContentItemIcon? Icon { get; set; } - [JsonPropertyName("iframe")] - public LtiResourceLinkIframe? Iframe { get; set; } + [JsonPropertyName("thumbnail")] + public ContentItemThumbnail? Thumbnail { get; set; } - [JsonPropertyName("custom")] - public IDictionary? Custom { get; set; } + [JsonPropertyName("window")] + public ContentItemWindow? Window { get; set; } - [JsonPropertyName("lineItem")] - public LtiResourceLinkLineItem? LineItem { get; set; } + [JsonPropertyName("iframe")] + public LtiResourceLinkIframe? Iframe { get; set; } - [JsonPropertyName("available")] - public LtiResourceLinkAvailable? Available { get; set; } + [JsonPropertyName("custom")] + public IDictionary? Custom { get; set; } - [JsonPropertyName("submission")] - public LtiResourceLinkSubmission? Submission { get; set; } + [JsonPropertyName("lineItem")] + public LtiResourceLinkLineItem? LineItem { get; set; } - public class LtiResourceLinkIframe - { - [JsonPropertyName("width")] - public int? Width { get; set; } + [JsonPropertyName("available")] + public LtiResourceLinkAvailable? Available { get; set; } - [JsonPropertyName("height")] - public int? Height { get; set; } - } + [JsonPropertyName("submission")] + public LtiResourceLinkSubmission? Submission { get; set; } - public class LtiResourceLinkLineItem - { - [JsonPropertyName("label")] - public string? Label { get; set; } + public class LtiResourceLinkIframe + { + [JsonPropertyName("width")] + public int? Width { get; set; } - [JsonPropertyName("scoreMaximum")] - public decimal ScoreMaximum { get; set; } + [JsonPropertyName("height")] + public int? Height { get; set; } + } - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } + public class LtiResourceLinkLineItem + { + [JsonPropertyName("label")] + public string? Label { get; set; } - [JsonPropertyName("tag")] - public string? Tag { get; set; } + [JsonPropertyName("scoreMaximum")] + public decimal ScoreMaximum { get; set; } - [JsonPropertyName("gradesReleased")] - public bool? GradesReleased { get; set; } - } + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } - public class LtiResourceLinkAvailable - { - [JsonPropertyName("startDateTime")] - public DateTimeOffset? StartDateTime { get; set; } + [JsonPropertyName("tag")] + public string? Tag { get; set; } - [JsonPropertyName("endDateTime")] - public DateTimeOffset? EndDateTime { get; set; } - } + [JsonPropertyName("gradesReleased")] + public bool? GradesReleased { get; set; } + } - public class LtiResourceLinkSubmission - { - [JsonPropertyName("startDateTime")] - public DateTimeOffset? StartDateTime { get; set; } + public class LtiResourceLinkAvailable + { + [JsonPropertyName("startDateTime")] + public DateTimeOffset? StartDateTime { get; set; } - [JsonPropertyName("endDateTime")] - public DateTimeOffset? EndDateTime { get; set; } - } + [JsonPropertyName("endDateTime")] + public DateTimeOffset? EndDateTime { get; set; } } - public class FileContentItem : ContentItem + public class LtiResourceLinkSubmission { - [JsonPropertyName("url")] - public required string Url { get; set; } + [JsonPropertyName("startDateTime")] + public DateTimeOffset? StartDateTime { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("endDateTime")] + public DateTimeOffset? EndDateTime { get; set; } + } +} - [JsonPropertyName("text")] - public string? Text { get; set; } +public class FileContentItem : ContentItem +{ + [JsonPropertyName("url")] + public required string Url { get; set; } - [JsonPropertyName("icon")] - public ContentItemIcon? Icon { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("thumbnail")] - public ContentItemThumbnail? Thumbnail { get; set; } + [JsonPropertyName("text")] + public string? Text { get; set; } - [JsonPropertyName("expiresAt")] - public DateTimeOffset? ExpiresAt { get; set; } - } + [JsonPropertyName("icon")] + public ContentItemIcon? Icon { get; set; } - public class HtmlContentItem : ContentItem - { - [JsonPropertyName("html")] - public required string Html { get; set; } + [JsonPropertyName("thumbnail")] + public ContentItemThumbnail? Thumbnail { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("expiresAt")] + public DateTimeOffset? ExpiresAt { get; set; } +} - [JsonPropertyName("text")] - public string? Text { get; set; } - } +public class HtmlContentItem : ContentItem +{ + [JsonPropertyName("html")] + public required string Html { get; set; } - public class ImageContentItem : ContentItem - { - [JsonPropertyName("url")] - public required string Url { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("text")] + public string? Text { get; set; } +} - [JsonPropertyName("text")] - public string? Text { get; set; } +public class ImageContentItem : ContentItem +{ + [JsonPropertyName("url")] + public required string Url { get; set; } - [JsonPropertyName("icon")] - public ContentItemIcon? Icon { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("thumbnail")] - public ContentItemThumbnail? Thumbnail { get; set; } + [JsonPropertyName("text")] + public string? Text { get; set; } - [JsonPropertyName("width")] - public int? Width { get; set; } + [JsonPropertyName("icon")] + public ContentItemIcon? Icon { get; set; } - [JsonPropertyName("height")] - public int? Height { get; set; } - } + [JsonPropertyName("thumbnail")] + public ContentItemThumbnail? Thumbnail { get; set; } - public class DefaultContentItem : ContentItem, IDictionary - { - private readonly IDictionary _items = new Dictionary(); - private static readonly string TypePropertyName = typeof(ContentItem).GetProperty(nameof(Type))?.GetCustomAttribute()?.Name ?? string.Empty; + [JsonPropertyName("width")] + public int? Width { get; set; } + + [JsonPropertyName("height")] + public int? Height { get; set; } +} - public JsonElement this[string key] +public class DefaultContentItem : ContentItem, IDictionary +{ + private readonly IDictionary _items = new Dictionary(); + private static readonly string TypePropertyName = typeof(ContentItem).GetProperty(nameof(Type))?.GetCustomAttribute()?.Name ?? string.Empty; + + public JsonElement this[string key] + { + get => _items[key]; + set { - get => _items[key]; - set - { - SetKnownProperty(key, value); - _items[key] = value; - } + SetKnownProperty(key, value); + _items[key] = value; } + } - public ICollection Keys => _items.Keys; + public ICollection Keys => _items.Keys; - public ICollection Values => _items.Values; + public ICollection Values => _items.Values; - public int Count => _items.Count; + public int Count => _items.Count; - public bool IsReadOnly => _items.IsReadOnly; + public bool IsReadOnly => _items.IsReadOnly; - public void Add(string key, JsonElement value) - { - SetKnownProperty(key, value); - _items.Add(key, value); - } + public void Add(string key, JsonElement value) + { + SetKnownProperty(key, value); + _items.Add(key, value); + } - public void Add(KeyValuePair item) - { - SetKnownProperty(item.Key, item.Value); - _items.Add(item); - } + public void Add(KeyValuePair item) + { + SetKnownProperty(item.Key, item.Value); + _items.Add(item); + } - public void Clear() - { - Type = string.Empty; - _items.Clear(); - } + public void Clear() + { + Type = string.Empty; + _items.Clear(); + } - public bool Contains(KeyValuePair item) => _items.Contains(item); + public bool Contains(KeyValuePair item) => _items.Contains(item); - public bool ContainsKey(string key) => _items.ContainsKey(key); + public bool ContainsKey(string key) => _items.ContainsKey(key); - public void CopyTo(KeyValuePair[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); + public void CopyTo(KeyValuePair[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); - public IEnumerator> GetEnumerator() => _items.GetEnumerator(); + public IEnumerator> GetEnumerator() => _items.GetEnumerator(); - public bool Remove(string key) + public bool Remove(string key) + { + if (_items.Remove(key)) { - if (_items.Remove(key)) - { - SetKnownProperty(key, null); - return true; - } - - return false; + SetKnownProperty(key, null); + return true; } - public bool Remove(KeyValuePair item) - { - if (_items.Remove(item.Key)) - { - SetKnownProperty(item.Key, null); - return true; - } + return false; + } - return false; + public bool Remove(KeyValuePair item) + { + if (_items.Remove(item.Key)) + { + SetKnownProperty(item.Key, null); + return true; } - public bool TryGetValue(string key, [MaybeNullWhen(false)] out JsonElement value) => _items.TryGetValue(key, out value); + return false; + } + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out JsonElement value) => _items.TryGetValue(key, out value); - IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); - private void SetKnownProperty(string key, JsonElement? value) + private void SetKnownProperty(string key, JsonElement? value) + { + if (key == TypePropertyName) { - if (key == TypePropertyName) - { - Type = value?.GetString() ?? string.Empty; - } + Type = value?.GetString() ?? string.Empty; } } +} - public class ContentItemWindow - { - [JsonPropertyName("targetName")] - public string? TargetName { get; set; } +public class ContentItemWindow +{ + [JsonPropertyName("targetName")] + public string? TargetName { get; set; } - [JsonPropertyName("windowFeatures")] - public string? WindowFeatures { get; set; } + [JsonPropertyName("windowFeatures")] + public string? WindowFeatures { get; set; } - [JsonPropertyName("width")] - public int? Width { get; set; } + [JsonPropertyName("width")] + public int? Width { get; set; } - [JsonPropertyName("height")] - public int? Height { get; set; } - } + [JsonPropertyName("height")] + public int? Height { get; set; } +} - public class ContentItemThumbnail - { - [JsonPropertyName("url")] - public required string Url { get; set; } +public class ContentItemThumbnail +{ + [JsonPropertyName("url")] + public required string Url { get; set; } - [JsonPropertyName("width")] - public int? Width { get; set; } + [JsonPropertyName("width")] + public int? Width { get; set; } - [JsonPropertyName("height")] - public int? Height { get; set; } - } + [JsonPropertyName("height")] + public int? Height { get; set; } +} - public class ContentItemIcon - { - [JsonPropertyName("url")] - public required string Url { get; set; } +public class ContentItemIcon +{ + [JsonPropertyName("url")] + public required string Url { get; set; } - [JsonPropertyName("width")] - public int? Width { get; set; } + [JsonPropertyName("width")] + public int? Width { get; set; } - [JsonPropertyName("height")] - public int? Height { get; set; } - } + [JsonPropertyName("height")] + public int? Height { get; set; } } \ No newline at end of file diff --git a/NP.Lti13Platform.DeepLinking/Populators/DeepLinkingPopulator.cs b/NP.Lti13Platform.DeepLinking/Populators/DeepLinkingPopulator.cs index c417f1c..3db93cf 100644 --- a/NP.Lti13Platform.DeepLinking/Populators/DeepLinkingPopulator.cs +++ b/NP.Lti13Platform.DeepLinking/Populators/DeepLinkingPopulator.cs @@ -7,100 +7,99 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.DeepLinking.Populators +namespace NP.Lti13Platform.DeepLinking.Populators; + +public interface IDeepLinkingMessage : ILaunchPresentationMessage { - public interface IDeepLinkingMessage : ILaunchPresentationMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/version")] - string LtiVersion { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/version")] + string LtiVersion { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/deployment_id")] - string DeploymentId { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/deployment_id")] + string DeploymentId { get; set; } - [JsonPropertyName("https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings")] - DeepLinkSettingsMessage DeepLinkSettings { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings")] + DeepLinkSettingsMessage DeepLinkSettings { get; set; } - public class DeepLinkSettingsMessage - { - [JsonPropertyName("deep_link_return_url")] - public required string DeepLinkReturnUrl { get; set; } + public class DeepLinkSettingsMessage + { + [JsonPropertyName("deep_link_return_url")] + public required string DeepLinkReturnUrl { get; set; } - [JsonPropertyName("accept_types")] - public required IEnumerable AcceptTypes { get; set; } + [JsonPropertyName("accept_types")] + public required IEnumerable AcceptTypes { get; set; } - [JsonPropertyName("accept_presentation_document_targets")] - public required IEnumerable AcceptPresentationDocumentTargets { get; set; } + [JsonPropertyName("accept_presentation_document_targets")] + public required IEnumerable AcceptPresentationDocumentTargets { get; set; } - [JsonPropertyName("accept_media_types")] - public string? AcceptMediaTypesSerialized => AcceptMediaTypes == null ? null : string.Join(",", AcceptMediaTypes); + [JsonPropertyName("accept_media_types")] + public string? AcceptMediaTypesSerialized => AcceptMediaTypes == null ? null : string.Join(",", AcceptMediaTypes); - [JsonIgnore] - public IEnumerable? AcceptMediaTypes { get; set; } + [JsonIgnore] + public IEnumerable? AcceptMediaTypes { get; set; } - [JsonPropertyName("accept_multiple")] - public bool? AcceptMultiple { get; set; } + [JsonPropertyName("accept_multiple")] + public bool? AcceptMultiple { get; set; } - [JsonPropertyName("accept_lineitem")] - public bool? AcceptLineItem { get; set; } + [JsonPropertyName("accept_lineitem")] + public bool? AcceptLineItem { get; set; } - [JsonPropertyName("auto_create")] - public bool? AutoCreate { get; set; } + [JsonPropertyName("auto_create")] + public bool? AutoCreate { get; set; } - [JsonPropertyName("title")] - public string? Title { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonPropertyName("text")] - public string? Text { get; set; } + [JsonPropertyName("text")] + public string? Text { get; set; } - [JsonPropertyName("data")] - public string? Data { get; set; } - } + [JsonPropertyName("data")] + public string? Data { get; set; } } +} - public class DeepLinkingPopulator(LinkGenerator linkGenerator, ILti13DeepLinkingConfigService deepLinkingService) : Populator +public class DeepLinkingPopulator(LinkGenerator linkGenerator, ILti13DeepLinkingConfigService deepLinkingService) : Populator +{ + public override async Task PopulateAsync(IDeepLinkingMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IDeepLinkingMessage obj, MessageScope scope, CancellationToken cancellationToken = default) - { - obj.LtiVersion = "1.3.0"; - obj.DeploymentId = scope.Deployment.Id; + obj.LtiVersion = "1.3.0"; + obj.DeploymentId = scope.Deployment.Id; - DeepLinkSettingsOverride? deepLinkSettings = default; - LaunchPresentationOverride? launchPresentation = default; + DeepLinkSettingsOverride? deepLinkSettings = default; + LaunchPresentationOverride? launchPresentation = default; - if (!string.IsNullOrWhiteSpace(scope.MessageHint)) - { - var parts = Encoding.UTF8.GetString(Convert.FromBase64String(scope.MessageHint)).Split('|'); - deepLinkSettings = JsonSerializer.Deserialize(parts[0]); - launchPresentation = JsonSerializer.Deserialize(parts[1]); - } + if (!string.IsNullOrWhiteSpace(scope.MessageHint)) + { + var parts = Encoding.UTF8.GetString(Convert.FromBase64String(scope.MessageHint)).Split('|'); + deepLinkSettings = JsonSerializer.Deserialize(parts[0]); + launchPresentation = JsonSerializer.Deserialize(parts[1]); + } - var config = await deepLinkingService.GetConfigAsync(scope.Tool.ClientId, cancellationToken); + var config = await deepLinkingService.GetConfigAsync(scope.Tool.ClientId, cancellationToken); - obj.DeepLinkSettings = new IDeepLinkingMessage.DeepLinkSettingsMessage + obj.DeepLinkSettings = new IDeepLinkingMessage.DeepLinkSettingsMessage + { + AcceptPresentationDocumentTargets = deepLinkSettings?.AcceptPresentationDocumentTargets ?? config.AcceptPresentationDocumentTargets, + AcceptTypes = deepLinkSettings?.AcceptTypes ?? config.AcceptTypes, + DeepLinkReturnUrl = linkGenerator.GetUriByName(RouteNames.DEEP_LINKING_RESPONSE, new { contextId = scope.Context?.Id }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)) ?? string.Empty, + AcceptLineItem = deepLinkSettings?.AcceptLineItem ?? config.AcceptLineItem, + AcceptMediaTypes = deepLinkSettings?.AcceptMediaTypes ?? config.AcceptMediaTypes, + AcceptMultiple = deepLinkSettings?.AcceptMultiple ?? config.AcceptMultiple, + AutoCreate = deepLinkSettings?.AutoCreate ?? config.AutoCreate, + Data = deepLinkSettings?.Data, + Text = deepLinkSettings?.Text, + Title = deepLinkSettings?.Title, + }; + + if (launchPresentation != null) + { + obj.LaunchPresentation = new ILaunchPresentationMessage.LaunchPresentationDefinition { - AcceptPresentationDocumentTargets = deepLinkSettings?.AcceptPresentationDocumentTargets ?? config.AcceptPresentationDocumentTargets, - AcceptTypes = deepLinkSettings?.AcceptTypes ?? config.AcceptTypes, - DeepLinkReturnUrl = linkGenerator.GetUriByName(RouteNames.DEEP_LINKING_RESPONSE, new { contextId = scope.Context?.Id }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)) ?? string.Empty, - AcceptLineItem = deepLinkSettings?.AcceptLineItem ?? config.AcceptLineItem, - AcceptMediaTypes = deepLinkSettings?.AcceptMediaTypes ?? config.AcceptMediaTypes, - AcceptMultiple = deepLinkSettings?.AcceptMultiple ?? config.AcceptMultiple, - AutoCreate = deepLinkSettings?.AutoCreate ?? config.AutoCreate, - Data = deepLinkSettings?.Data, - Text = deepLinkSettings?.Text, - Title = deepLinkSettings?.Title, + DocumentTarget = launchPresentation.DocumentTarget, + Height = launchPresentation.Height, + Locale = launchPresentation.Locale, + ReturnUrl = launchPresentation.ReturnUrl, + Width = launchPresentation.Width, }; - - if (launchPresentation != null) - { - obj.LaunchPresentation = new ILaunchPresentationMessage.LaunchPresentationDefinition - { - DocumentTarget = launchPresentation.DocumentTarget, - Height = launchPresentation.Height, - Locale = launchPresentation.Locale, - ReturnUrl = launchPresentation.ReturnUrl, - Width = launchPresentation.Width, - }; - } } } } diff --git a/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingConfigService.cs b/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingConfigService.cs index 6a2a7ab..3967902 100644 --- a/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingConfigService.cs +++ b/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingConfigService.cs @@ -2,19 +2,18 @@ using Microsoft.Extensions.Options; using NP.Lti13Platform.DeepLinking.Configs; -namespace NP.Lti13Platform.DeepLinking.Services +namespace NP.Lti13Platform.DeepLinking.Services; + +internal class DefaultDeepLinkingConfigService(IOptionsMonitor config, IHttpContextAccessor httpContextAccessor) : ILti13DeepLinkingConfigService { - internal class DefaultDeepLinkingConfigService(IOptionsMonitor config, IHttpContextAccessor httpContextAccessor) : ILti13DeepLinkingConfigService + public async Task GetConfigAsync(string clientId, CancellationToken cancellationToken = default) { - public async Task GetConfigAsync(string clientId, CancellationToken cancellationToken = default) + var deepLinkingConfig = config.CurrentValue; + if (deepLinkingConfig.ServiceAddress == DeepLinkingConfig.DefaultUri) { - var deepLinkingConfig = config.CurrentValue; - if (deepLinkingConfig.ServiceAddress == DeepLinkingConfig.DefaultUri) - { - deepLinkingConfig = deepLinkingConfig with { ServiceAddress = new UriBuilder(httpContextAccessor.HttpContext?.Request.Scheme, httpContextAccessor.HttpContext?.Request.Host.Value).Uri }; - } - - return await Task.FromResult(deepLinkingConfig); + deepLinkingConfig = deepLinkingConfig with { ServiceAddress = new UriBuilder(httpContextAccessor.HttpContext?.Request.Scheme, httpContextAccessor.HttpContext?.Request.Host.Value).Uri }; } + + return await Task.FromResult(deepLinkingConfig); } } diff --git a/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingHandler.cs b/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingHandler.cs index 54c81e2..d11aca5 100644 --- a/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingHandler.cs +++ b/NP.Lti13Platform.DeepLinking/Services/DefaultDeepLinkingHandler.cs @@ -1,17 +1,16 @@ using Microsoft.AspNetCore.Http; using System.Net.Mime; -namespace NP.Lti13Platform.DeepLinking.Services +namespace NP.Lti13Platform.DeepLinking.Services; + +internal class DefaultDeepLinkingHandler() : ILti13DeepLinkingHandler { - internal class DefaultDeepLinkingHandler() : ILti13DeepLinkingHandler - { - public Task HandleResponseAsync(string clientId, string deploymentId, string? contextId, DeepLinkResponse response, CancellationToken cancellationToken = default) => - Task.FromResult(Results.Content(@$" + public Task HandleResponseAsync(string clientId, string deploymentId, string? contextId, DeepLinkResponse response, CancellationToken cancellationToken = default) => + Task.FromResult(Results.Content(@$"

This is the end of the Deep Linking flow. Please override the {nameof(ILti13DeepLinkingHandler)} for a better experience.

", - MediaTypeNames.Text.Html)); - } + MediaTypeNames.Text.Html)); } diff --git a/NP.Lti13Platform.DeepLinking/Startup.cs b/NP.Lti13Platform.DeepLinking/Startup.cs index 05d5486..03ec3f5 100644 --- a/NP.Lti13Platform.DeepLinking/Startup.cs +++ b/NP.Lti13Platform.DeepLinking/Startup.cs @@ -64,23 +64,11 @@ public static IEndpointRouteBuilder UseLti13PlatformDeepLinking(this IEndpointRo async ([FromForm] DeepLinkResponseRequest request, string? contextId, ILogger logger, ILti13TokenConfigService tokenService, ILti13CoreDataService coreDataService, ILti13DeepLinkingDataService deepLinkingDataService, ILti13DeepLinkingConfigService deepLinkingService, ILti13DeepLinkingHandler deepLinkingHandler, CancellationToken cancellationToken) => { const string DEEP_LINKING_SPEC = "https://www.imsglobal.org/spec/lti-dl/v2p0/#deep-linking-response-message"; - const string INVALID_CLIENT = "invalid_client"; const string INVALID_REQUEST = "invalid_request"; - const string JWT_REQUIRED = "JWT is required"; - const string DEPLOYMENT_ID_REQUIRED = "deployment_id is required"; - const string CLIENT_ID_REQUIRED = "client_id is required"; - const string DEPLOYMENT_ID_INVALID = "deployment_id is invalid"; - const string MESSAGE_TYPE_INVALID = "message_type is invalid"; - const string VERSION_INVALID = "version is invalid"; - const string UNKNOWN = "unknown"; - const string TYPE = "type"; - const string VERSION = "1.3.0"; - const string LTI_DEEP_LINKING_RESPONSE = "LtiDeepLinkingResponse"; - const string DEPLOYMENT_ID_CLAIM = "https://purl.imsglobal.org/spec/lti/claim/deployment_id"; if (string.IsNullOrWhiteSpace(request.Jwt)) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = JWT_REQUIRED, Error_Uri = DEEP_LINKING_SPEC }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "JWT is required", Error_Uri = DEEP_LINKING_SPEC }); } var jwt = new JsonWebToken(request.Jwt); @@ -89,18 +77,18 @@ public static IEndpointRouteBuilder UseLti13PlatformDeepLinking(this IEndpointRo var tool = await coreDataService.GetToolAsync(clientId, cancellationToken); if (tool?.Jwks == null) { - return Results.NotFound(new { Error = INVALID_CLIENT, Error_Description = CLIENT_ID_REQUIRED, Error_Uri = DEEP_LINKING_SPEC }); + return Results.NotFound(new { Error = "invalid_client", Error_Description = "client_id is required", Error_Uri = DEEP_LINKING_SPEC }); } - if (!jwt.TryGetClaim(DEPLOYMENT_ID_CLAIM, out var deploymentIdClaim)) + if (!jwt.TryGetClaim("https://purl.imsglobal.org/spec/lti/claim/deployment_id", out var deploymentIdClaim)) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = DEPLOYMENT_ID_REQUIRED, Error_Uri = DEEP_LINKING_SPEC }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "deployment_id is required", Error_Uri = DEEP_LINKING_SPEC }); } var deployment = await coreDataService.GetDeploymentAsync(deploymentIdClaim.Value, cancellationToken); if (deployment == null || deployment.ToolId != tool.Id) { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = DEPLOYMENT_ID_INVALID, Error_Uri = DEEP_LINKING_SPEC }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "deployment_id is invalid", Error_Uri = DEEP_LINKING_SPEC }); } var tokenConfig = await tokenService.GetTokenConfigAsync(tool.ClientId, cancellationToken); @@ -117,14 +105,14 @@ public static IEndpointRouteBuilder UseLti13PlatformDeepLinking(this IEndpointRo return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = validatedToken.Exception.Message, Error_Uri = DEEP_LINKING_SPEC }); } - if (!validatedToken.Claims.TryGetValue("https://purl.imsglobal.org/spec/lti/claim/message_type", out var messageType) || (string)messageType != LTI_DEEP_LINKING_RESPONSE) + if (!validatedToken.Claims.TryGetValue("https://purl.imsglobal.org/spec/lti/claim/message_type", out var messageType) || (string)messageType != "LtiDeepLinkingResponse") { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = MESSAGE_TYPE_INVALID, Error_Uri = DEEP_LINKING_SPEC }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "message_type is invalid", Error_Uri = DEEP_LINKING_SPEC }); } - if (!validatedToken.Claims.TryGetValue("https://purl.imsglobal.org/spec/lti/claim/version", out var version) || (string)version != VERSION) + if (!validatedToken.Claims.TryGetValue("https://purl.imsglobal.org/spec/lti/claim/version", out var version) || (string)version != "1.3.0") { - return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = VERSION_INVALID, Error_Uri = DEEP_LINKING_SPEC }); + return Results.BadRequest(new { Error = INVALID_REQUEST, Error_Description = "version is invalid", Error_Uri = DEEP_LINKING_SPEC }); } var deepLinkingConfig = await deepLinkingService.GetConfigAsync(tool.ClientId, cancellationToken); @@ -132,7 +120,7 @@ public static IEndpointRouteBuilder UseLti13PlatformDeepLinking(this IEndpointRo List<(ContentItem ContentItem, LtiResourceLinkContentItem? LtiResourceLink)> contentItems = validatedToken.ClaimsIdentity.FindAll("https://purl.imsglobal.org/spec/lti-dl/claim/content_items") .Select((x, ix) => { - var type = JsonDocument.Parse(x.Value).RootElement.GetProperty(TYPE).GetString() ?? UNKNOWN; + var type = JsonDocument.Parse(x.Value).RootElement.GetProperty("type").GetString() ?? "unknown"; var customItem = (ContentItem)JsonSerializer.Deserialize(x.Value, deepLinkingConfig.ContentItemTypes[(tool.ClientId, type)])!; return (customItem, type == ContentItemType.LtiResourceLink ? JsonSerializer.Deserialize(x.Value) : null); diff --git a/NP.Lti13Platform.DynamicRegistration/Class1.cs b/NP.Lti13Platform.DynamicRegistration/Class1.cs deleted file mode 100644 index 5a1a472..0000000 --- a/NP.Lti13Platform.DynamicRegistration/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NP.Lti13Platform.DynamicRegistration -{ - public class Class1 - { - - } -} diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Configs/EndpointsConfig.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Configs/EndpointsConfig.cs index 15b7c04..a872fa1 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Configs/EndpointsConfig.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Configs/EndpointsConfig.cs @@ -1,12 +1,11 @@ -namespace NP.Lti13Platform.NameRoleProvisioningServices.Configs +namespace NP.Lti13Platform.NameRoleProvisioningServices.Configs; + +public class EndpointsConfig { - public class EndpointsConfig - { - /// - /// Endpoint used to get a list of members in the context. - /// Must include route parameters for {deploymentId} and {contextId}. - /// - /// Default: /lti13/{deploymentId}/{contextId}/memberships - public string NamesAndRoleProvisioningServicesUrl { get; set; } = "/lti13/{deploymentId}/{contextId}/memberships"; - } + /// + /// Endpoint used to get a list of members in the context. + /// Must include route parameters for {deploymentId} and {contextId}. + /// + /// Default: /lti13/{deploymentId}/{contextId}/memberships + public string NamesAndRoleProvisioningServicesUrl { get; set; } = "/lti13/{deploymentId}/{contextId}/memberships"; } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Constants.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Constants.cs index f6da4b6..5e14b16 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Constants.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Constants.cs @@ -2,16 +2,16 @@ { internal static class RouteNames { - public const string GET_MEMBERSHIPS = "GET_MEMBERSHIPS"; + public static readonly string GET_MEMBERSHIPS = "GET_MEMBERSHIPS"; } internal static class Lti13ContentTypes { - internal const string MembershipContainer = "application/vnd.ims.lti-nrps.v2.membershipcontainer+json"; + internal static readonly string MembershipContainer = "application/vnd.ims.lti-nrps.v2.membershipcontainer+json"; } public static class Lti13ServiceScopes { - public const string MembershipReadOnly = "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"; + public static readonly string MembershipReadOnly = "https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly"; } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessage.cs b/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessage.cs index e42b6e4..933c264 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessage.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessage.cs @@ -1,10 +1,9 @@ using System.Text.Json.Serialization; -namespace NP.Lti13Platform.NameRoleProvisioningServices +namespace NP.Lti13Platform.NameRoleProvisioningServices; + +public class NameRoleProvisioningMessage { - public class NameRoleProvisioningMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/message_type")] - public required string MessageType { get; set; } - } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/message_type")] + public required string MessageType { get; set; } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessageTypeResolver.cs b/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessageTypeResolver.cs index 035594c..819ea81 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessageTypeResolver.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/NameRoleProvisioningMessageTypeResolver.cs @@ -1,36 +1,35 @@ using System.Text.Json; using System.Text.Json.Serialization.Metadata; -namespace NP.Lti13Platform.NameRoleProvisioningServices +namespace NP.Lti13Platform.NameRoleProvisioningServices; + +internal class NameRoleProvisioningMessageTypeResolver : DefaultJsonTypeInfoResolver { - internal class NameRoleProvisioningMessageTypeResolver : DefaultJsonTypeInfoResolver + private static readonly HashSet derivedTypes = []; + + public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) { - private static readonly HashSet derivedTypes = []; + var jsonTypeInfo = base.GetTypeInfo(type, options); - public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) + var baseType = typeof(NameRoleProvisioningMessage); + if (jsonTypeInfo.Type == baseType) { - var jsonTypeInfo = base.GetTypeInfo(type, options); - - var baseType = typeof(NameRoleProvisioningMessage); - if (jsonTypeInfo.Type == baseType) + jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions { - jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions - { - IgnoreUnrecognizedTypeDiscriminators = true, - }; + IgnoreUnrecognizedTypeDiscriminators = true, + }; - foreach (var derivedType in derivedTypes) - { - jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derivedType)); - } + foreach (var derivedType in derivedTypes) + { + jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(derivedType)); } - - return jsonTypeInfo; } - public static void AddDerivedType(Type type) - { - derivedTypes.Add(type); - } + return jsonTypeInfo; + } + + public static void AddDerivedType(Type type) + { + derivedTypes.Add(type); } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Populators/CustomPopulator.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Populators/CustomPopulator.cs index 53d4ebe..3ce8553 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Populators/CustomPopulator.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Populators/CustomPopulator.cs @@ -5,91 +5,90 @@ using NP.Lti13Platform.Core.Services; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.NameRoleProvisioningServices.Populators +namespace NP.Lti13Platform.NameRoleProvisioningServices.Populators; + +public interface ICustomMessage { - public interface ICustomMessage - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/custom")] - public IDictionary? Custom { get; set; } - } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti/claim/custom")] + public IDictionary? Custom { get; set; } +} + +public class CustomPopulator(ILti13CoreDataService dataService) : Populator +{ + private static readonly IEnumerable LineItemAttemptGradeVariables = [ + Lti13ResourceLinkVariables.AvailableUserStartDateTime, + Lti13ResourceLinkVariables.AvailableUserEndDateTime, + Lti13ResourceLinkVariables.SubmissionUserStartDateTime, + Lti13ResourceLinkVariables.SubmissionUserEndDateTime, + Lti13ResourceLinkVariables.LineItemUserReleaseDateTime]; - public class CustomPopulator(ILti13CoreDataService dataService) : Populator + public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope, CancellationToken cancellationToken = default) { - private static readonly IEnumerable LineItemAttemptGradeVariables = [ - Lti13ResourceLinkVariables.AvailableUserStartDateTime, - Lti13ResourceLinkVariables.AvailableUserEndDateTime, - Lti13ResourceLinkVariables.SubmissionUserStartDateTime, - Lti13ResourceLinkVariables.SubmissionUserEndDateTime, - Lti13ResourceLinkVariables.LineItemUserReleaseDateTime]; + var customDictionary = scope.Tool.Custom.Merge(scope.Deployment.Custom).Merge(scope.ResourceLink?.Custom); - public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope, CancellationToken cancellationToken = default) + if (customDictionary == null) { - var customDictionary = scope.Tool.Custom.Merge(scope.Deployment.Custom).Merge(scope.ResourceLink?.Custom); - - if (customDictionary == null) - { - return; - } + return; + } - IEnumerable mentoredUserIds = []; - if (customDictionary.Values.Any(v => v == Lti13UserVariables.ScopeMentor) && scope.Context != null ) + IEnumerable mentoredUserIds = []; + if (customDictionary.Values.Any(v => v == Lti13UserVariables.ScopeMentor) && scope.Context != null ) + { + var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.User.Id, cancellationToken); + if (membership != null && membership.Roles.Contains(Lti13ContextRoles.Mentor)) { - var membership = await dataService.GetMembershipAsync(scope.Context.Id, scope.UserScope.User.Id, cancellationToken); - if (membership != null && membership.Roles.Contains(Lti13ContextRoles.Mentor)) - { - mentoredUserIds = membership.MentoredUserIds; - } + mentoredUserIds = membership.MentoredUserIds; } + } - LineItem? lineItem = null; - Attempt? attempt = null; - Grade? grade = null; - if (customDictionary.Values.Any(v => LineItemAttemptGradeVariables.Contains(v)) && scope.Context != null && scope.ResourceLink != null) + LineItem? lineItem = null; + Attempt? attempt = null; + Grade? grade = null; + if (customDictionary.Values.Any(v => LineItemAttemptGradeVariables.Contains(v)) && scope.Context != null && scope.ResourceLink != null) + { + var lineItems = await dataService.GetLineItemsAsync(scope.Deployment.Id, scope.Context.Id, pageIndex: 0, limit: 1, resourceLinkId: scope.ResourceLink.Id, cancellationToken: cancellationToken); + if (lineItems.TotalItems == 1) { - var lineItems = await dataService.GetLineItemsAsync(scope.Deployment.Id, scope.Context.Id, pageIndex: 0, limit: 1, resourceLinkId: scope.ResourceLink.Id, cancellationToken: cancellationToken); - if (lineItems.TotalItems == 1) - { - lineItem = lineItems.Items.First(); - - grade = await dataService.GetGradeAsync(lineItem.Id, scope.UserScope.User.Id, cancellationToken); - } + lineItem = lineItems.Items.First(); - attempt = await dataService.GetAttemptAsync(scope.ResourceLink.Id, scope.UserScope.User.Id, cancellationToken); + grade = await dataService.GetGradeAsync(lineItem.Id, scope.UserScope.User.Id, cancellationToken); } - var customPermissions = await dataService.GetCustomPermissions(scope.Deployment.Id, cancellationToken); + attempt = await dataService.GetAttemptAsync(scope.ResourceLink.Id, scope.UserScope.User.Id, cancellationToken); + } + + var customPermissions = await dataService.GetCustomPermissions(scope.Deployment.Id, cancellationToken); - var dictionaryValues = customDictionary.ToList(); - foreach (var kvp in dictionaryValues) + var dictionaryValues = customDictionary.ToList(); + foreach (var kvp in dictionaryValues) + { + var value = kvp.Value switch { - var value = kvp.Value switch - { - Lti13UserVariables.Id when customPermissions.UserId => scope.UserScope.User.Id, - Lti13UserVariables.Image when customPermissions.UserImage => scope.UserScope.User.ImageUrl, - Lti13UserVariables.Username when customPermissions.UserUsername => scope.UserScope.User.Username, - Lti13UserVariables.Org when customPermissions.UserOrg => string.Join(',', scope.UserScope.User.Orgs), - Lti13UserVariables.ScopeMentor when customPermissions.UserScopeMentor => string.Join(',', mentoredUserIds), - Lti13UserVariables.GradeLevelsOneRoster when customPermissions.UserGradeLevelsOneRoster => string.Join(',', scope.UserScope.User.OneRosterGrades), + Lti13UserVariables.Id when customPermissions.UserId => scope.UserScope.User.Id, + Lti13UserVariables.Image when customPermissions.UserImage => scope.UserScope.User.ImageUrl, + Lti13UserVariables.Username when customPermissions.UserUsername => scope.UserScope.User.Username, + Lti13UserVariables.Org when customPermissions.UserOrg => string.Join(',', scope.UserScope.User.Orgs), + Lti13UserVariables.ScopeMentor when customPermissions.UserScopeMentor => string.Join(',', mentoredUserIds), + Lti13UserVariables.GradeLevelsOneRoster when customPermissions.UserGradeLevelsOneRoster => string.Join(',', scope.UserScope.User.OneRosterGrades), - Lti13ResourceLinkVariables.AvailableUserStartDateTime when customPermissions.ResourceLinkAvailableUserStartDateTime => attempt?.AvailableStartDateTime?.ToString("O"), - Lti13ResourceLinkVariables.AvailableUserEndDateTime when customPermissions.ResourceLinkAvailableUserEndDateTime => attempt?.AvailableEndDateTime?.ToString("O"), - Lti13ResourceLinkVariables.SubmissionUserStartDateTime when customPermissions.ResourceLinkSubmissionUserStartDateTime => attempt?.SubmisstionStartDateTime?.ToString("O"), - Lti13ResourceLinkVariables.SubmissionUserEndDateTime when customPermissions.ResourceLinkSubmissionUserEndDateTime => attempt?.SubmissionEndDateTime?.ToString("O"), - Lti13ResourceLinkVariables.LineItemUserReleaseDateTime when customPermissions.ResourceLinkLineItemUserReleaseDateTime => grade?.ReleaseDateTime?.ToString("O"), - _ => null - }; + Lti13ResourceLinkVariables.AvailableUserStartDateTime when customPermissions.ResourceLinkAvailableUserStartDateTime => attempt?.AvailableStartDateTime?.ToString("O"), + Lti13ResourceLinkVariables.AvailableUserEndDateTime when customPermissions.ResourceLinkAvailableUserEndDateTime => attempt?.AvailableEndDateTime?.ToString("O"), + Lti13ResourceLinkVariables.SubmissionUserStartDateTime when customPermissions.ResourceLinkSubmissionUserStartDateTime => attempt?.SubmisstionStartDateTime?.ToString("O"), + Lti13ResourceLinkVariables.SubmissionUserEndDateTime when customPermissions.ResourceLinkSubmissionUserEndDateTime => attempt?.SubmissionEndDateTime?.ToString("O"), + Lti13ResourceLinkVariables.LineItemUserReleaseDateTime when customPermissions.ResourceLinkLineItemUserReleaseDateTime => grade?.ReleaseDateTime?.ToString("O"), + _ => null + }; - if (value == null) - { - customDictionary.Remove(kvp.Key); - } - else - { - customDictionary[kvp.Key] = value; - } + if (value == null) + { + customDictionary.Remove(kvp.Key); + } + else + { + customDictionary[kvp.Key] = value; } - - obj.Custom = obj.Custom.Merge(customDictionary); } + + obj.Custom = obj.Custom.Merge(customDictionary); } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Populators/ServiceEndpointsPopulator.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Populators/ServiceEndpointsPopulator.cs index b5da5f3..b79875f 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Populators/ServiceEndpointsPopulator.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Populators/ServiceEndpointsPopulator.cs @@ -4,37 +4,36 @@ using NP.Lti13Platform.NameRoleProvisioningServices.Services; using System.Text.Json.Serialization; -namespace NP.Lti13Platform.NameRoleProvisioningServices.Populators +namespace NP.Lti13Platform.NameRoleProvisioningServices.Populators; + +public interface IServiceEndpoints { - public interface IServiceEndpoints - { - [JsonPropertyName("https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice")] - public ServiceEndpoints? NamesRoleService { get; set; } + [JsonPropertyName("https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice")] + public ServiceEndpoints? NamesRoleService { get; set; } - public class ServiceEndpoints - { - [JsonPropertyName("context_memberships_url")] - public required string ContextMembershipsUrl { get; set; } + public class ServiceEndpoints + { + [JsonPropertyName("context_memberships_url")] + public required string ContextMembershipsUrl { get; set; } - [JsonPropertyName("service_versions")] - public required IEnumerable ServiceVersions { get; set; } - } + [JsonPropertyName("service_versions")] + public required IEnumerable ServiceVersions { get; set; } } +} - public class ServiceEndpointsPopulator(LinkGenerator linkGenerator, ILti13NameRoleProvisioningConfigService nameRoleProvisioningService) : Populator +public class ServiceEndpointsPopulator(LinkGenerator linkGenerator, ILti13NameRoleProvisioningConfigService nameRoleProvisioningService) : Populator +{ + public override async Task PopulateAsync(IServiceEndpoints obj, MessageScope scope, CancellationToken cancellationToken = default) { - public override async Task PopulateAsync(IServiceEndpoints obj, MessageScope scope, CancellationToken cancellationToken = default) + if (scope.Tool.ServiceScopes.Contains(Lti13ServiceScopes.MembershipReadOnly) && !string.IsNullOrWhiteSpace(scope.Context?.Id)) { - if (scope.Tool.ServiceScopes.Contains(Lti13ServiceScopes.MembershipReadOnly) && !string.IsNullOrWhiteSpace(scope.Context?.Id)) - { - var config = await nameRoleProvisioningService.GetConfigAsync(scope.Tool.ClientId, cancellationToken); + var config = await nameRoleProvisioningService.GetConfigAsync(scope.Tool.ClientId, cancellationToken); - obj.NamesRoleService = new IServiceEndpoints.ServiceEndpoints - { - ContextMembershipsUrl = linkGenerator.GetUriByName(RouteNames.GET_MEMBERSHIPS, new { deploymentId = scope.Deployment.Id, contextId = scope.Context.Id }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)) ?? string.Empty, - ServiceVersions = ["2.0"] - }; - } + obj.NamesRoleService = new IServiceEndpoints.ServiceEndpoints + { + ContextMembershipsUrl = linkGenerator.GetUriByName(RouteNames.GET_MEMBERSHIPS, new { deploymentId = scope.Deployment.Id, contextId = scope.Context.Id }, config.ServiceAddress.Scheme, new HostString(config.ServiceAddress.Authority)) ?? string.Empty, + ServiceVersions = ["2.0"] + }; } } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Services/DefaultNameRoleProvisioningConfigService.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Services/DefaultNameRoleProvisioningConfigService.cs index 703485d..ff4bf1e 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Services/DefaultNameRoleProvisioningConfigService.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Services/DefaultNameRoleProvisioningConfigService.cs @@ -2,19 +2,18 @@ using Microsoft.Extensions.Options; using NP.Lti13Platform.NameRoleProvisioningServices.Configs; -namespace NP.Lti13Platform.NameRoleProvisioningServices.Services +namespace NP.Lti13Platform.NameRoleProvisioningServices.Services; + +internal class DefaultNameRoleProvisioningConfigService(IOptionsMonitor config, IHttpContextAccessor httpContextAccessor) : ILti13NameRoleProvisioningConfigService { - internal class DefaultNameRoleProvisioningConfigService(IOptionsMonitor config, IHttpContextAccessor httpContextAccessor) : ILti13NameRoleProvisioningConfigService + public async Task GetConfigAsync(string clientId, CancellationToken cancellationToken = default) { - public async Task GetConfigAsync(string clientId, CancellationToken cancellationToken = default) + var servicesConfig = config.CurrentValue; + if (servicesConfig.ServiceAddress == ServicesConfig.DefaultUri) { - var servicesConfig = config.CurrentValue; - if (servicesConfig.ServiceAddress == ServicesConfig.DefaultUri) - { - servicesConfig = servicesConfig with { ServiceAddress = new UriBuilder(httpContextAccessor.HttpContext?.Request.Scheme, httpContextAccessor.HttpContext?.Request.Host.Value).Uri }; - } - - return await Task.FromResult(servicesConfig); + servicesConfig = servicesConfig with { ServiceAddress = new UriBuilder(httpContextAccessor.HttpContext?.Request.Scheme, httpContextAccessor.HttpContext?.Request.Host.Value).Uri }; } + + return await Task.FromResult(servicesConfig); } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Services/ILti13NameRoleProvisioningDataService.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Services/ILti13NameRoleProvisioningDataService.cs index e0b78c8..2e16982 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Services/ILti13NameRoleProvisioningDataService.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Services/ILti13NameRoleProvisioningDataService.cs @@ -5,6 +5,6 @@ namespace NP.Lti13Platform.NameRoleProvisioningServices.Services public interface ILti13NameRoleProvisioningDataService { Task> GetMembershipsAsync(string deploymentId, string contextId, int pageIndex, int limit, string? role, string? resourceLinkId, DateTime? asOfDate = null, CancellationToken cancellationToken = default); - Task> GetUserPermissionsAsync(string deploymentId, IEnumerable userIds, CancellationToken cancellationToken = default); + Task> GetUserPermissionsAsync(string deploymentId, IEnumerable userIds, CancellationToken cancellationToken = default); } } diff --git a/NP.Lti13Platform.NameRoleProvisioningServices/Startup.cs b/NP.Lti13Platform.NameRoleProvisioningServices/Startup.cs index 2bd8881..677d627 100644 --- a/NP.Lti13Platform.NameRoleProvisioningServices/Startup.cs +++ b/NP.Lti13Platform.NameRoleProvisioningServices/Startup.cs @@ -112,13 +112,6 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices routeBuilder.MapGet(config.NamesAndRoleProvisioningServicesUrl, async (IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor, ILti13CoreDataService coreDataService, ILti13NameRoleProvisioningDataService nrpsDataService, LinkGenerator linkGenerator, string deploymentId, string contextId, string? role, string? rlid, int? limit, int pageIndex = 0, long? since = null, CancellationToken cancellationToken = default) => { - const string RESOURCE_LINK_UNAVAILABLE = "resource link unavailable"; - const string RESOURCE_LINK_UNAVAILABLE_DESCRIPTION = "resource link does not exist in the context"; - const string RESOURCE_LINK_UNAVAILABLE_URI = "https://www.imsglobal.org/spec/lti-nrps/v2p0#access-restriction"; - const string ACTIVE = "Active"; - const string INACTIVE = "Inactive"; - const string DELETED = "Deleted"; - var httpContext = httpContextAccessor.HttpContext!; var context = await coreDataService.GetContextAsync(contextId, cancellationToken); @@ -137,7 +130,7 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices var deployment = await coreDataService.GetDeploymentAsync(deploymentId, cancellationToken); if (deployment?.ToolId != tool.Id) { - return Results.BadRequest(new { Error = RESOURCE_LINK_UNAVAILABLE, Error_Description = RESOURCE_LINK_UNAVAILABLE_DESCRIPTION, Error_Uri = RESOURCE_LINK_UNAVAILABLE_URI }); + return Results.NotFound(); } var membersResponse = await nrpsDataService.GetMembershipsAsync(deploymentId, contextId, pageIndex, limit ?? int.MaxValue, role, rlid, cancellationToken: cancellationToken); @@ -177,12 +170,12 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices var resourceLink = await coreDataService.GetResourceLinkAsync(rlid, cancellationToken); if (resourceLink == null || resourceLink.DeploymentId != deploymentId) { - return Results.BadRequest(new { Error = RESOURCE_LINK_UNAVAILABLE, Error_Description = RESOURCE_LINK_UNAVAILABLE_DESCRIPTION, Error_Uri = RESOURCE_LINK_UNAVAILABLE_URI }); + return Results.BadRequest(new { Error = "resource link unavailable", Error_Description = "resource link does not exist in the context", Error_Uri = "https://www.imsglobal.org/spec/lti-nrps/v2p0#access-restriction" }); } var messageTypes = LtiMessageTypes.ToDictionary(mt => mt.Key, mt => serviceProvider.GetKeyedServices(mt.Key)); - foreach (var currentUser in currentUsers) + foreach (var currentUser in currentUsers) // TODO: await in list { ICollection userMessages = []; messages.Add(currentUser.User.Id, userMessages); @@ -195,7 +188,7 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices resourceLink, MessageHint: null); - foreach (var messageType in messageTypes) + foreach (var messageType in messageTypes) // TODO: await in list { var message = serviceProvider.GetKeyedService(messageType.Key); @@ -203,7 +196,7 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices { message.MessageType = messageType.Key.Name; - foreach (var populator in messageType.Value) + foreach (var populator in messageType.Value) // TODO: await in list { await populator.PopulateAsync(message, scope, cancellationToken); } @@ -218,7 +211,7 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices var userPermissions = await nrpsDataService.GetUserPermissionsAsync(deployment.Id, usersWithRoles.Select(u => u.User.Id), cancellationToken); - var users = usersWithRoles.Join(userPermissions, u => u.User.Id, p => p.UserId, (u, p) => (u.User, u.Membership, p.UserPermissions, u.IsCurrent)); + var users = usersWithRoles.Join(userPermissions, u => u.User.Id, p => p.UserId, (u, p) => (u.User, u.Membership, UserPermissions: p, u.IsCurrent)); return Results.Json(new { @@ -242,9 +235,9 @@ public static IEndpointRouteBuilder UseLti13PlatformNameRoleProvisioningServices picture = x.UserPermissions.Picture ? x.User.Picture : null, status = x.Membership.Status switch { - MembershipStatus.Active when x.IsCurrent => ACTIVE, - MembershipStatus.Inactive when x.IsCurrent => INACTIVE, - _ => DELETED + MembershipStatus.Active when x.IsCurrent => "Active", + MembershipStatus.Inactive when x.IsCurrent => "Inactive", + _ => "Deleted" }, message = messages.TryGetValue(x.User.Id, out var message) ? message : null }; diff --git a/NP.Lti13Platform.ProctoringServices/Class1.cs b/NP.Lti13Platform.ProctoringServices/Class1.cs deleted file mode 100644 index b35b84d..0000000 --- a/NP.Lti13Platform.ProctoringServices/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NP.Lti13Platform.ProctoringServices -{ - public class Class1 - { - - } -} diff --git a/NP.Lti13Platform.SubmissionReviewService/Class1.cs b/NP.Lti13Platform.SubmissionReviewService/Class1.cs deleted file mode 100644 index eae271c..0000000 --- a/NP.Lti13Platform.SubmissionReviewService/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NP.Lti13Platform.SubmissionReviewService -{ - public class Class1 - { - - } -} diff --git a/NP.Lti13Platform.WebExample/Controllers/HomeController.cs b/NP.Lti13Platform.WebExample/Controllers/HomeController.cs index 58c8195..168b034 100644 --- a/NP.Lti13Platform.WebExample/Controllers/HomeController.cs +++ b/NP.Lti13Platform.WebExample/Controllers/HomeController.cs @@ -4,52 +4,51 @@ using NP.Lti13Platform.Core.Services; using NP.Lti13Platform.DeepLinking; -namespace NP.Lti13Platform.WebExample.Controllers +namespace NP.Lti13Platform.WebExample.Controllers; + +public class HomeController(ILogger logger, IUrlServiceHelper service, ILti13CoreDataService dataService) : Controller { - public class HomeController(ILogger logger, IUrlServiceHelper service, ILti13CoreDataService dataService) : Controller + public async Task Index(CancellationToken cancellationToken) { - public async Task Index(CancellationToken cancellationToken) - { - var tool = await dataService.GetToolAsync("clientId", cancellationToken); - var deployment = await dataService.GetDeploymentAsync("deploymentId", cancellationToken); - var context = await dataService.GetContextAsync("contextId", cancellationToken); - var userId = "userId"; - var documentTarget = Lti13PresentationTargetDocuments.Window; - var height = 200; - var width = 250; - var locale = "en-US"; + var tool = await dataService.GetToolAsync("clientId", cancellationToken); + var deployment = await dataService.GetDeploymentAsync("deploymentId", cancellationToken); + var context = await dataService.GetContextAsync("contextId", cancellationToken); + var userId = "userId"; + var documentTarget = Lti13PresentationTargetDocuments.Window; + var height = 200; + var width = 250; + var locale = "en-US"; - logger.LogInformation("LOGGING INFORMATION"); + logger.LogInformation("LOGGING INFORMATION"); - return Results.Ok(new - { - deepLinkUrl = await service.GetDeepLinkInitiationUrlAsync( + return Results.Ok(new + { + deepLinkUrl = await service.GetDeepLinkInitiationUrlAsync( + tool!, + deployment!.Id, + userId, + false, + null, + context!.Id, + new DeepLinkSettingsOverride { Title = "TiTlE", Text = "TEXT", Data = "data" }, + cancellationToken: cancellationToken), + resourceLinkUrls = DataService.ResourceLinks + .Select(async resourceLink => await service.GetResourceLinkInitiationUrlAsync( tool!, deployment!.Id, + context!.Id, + resourceLink, userId, false, - null, - context!.Id, - new DeepLinkSettingsOverride { Title = "TiTlE", Text = "TEXT", Data = "data" }, - cancellationToken: cancellationToken), - resourceLinkUrls = DataService.ResourceLinks - .Select(async resourceLink => await service.GetResourceLinkInitiationUrlAsync( - tool!, - deployment!.Id, - context!.Id, - resourceLink, - userId, - false, - launchPresentation: new LaunchPresentationOverride - { - DocumentTarget = documentTarget, - Height = height, - Width = width, - Locale = locale - }, - cancellationToken: cancellationToken)) - .Select(t => t.Result) - }); - } + launchPresentation: new LaunchPresentationOverride + { + DocumentTarget = documentTarget, + Height = height, + Width = width, + Locale = locale + }, + cancellationToken: cancellationToken)) + .Select(t => t.Result) + }); } } diff --git a/NP.Lti13Platform.WebExample/DevTunnelHttpContextAccessor.cs b/NP.Lti13Platform.WebExample/DevTunnelHttpContextAccessor.cs index 00916c1..ef64929 100644 --- a/NP.Lti13Platform.WebExample/DevTunnelHttpContextAccessor.cs +++ b/NP.Lti13Platform.WebExample/DevTunnelHttpContextAccessor.cs @@ -1,43 +1,42 @@ -namespace NP.Lti13Platform.WebExample +namespace NP.Lti13Platform.WebExample; + +// Copied from HttpContextAccessor (with VS_TUNNEL_URL modification) +public class DevTunnelHttpContextAccessor : IHttpContextAccessor { - // Copied from HttpContextAccessor (with VS_TUNNEL_URL modification) - public class DevTunnelHttpContextAccessor : IHttpContextAccessor - { - private static readonly AsyncLocal _httpContextCurrent = new(); + private static readonly AsyncLocal _httpContextCurrent = new(); - public HttpContext? HttpContext + public HttpContext? HttpContext + { + get { - get + return _httpContextCurrent.Value?.Context; + } + set + { + var holder = _httpContextCurrent.Value; + if (holder != null) { - return _httpContextCurrent.Value?.Context; + // Clear current HttpContext trapped in the AsyncLocals, as its done. + holder.Context = null; } - set + + if (value != null) { - var holder = _httpContextCurrent.Value; - if (holder != null) + var devTunnel = Environment.GetEnvironmentVariable("VS_TUNNEL_URL"); + if (!string.IsNullOrWhiteSpace(devTunnel)) { - // Clear current HttpContext trapped in the AsyncLocals, as its done. - holder.Context = null; + value.Request.Host = new HostString(new Uri(devTunnel).Host); } - if (value != null) - { - var devTunnel = Environment.GetEnvironmentVariable("VS_TUNNEL_URL"); - if (!string.IsNullOrWhiteSpace(devTunnel)) - { - value.Request.Host = new HostString(new Uri(devTunnel).Host); - } - - // Use an object indirection to hold the HttpContext in the AsyncLocal, - // so it can be cleared in all ExecutionContexts when its cleared. - _httpContextCurrent.Value = new HttpContextHolder { Context = value }; - } + // Use an object indirection to hold the HttpContext in the AsyncLocal, + // so it can be cleared in all ExecutionContexts when its cleared. + _httpContextCurrent.Value = new HttpContextHolder { Context = value }; } } + } - private sealed class HttpContextHolder - { - public HttpContext? Context; - } + private sealed class HttpContextHolder + { + public HttpContext? Context; } } diff --git a/NP.Lti13Platform.WebExample/Program.cs b/NP.Lti13Platform.WebExample/Program.cs index 13c6f2d..53b47e3 100644 --- a/NP.Lti13Platform.WebExample/Program.cs +++ b/NP.Lti13Platform.WebExample/Program.cs @@ -215,12 +215,12 @@ Task ILti13AssignmentGradeDataService.SaveGradeAsync(Grade grade, CancellationTo return Task.CompletedTask; } - Task ILti13CoreDataService.GetServiceTokenRequestAsync(string toolId, string serviceTokenId, CancellationToken cancellationToken) + Task ILti13CoreDataService.GetServiceTokenAsync(string toolId, string serviceTokenId, CancellationToken cancellationToken) { return Task.FromResult(ServiceTokens.FirstOrDefault(x => x.ToolId == toolId && x.Id == serviceTokenId)); } - Task ILti13CoreDataService.SaveServiceTokenRequestAsync(ServiceToken serviceToken, CancellationToken cancellationToken) + Task ILti13CoreDataService.SaveServiceTokenAsync(ServiceToken serviceToken, CancellationToken cancellationToken) { var existing = ServiceTokens.SingleOrDefault(x => x.ToolId == serviceToken.ToolId && x.Id == serviceToken.Id); if (existing != null) @@ -282,8 +282,6 @@ Task ILti13CoreDataService.GetPrivateKeyAsync(CancellationToken can return Task.FromResult(PartialList<(Membership, User)>.Empty); } - - Task ILti13DeepLinkingDataService.SaveContentItemAsync(string deploymentId, string? contextId, ContentItem contentItem, CancellationToken cancellationToken) { var id = Guid.NewGuid().ToString(); @@ -331,12 +329,12 @@ Task ILti13CoreDataService.GetCustomPermissions(string deploy public Task GetUserPermissionsAsync(string deploymentId, string userId, CancellationToken cancellationToken = default) { - return Task.FromResult(new UserPermissions { FamilyName = true, Name = true, GivenName = true }); + return Task.FromResult(new UserPermissions { UserId = userId, FamilyName = true, Name = true, GivenName = true }); } - public Task> GetUserPermissionsAsync(string deploymentId, IEnumerable userIds, CancellationToken cancellationToken = default) + public Task> GetUserPermissionsAsync(string deploymentId, IEnumerable userIds, CancellationToken cancellationToken = default) { - return Task.FromResult(userIds.Select(x => (x, new UserPermissions { FamilyName = true, Name = true, GivenName = true }))); + return Task.FromResult(userIds.Select(x => new UserPermissions { UserId = x, FamilyName = true, Name = true, GivenName = true })); } } } \ No newline at end of file diff --git a/NP.Lti13Platform/Startup.cs b/NP.Lti13Platform/Startup.cs index 21c8db4..e2d3231 100644 --- a/NP.Lti13Platform/Startup.cs +++ b/NP.Lti13Platform/Startup.cs @@ -9,48 +9,47 @@ using NP.Lti13Platform.NameRoleProvisioningServices; using NP.Lti13Platform.NameRoleProvisioningServices.Configs; -namespace NP.Lti13Platform +namespace NP.Lti13Platform; + +public static class Startup { - public static class Startup + public static Lti13PlatformBuilder AddLti13Platform(this IServiceCollection services) { - public static Lti13PlatformBuilder AddLti13Platform(this IServiceCollection services) - { - return services - .AddLti13PlatformCore() - .AddLti13PlatformDeepLinking() - .AddLti13PlatformNameRoleProvisioningServices() - .AddLti13PlatformAssignmentGradeServices(); - } - - public static Lti13PlatformBuilder WithLti13DataService(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) - where T : ILti13DataService - { - builder.WithLti13CoreDataService(serviceLifetime) - .WithLti13DeepLinkingDataService(serviceLifetime) - .WithLti13NameRoleProvisioningDataService(serviceLifetime) - .WithLti13AssignmentGradeDataService(serviceLifetime); - - return builder; - } + return services + .AddLti13PlatformCore() + .AddLti13PlatformDeepLinking() + .AddLti13PlatformNameRoleProvisioningServices() + .AddLti13PlatformAssignmentGradeServices(); + } - public static IEndpointRouteBuilder UseLti13Platform(this IEndpointRouteBuilder app, Func? configure = null) - { - Lti13PlatformEndpointsConfig config = new(); - config = configure?.Invoke(config) ?? config; + public static Lti13PlatformBuilder WithLti13DataService(this Lti13PlatformBuilder builder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + where T : ILti13DataService + { + builder.WithLti13CoreDataService(serviceLifetime) + .WithLti13DeepLinkingDataService(serviceLifetime) + .WithLti13NameRoleProvisioningDataService(serviceLifetime) + .WithLti13AssignmentGradeDataService(serviceLifetime); - return app - .UseLti13PlatformCore(x => config.Core) - .UseLti13PlatformDeepLinking(x => config.DeepLinking) - .UseLti13PlatformNameRoleProvisioningServices(x => config.NameRoleProvisioningServices) - .UseLti13PlatformAssignmentGradeServices(x => config.AssignmentGradeServices); - } + return builder; } - public class Lti13PlatformEndpointsConfig + public static IEndpointRouteBuilder UseLti13Platform(this IEndpointRouteBuilder app, Func? configure = null) { - public Lti13PlatformCoreEndpointsConfig Core { get; set; } = new(); - public DeepLinkingEndpointsConfig DeepLinking { get; set; } = new(); - public EndpointsConfig NameRoleProvisioningServices { get; set; } = new(); - public ServiceEndpointsConfig AssignmentGradeServices { get; set; } = new(); + Lti13PlatformEndpointsConfig config = new(); + config = configure?.Invoke(config) ?? config; + + return app + .UseLti13PlatformCore(x => config.Core) + .UseLti13PlatformDeepLinking(x => config.DeepLinking) + .UseLti13PlatformNameRoleProvisioningServices(x => config.NameRoleProvisioningServices) + .UseLti13PlatformAssignmentGradeServices(x => config.AssignmentGradeServices); } +} + +public class Lti13PlatformEndpointsConfig +{ + public Lti13PlatformCoreEndpointsConfig Core { get; set; } = new(); + public DeepLinkingEndpointsConfig DeepLinking { get; set; } = new(); + public EndpointsConfig NameRoleProvisioningServices { get; set; } = new(); + public ServiceEndpointsConfig AssignmentGradeServices { get; set; } = new(); } \ No newline at end of file