atomic_lti/
deep_linking.rs

1use self::html_fragment::HtmlFragment;
2use self::image::Image;
3use self::link::Link;
4use self::lti_resource_link::LTIResourceLink;
5use crate::deep_linking::file::File;
6use crate::errors::SecureError;
7use crate::jwt;
8use crate::{
9  lti_definitions::{DEEP_LINKING_VERSION, LTI_DEEP_LINKING_RESPONSE},
10  secure::generate_secure_string,
11};
12use chrono::{Duration, Utc};
13use openssl::rsa::Rsa;
14use serde::{Deserialize, Serialize};
15use serde_with::skip_serializing_none;
16
17pub mod file;
18pub mod html_fragment;
19pub mod image;
20pub mod link;
21pub mod lti_resource_link;
22pub mod shared;
23
24#[skip_serializing_none]
25#[derive(Debug, Deserialize, Serialize, Clone)]
26#[serde(tag = "type")]
27pub enum ContentItem {
28  #[serde(rename = "file")]
29  #[serde(alias = "file", alias = "File", alias = "FILE")]
30  File(File),
31  #[serde(rename = "html")]
32  #[serde(alias = "html", alias = "Html", alias = "HTML")]
33  HtmlFragment(HtmlFragment),
34  #[serde(rename = "image")]
35  #[serde(alias = "image", alias = "Image")]
36  Image(Image),
37  #[serde(rename = "link")]
38  #[serde(alias = "link", alias = "Link")]
39  Link(Link),
40  #[serde(rename = "ltiResourceLink")]
41  #[serde(
42    alias = "ltiResourceLink",
43    alias = "ltiresourcelink",
44    alias = "LtiResourceLink"
45  )]
46  LTIResourceLink(LTIResourceLink),
47}
48
49#[skip_serializing_none]
50#[derive(Debug, Deserialize, Serialize, Clone)]
51pub struct DeepLinkPayload {
52  pub iss: String, // client_id
53  pub aud: String, // iss from id token
54  pub azp: String, // client_id
55  pub exp: i64,
56  pub iat: i64,
57  pub nonce: String,
58  #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/message_type")]
59  pub message_type: String,
60  #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/version")]
61  pub version: String,
62  #[serde(rename = "https://purl.imsglobal.org/spec/lti/claim/deployment_id")]
63  pub deployment_id: String,
64  #[serde(rename = "https://purl.imsglobal.org/spec/lti-dl/claim/content_items")]
65  pub content_items: Vec<ContentItem>,
66  #[serde(rename = "https://purl.imsglobal.org/spec/lti-dl/claim/data")]
67  pub data: Option<String>,
68}
69
70impl DeepLinkPayload {
71  // client_id -The LTI tool's client_id as provided by the platform
72  // iss - iss from id token
73  // deployment_id - The deployment_id from the id token
74  // content_items - A list of ContentItem objects to be returned to the platform
75  // deep_link_claim_data - The https://purl.imsglobal.org/spec/lti-dl/claim/data
76  //                        value must match the value of the data property of the
77  //                        https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings
78  //                        claim from the LtiDeepLinkinkingRequest message.
79  //                        This claim is required if present in LtiDeepLinkingRequest message.
80  pub fn new(
81    client_id: &str,
82    iss: &str,
83    deployment_id: &str,
84    content_items: Vec<ContentItem>,
85    deep_link_claim_data: Option<String>,
86  ) -> Self {
87    let now = Utc::now().timestamp();
88    let nonce: String = generate_secure_string(32);
89
90    DeepLinkPayload {
91      iss: client_id.to_string(),
92      aud: iss.to_string(),
93      azp: client_id.to_string(),
94      exp: (Utc::now() + Duration::minutes(5)).timestamp(),
95      iat: now,
96      nonce,
97      message_type: LTI_DEEP_LINKING_RESPONSE.to_string(),
98      version: DEEP_LINKING_VERSION.to_string(),
99      deployment_id: deployment_id.to_string(),
100      content_items,
101      data: deep_link_claim_data,
102    }
103  }
104}
105pub struct DeepLinking;
106
107impl DeepLinking {
108  pub fn create_deep_link_jwt(
109    client_id: &str,
110    iss: &str,
111    deployment_id: &str,
112    content_items: &[ContentItem],
113    deep_link_claim_data: Option<String>,
114    kid: &str,
115    rsa_key_pair: Rsa<openssl::pkey::Private>,
116  ) -> Result<String, SecureError> {
117    let payload = DeepLinkPayload::new(
118      client_id,
119      iss,
120      deployment_id,
121      content_items.to_owned(),
122      deep_link_claim_data,
123    );
124
125    // Encode the ID Token using the private key
126    jwt::encode(&payload, kid, rsa_key_pair)
127  }
128}
129
130#[cfg(test)]
131mod tests {
132  use super::*;
133
134  #[test]
135  fn test_new_deep_link_payload() {
136    let client_id = "client_id";
137    let iss = "iss";
138    let deployment_id = "deployment_id";
139    let link = Link {
140      type_: "link".to_string(),
141      url: "https://example.com".to_string(),
142      title: Some("Example".to_string()),
143      text: Some("Example".to_string()),
144      icon: None,
145      thumbnail: None,
146      embed: None,
147      window: None,
148      iframe: None,
149    };
150    let content_items = vec![ContentItem::Link(link)];
151    let deep_link_claim_data = Some("data".to_string());
152
153    let payload = DeepLinkPayload::new(
154      client_id,
155      iss,
156      deployment_id,
157      content_items,
158      deep_link_claim_data.clone(),
159    );
160
161    assert_eq!(payload.iss, client_id);
162    assert_eq!(payload.aud, iss);
163    assert_eq!(payload.azp, client_id);
164    assert_eq!(payload.data, deep_link_claim_data);
165  }
166}