Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Changes for lms #14

Merged
merged 4 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Models/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class Platform

public string? Name { get; set; }

public string? Url { get; set; }
public Uri? Url { get; set; }

public string? ProductFamilyCode { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Models/ResourceLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class ResourceLink

public required string ContextId { get; set; }

public string? Url { get; set; }
public Uri? Url { get; set; }

public string? Title { get; set; }

Expand Down
8 changes: 4 additions & 4 deletions NP.Lti13Platform.Core/Models/Tool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ public class Tool

public required string ClientId { get; set; }

public required string OidcInitiationUrl { get; set; }
public required Uri OidcInitiationUrl { get; set; }

public required string DeepLinkUrl { get; set; }
public required Uri DeepLinkUrl { get; set; }

public required string LaunchUrl { get; set; }
public required Uri LaunchUrl { get; set; }

public IEnumerable<string> RedirectUrls => new[] { DeepLinkUrl, LaunchUrl }.Where(x => x != null).Select(x => x!);
public IEnumerable<Uri> RedirectUrls => [DeepLinkUrl, LaunchUrl];

public Jwks? Jwks { get; set; }

Expand Down
101 changes: 92 additions & 9 deletions NP.Lti13Platform.Core/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,64 +4,147 @@ public class User
{
public required string Id { get; set; }

/// <summary>
/// Full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the user's locale and preferences.
/// </summary>
public string? Name { get; set; }

/// <summary>
/// Given name(s) or first name(s). Note that in some cultures, people can have multiple given names; all can be present, with the names being separated by space characters.
/// </summary>
public string? GivenName { get; set; }

/// <summary>
/// Surname(s) or last name(s). Note that in some cultures, people can have multiple family names or no family name; all can be present, with the names being separated by space characters.
/// </summary>
public string? FamilyName { get; set; }

/// <summary>
/// Middle name(s). Note that in some cultures, people can have multiple middle names; all can be present, with the names being separated by space characters. Also note that in some cultures, middle names are not used.
/// </summary>
public string? MiddleName { get; set; }

/// <summary>
/// Casual name that may or may not be the same as the given_name. For instance, a nickname value of Mike might be returned alongside a given_name value of Michael.
/// </summary>
public string? Nickname { get; set; }

/// <summary>
/// Shorthand name by which the user wishes to be referred to, such as janedoe or j.doe. This value MAY be any valid JSON string including special characters such as @, /, or whitespace. MUST NOT rely upon this value being unique.
/// </summary>
public string? PreferredUsername { get; set; }

public string? Profile { get; set; }

public string? Picture { get; set; }

public string? Website { get; set; }

/// <summary>
/// URL of the profile page. The contents of this Web page SHOULD be about the user.
/// </summary>
public Uri? Profile { get; set; }

/// <summary>
/// URL of the profile picture. This URL MUST refer to an image file (for example, a PNG, JPEG, or GIF image file), rather than to a Web page containing an image. Note that this URL SHOULD specifically reference a profile photo of the user suitable for displaying, rather than an arbitrary photo taken by the user.
/// </summary>
public Uri? Picture { get; set; }

/// <summary>
/// URL of the user's Web page or blog. This Web page SHOULD contain information published by the user or an organization that the user is affiliated with.
/// </summary>
public Uri? Website { get; set; }

/// <summary>
/// Preferred e-mail address. Its value MUST conform to the <see href="https://www.rfc-editor.org/rfc/rfc5322.txt">RFC 5322</see> addr-spec syntax. MUST NOT rely upon this value being unique.
/// </summary>
///
public string? Email { get; set; }

/// <summary>
/// True if the user's e-mail address has been verified; otherwise false. When this value is true, this means that affirmative steps were taken to ensure that this e-mail address was controlled by the user at the time the verification was performed.
/// </summary>
public bool? EmailVerified { get; set; }

/// <summary>
/// Values defined by this specification are female and male. Other values MAY be used when neither of the defined values are applicable.
/// </summary>
public string? Gender { get; set; }

/// <summary>
/// User's birthday, The year MAY be 0000, indicating that it is omitted.
/// </summary>
public DateOnly? Birthdate { get; set; }

/// <summary>
/// String from IANA Time Zone Database representing the user's time zone. For example, Europe/Paris or America/Los_Angeles.
/// </summary>
public string? TimeZone { get; set; }

/// <summary>
/// User's locale, represented as a <see href="https://www.rfc-editor.org/rfc/rfc5646.txt">BCP47</see> language tag. This is typically a language code in lowercase and an country code in uppercase, separated by a dash. For example, en-US or fr-CA. As a compatibility note, some implementations have used an underscore as the separator rather than a dash, for example, en_US; MAY choose to accept this locale syntax as well.
/// </summary>
public string? Locale { get; set; }

/// <summary>
/// User's preferred telephone number. <see href="https://www.itu.int/rec/T-REC-E.164-201011-I/en">E.164</see> is RECOMMENDED as the format, for example, +1 (425) 555-1212 or +56 (2) 687 2400. If the phone number contains an extension, it is RECOMMENDED that the extension be represented using the <see href="https://www.rfc-editor.org/rfc/rfc3966.txt">RFC 3966</see> extension syntax, for example, +1 (604) 555-1234;ext=5678.
/// </summary>
public string? PhoneNumber { get; set; }

/// <summary>
/// True if the user's phone number has been verified; otherwise false. When this is true, this means that the affirmative steps were taken to ensure that this phone number was controlled by the user at the time the verification was performed. When true, the phone_number Claim MUST be in E.164 format and any extensions MUST be represented in RFC 3966 format.
/// </summary>
public bool? PhoneNumberVerified { get; set; }

/// <summary>
/// Time the user's information was last updated.
/// </summary>
public DateTime? UpdatedAt { get; set; }

/// <summary>
/// User's preferred postal address.
/// </summary>
public Address? Address { get; set; }

public string? ImageUrl { get; set; }

/// <summary>
/// Username (typically, the name a user logs in with).
/// </summary>
public string? Username { get; set; }

public IEnumerable<string> Orgs { get; set; } = [];
/// <summary>
/// One or more URIs describing the user's organizational properties (for example, an ldap:// URI).
/// </summary>
public IEnumerable<Uri> Orgs { get; set; } = [];

/// <summary>
/// A list of grade(s) for which the user is enrolled. The permitted vocabulary is from the grades field utilized in <see href="https://ceds.ed.gov/CEDSElementDetails.aspx?TermId=7100">OneRoster Users</see>.
/// </summary>
public IEnumerable<string> OneRosterGrades { get; set; } = [];
}

public class Address
{
/// <summary>
/// Full mailing address, formatted for display or use on a mailing label. This field MAY contain multiple lines, separated by newlines. Newlines can be represented either as a carriage return/line feed pair ("\r\n") or as a single line feed character ("\n").
/// </summary>
public string? Formatted { get; set; }

/// <summary>
/// Full street address component, which MAY include house number, street name, Post Office Box, and multi-line extended street address information. This field MAY contain multiple lines, separated by newlines. Newlines can be represented either as a carriage return/line feed pair ("\r\n") or as a single line feed character ("\n").
/// </summary>
public string? StreetAddress { get; set; }

/// <summary>
/// City or locality component.
/// </summary>
public string? Locality { get; set; }

/// <summary>
/// State, province, prefecture, or region component.
/// </summary>
public string? Region { get; set; }

/// <summary>
/// Zip code or postal code component.
/// </summary>
public string? PostalCode { get; set; }

/// <summary>
/// Country name component.
/// </summary>
public string? Country { get; set; }
}
6 changes: 3 additions & 3 deletions NP.Lti13Platform.Core/Populators/CustomPopulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope,
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.Image when customPermissions.UserImage && !scope.UserScope.IsAnonymous => scope.UserScope.User.Picture?.ToString(),
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.Image when customPermissions.ActualUserImage && !scope.UserScope.IsAnonymous => scope.UserScope.ActualUser?.Picture?.ToString(),
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),
Expand Down Expand Up @@ -123,7 +123,7 @@ public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope,
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.InstanceUrl when customPermissions.ToolPlatformProductInstanceUrl => platform?.Url?.ToString(),
Lti13ToolPlatformVariables.InstanceContactEmail when customPermissions.ToolPlatformProductInstanceContactEmail => platform?.ContactEmail,
_ => kvp.Value
} ?? string.Empty;
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Populators/PlatformPopulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public override async Task PopulateAsync(IPlatformMessage obj, MessageScope scop
Description = platform.Description,
Name = platform.Name,
ProductFamilyCode = platform.ProductFamilyCode,
Url = platform.Url,
Url = platform.Url?.ToString(),
Version = platform.Version,
};
}
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/Populators/ResourceLinkPopulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public override async Task PopulateAsync(IResourceLinkMessage obj, MessageScope
obj.LtiVersion = "1.3.0";
obj.DeploymentId = scope.Deployment.Id;

obj.TargetLinkUri = scope.ResourceLink.Url ?? scope.Tool.LaunchUrl;
obj.TargetLinkUri = (scope.ResourceLink.Url ?? scope.Tool.LaunchUrl).ToString();
obj.ResourceLink = new IResourceLinkMessage.ResourceLinkMessage
{
Id = scope.ResourceLink.Id,
Expand Down
2 changes: 1 addition & 1 deletion NP.Lti13Platform.Core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ The expiration time of the access tokens handed out to the tools.

The LTI specs allow for messages to be extended with custom data. This is handled by adding `Populators` in the setup of the platform. To extend the message, create an interface with the properties that will be used to extend the message, and create a `Populator<T>` to fill those properties when the request for that message is generated.

Multiple populators can be added to the same interface. Multiple interfaces can be added to the same message type. The populator interface properties support the System.Text.Json attributes for serialization.
Multiple populators can be added to the same interface. Multiple interfaces can be added to the same message type. The populator interface properties support the System.Text.Json attributes for serialization. Populators must be thread safe or have a Transient Dependency Injection strategy.

```csharp
interface ICustomMessage
Expand Down
8 changes: 4 additions & 4 deletions NP.Lti13Platform.Core/Services/UrlServiceHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace NP.Lti13Platform.Core.Services;
public interface IUrlServiceHelper
{
Task<Uri> GetResourceLinkInitiationUrlAsync(Tool tool, string deploymentId, string contextId, ResourceLink resourceLink, string userId, bool isAnonymous, string? actualUserId = null, LaunchPresentationOverride? launchPresentation = null, CancellationToken cancellationToken = default);
Task<Uri> 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<Uri> GetUrlAsync(string messageType, Tool tool, string deploymentId, Uri targetLinkUri, string userId, bool isAnonymous, string? actualUserId = null, string? contextId = null, string? resourceLinkId = null, string? messageHint = null, CancellationToken cancellationToken = default);

Task<string> GetLoginHintAsync(string userId, string? actualUserId, bool isAnonymous, CancellationToken cancellationToken = default);
Task<(string UserId, string? ActualUserId, bool IsAnonymous)> ParseLoginHintAsync(string loginHint, CancellationToken cancellationToken = default);
Expand All @@ -25,7 +25,7 @@ public async Task<Uri> GetResourceLinkInitiationUrlAsync(Tool tool, string deplo
Lti13MessageType.LtiResourceLinkRequest,
tool,
deploymentId,
string.IsNullOrWhiteSpace(resourceLink.Url) ? tool.LaunchUrl : resourceLink.Url,
resourceLink.Url ?? tool.LaunchUrl,
userId,
isAnonymous,
actualUserId,
Expand All @@ -38,7 +38,7 @@ public async Task<Uri> GetUrlAsync(
string messageType,
Tool tool,
string deploymentId,
string targetLinkUri,
Uri targetLinkUri,
string userId,
bool isAnonymous,
string? actualUserId = null,
Expand All @@ -52,7 +52,7 @@ public async Task<Uri> GetUrlAsync(
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("target_link_uri", targetLinkUri.ToString());
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);
Expand Down
10 changes: 5 additions & 5 deletions NP.Lti13Platform.Core/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,11 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil
ltiMessage.Nickname = userPermissions.Nickname ? user.Nickname : null;
ltiMessage.PhoneNumber = userPermissions.PhoneNumber ? user.PhoneNumber : null;
ltiMessage.PhoneNumberVerified = userPermissions.PhoneNumberVerified ? user.PhoneNumberVerified : null;
ltiMessage.Picture = userPermissions.Picture ? user.Picture : null;
ltiMessage.Picture = userPermissions.Picture ? user.Picture?.ToString() : null;
ltiMessage.PreferredUsername = userPermissions.PreferredUsername ? user.PreferredUsername : null;
ltiMessage.Profile = userPermissions.Profile ? user.Profile : null;
ltiMessage.Profile = userPermissions.Profile ? user.Profile?.ToString() : null;
ltiMessage.UpdatedAt = userPermissions.UpdatedAt ? user.UpdatedAt : null;
ltiMessage.Website = userPermissions.Website ? user.Website : null;
ltiMessage.Website = userPermissions.Website ? user.Website?.ToString() : null;
ltiMessage.TimeZone = userPermissions.TimeZone ? user.TimeZone : null;
}

Expand All @@ -287,7 +287,7 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil
messageHintString);

var services = serviceProvider.GetKeyedServices<Populator>(messageTypeString);
foreach (var service in services) // TODO: await in list
foreach (var service in services)
{
await service.PopulateAsync(ltiMessage, scope, cancellationToken);
}
Expand Down Expand Up @@ -429,7 +429,7 @@ public static IEndpointRouteBuilder UseLti13PlatformCore(this IEndpointRouteBuil
}
}

internal record AuthenticationRequest(string? Scope, string? Response_Type, string? Response_Mode, string? Prompt, string? Nonce, string? State, string? Client_Id, string? Redirect_Uri, string? Login_Hint, string? Lti_Message_Hint);
internal record AuthenticationRequest(string? Scope, string? Response_Type, string? Response_Mode, string? Prompt, string? Nonce, string? State, string? Client_Id, Uri? Redirect_Uri, string? Login_Hint, string? Lti_Message_Hint);

internal record TokenRequest(string Grant_Type, string Client_Assertion_Type, string Client_Assertion, string Scope);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public override async Task PopulateAsync(ICustomMessage obj, MessageScope scope,
var value = kvp.Value switch
{
Lti13UserVariables.Id when customPermissions.UserId => scope.UserScope.User.Id,
Lti13UserVariables.Image when customPermissions.UserImage => scope.UserScope.User.ImageUrl,
Lti13UserVariables.Image when customPermissions.UserImage => scope.UserScope.User.Picture?.ToString(),
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),
Expand Down
Loading
Loading