atomic_lti 1.5.7 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,5 +93,8 @@ module AtomicLti
93
93
  super(msg)
94
94
  end
95
95
  end
96
+
97
+ class PaginationLimitExceeded < AtomicLtiException
98
+ end
96
99
  end
97
100
  end
@@ -0,0 +1,36 @@
1
+ module AtomicLti
2
+ module PagingHelper
3
+ MAX_PAGES = 200
4
+
5
+ def self.response_link_urls(response, *rels)
6
+ links = response.headers["link"]&.split(",") || []
7
+ urls = {}
8
+ rels.each do |rel|
9
+ urls[rel] = link_url(links, rel)
10
+ end
11
+ urls.values_at(*rels)
12
+ end
13
+
14
+ def self.link_url(links, rel)
15
+ matching_link = links.detect { |link| link.include?("rel=\"#{rel}\"") }
16
+
17
+ return unless matching_link
18
+
19
+ matching_link.split(";")[0].gsub(/[<>\s]/, "")
20
+ end
21
+
22
+ def self.paginate_request
23
+ all = []
24
+ next_link = nil
25
+ loop do
26
+ result, next_link = yield(next_link)
27
+ all << result
28
+
29
+ break if next_link.blank? || result.blank?
30
+
31
+ raise AtomicLti::Exceptions::PaginationLimitExceeded if all.count > MAX_PAGES
32
+ end
33
+ all.compact.flatten
34
+ end
35
+ end
36
+ end
@@ -26,14 +26,57 @@ module AtomicLti
26
26
  end
27
27
 
28
28
  def get_next_url(response)
29
- link = response.headers["link"]
30
- return nil if link.blank?
29
+ next_url, = AtomicLti::PagingHelper.response_link_urls(response, "next")
30
+ next_url
31
+ end
31
32
 
32
- if url = link.split(",").detect { |l| l.split(";")[1].strip == 'rel="next"' }
33
- url.split(";")[0].gsub(/[\<\>\s]/, "")
34
- end
33
+ def service_get(*args)
34
+ logged_service_call(:get, *args)
35
+ end
36
+
37
+ def service_put(*args)
38
+ logged_service_call(:put, *args)
39
+ end
40
+
41
+ def service_post(*args)
42
+ logged_service_call(:post, *args)
43
+ end
44
+
45
+ def service_delete(*args)
46
+ logged_service_call(:delete, *args)
35
47
  end
36
48
 
49
+ def logged_service_call(method, *args)
50
+ Rails.logger.debug("Making service call #{method} #{args}")
51
+ response = HTTParty.send(method, *args)
52
+ Rails.logger.debug("Got status #{response.code} for service call #{method} #{args}")
53
+
54
+ if response.body.present? && response.success?
55
+ parsed_body = JSON.parse(response.body)
56
+ end
57
+
58
+ if !response.success? && response.code != 404
59
+ Rails.logger.error("Encountered an error while making service request #{method} #{args}")
60
+ Rails.logger.error("Got code #{response.code}")
61
+ Rails.logger.error(response.body)
62
+ end
63
+
64
+ [response, parsed_body]
65
+ rescue JSON::ParserError => e
66
+ # We do not reraise this error as previously we did not check at all for valid json. This is purely for
67
+ # logging purposes.
68
+ Rails.logger.error("Encountered an error while parsing response for service request #{method} #{args}")
69
+ Rails.logger.error(response.body)
70
+ Rails.logger.error(e)
71
+
72
+ [response, nil]
73
+ rescue StandardError => e
74
+ Rails.logger.error("Encountered an error while making service request #{method} #{args}")
75
+ Rails.logger.error(response&.body)
76
+ Rails.logger.error(e)
77
+
78
+ raise e
79
+ end
37
80
  end
38
81
  end
39
82
  end
@@ -60,16 +60,33 @@ module AtomicLti
60
60
 
61
61
  # List line items
62
62
  # Canvas: https://canvas.beta.instructure.com/doc/api/line_items.html#method.lti/ims/line_items.index
63
- def list(query = {})
63
+ def list(query = {}, page_url: nil)
64
+ url = if page_url.present?
65
+ page_url
66
+ else
67
+ uri = Addressable::URI.parse(endpoint(@id_token_decoded))
68
+ uri.query_values = (uri.query_values || {}).merge(query)
69
+ uri.to_str
70
+ end
71
+
64
72
  accept = { "Accept" => "application/vnd.ims.lis.v2.lineitemcontainer+json" }
65
- HTTParty.get(endpoint(@id_token_decoded), headers: headers(accept), query: query)
73
+ response, = service_get(url, headers: headers(accept))
74
+ response
75
+ end
76
+
77
+ def list_all(query = {})
78
+ AtomicLti::PagingHelper.paginate_request do |next_link|
79
+ result_page = list(query, page_url: next_link)
80
+ [JSON.parse(result_page.body), get_next_url(result_page)]
81
+ end
66
82
  end
67
83
 
68
84
  # Get a specific line item
69
85
  # https://canvas.beta.instructure.com/doc/api/line_items.html#method.lti/ims/line_items.show
70
86
  def show(line_item_url)
71
87
  accept = { "Accept" => "application/vnd.ims.lis.v2.lineitem+json" }
72
- HTTParty.get(line_item_url, headers: headers(accept))
88
+ response, = service_get(line_item_url, headers: headers(accept))
89
+ response
73
90
  end
74
91
 
75
92
  # Create a line item
@@ -77,18 +94,21 @@ module AtomicLti
77
94
  # Canvas: https://canvas.beta.instructure.com/doc/api/line_items.html#method.lti/ims/line_items.create
78
95
  def create(attrs = nil)
79
96
  content_type = { "Content-Type" => "application/vnd.ims.lis.v2.lineitem+json" }
80
- HTTParty.post(endpoint(@id_token_decoded), body: JSON.dump(attrs), headers: headers(content_type))
97
+ response, = service_post(endpoint(@id_token_decoded), body: JSON.dump(attrs), headers: headers(content_type))
98
+ response
81
99
  end
82
100
 
83
101
  # Update a line item
84
102
  # Canvas: https://canvas.beta.instructure.com/doc/api/line_items.html#method.lti/ims/line_items.update
85
103
  def update(line_item_url, attrs)
86
104
  content_type = { "Content-Type" => "application/vnd.ims.lis.v2.lineitem+json" }
87
- HTTParty.put(line_item_url, body: JSON.dump(attrs), headers: headers(content_type))
105
+ response, = service_put(line_item_url, body: JSON.dump(attrs), headers: headers(content_type))
106
+ response
88
107
  end
89
108
 
90
109
  def delete(line_item_url)
91
- HTTParty.delete(line_item_url, headers: headers)
110
+ response, = service_delete(line_item_url, headers: headers)
111
+ response
92
112
  end
93
113
  end
94
114
  end
@@ -49,16 +49,29 @@ module AtomicLti
49
49
  uri.query_values = (uri.query_values || {}).merge(query)
50
50
  uri.to_str
51
51
  end
52
- verify_received_user_names(
53
- HTTParty.get(
54
- url,
55
- headers: headers(
56
- {
57
- "Accept" => "application/vnd.ims.lti-nrps.v2.membershipcontainer+json",
58
- },
59
- ),
52
+ response, = service_get(
53
+ url,
54
+ headers: headers(
55
+ {
56
+ "Accept" => "application/vnd.ims.lti-nrps.v2.membershipcontainer+json",
57
+ },
60
58
  ),
61
59
  )
60
+
61
+ verify_received_user_names(response)
62
+ end
63
+
64
+ def list_all(query: {})
65
+ page_body = nil
66
+
67
+ members = AtomicLti::PagingHelper.paginate_request do |next_link|
68
+ result_page = list(query: query, page_url: next_link)
69
+ page_body = JSON.parse(result_page.body)
70
+ [page_body["members"], get_next_url(result_page)]
71
+ end
72
+
73
+ page_body["members"] = members
74
+ page_body
62
75
  end
63
76
 
64
77
  def verify_received_user_names(names_and_roles_memberships)
@@ -7,14 +7,30 @@ module AtomicLti
7
7
  [AtomicLti::Definitions::AGS_SCOPE_RESULT]
8
8
  end
9
9
 
10
- def list(line_item_id)
11
- url = "#{line_item_id}/results"
12
- HTTParty.get(url, headers: headers)
10
+ def list(line_item_id, query: {}, page_url: nil)
11
+ url = if page_url.present?
12
+ page_url
13
+ else
14
+ uri = Addressable::URI.parse("#{line_item_id}/results")
15
+ uri.query_values = (uri.query_values || {}).merge(query)
16
+ uri.to_str
17
+ end
18
+
19
+ accept = { "Accept" => "application/vnd.ims.lis.v2.resultcontainer+json" }
20
+ response, = service_get(url, headers: headers(accept))
21
+ response
22
+ end
23
+
24
+ def list_all(line_item_id, query: {})
25
+ AtomicLti::PagingHelper.paginate_request do |next_link|
26
+ result_page = list(line_item_id, query: query, page_url: next_link)
27
+ [JSON.parse(result_page.body), get_next_url(result_page)]
28
+ end
13
29
  end
14
30
 
15
- def show(line_item_id, result_id)
16
- url = "#{line_item_id}/results/#{result_id}"
17
- HTTParty.get(url, headers: headers)
31
+ def show(result_id)
32
+ response, = service_get(result_id, headers: headers)
33
+ response
18
34
  end
19
35
 
20
36
  end
@@ -190,7 +190,7 @@ module AtomicLti
190
190
  state_verified: state_verified,
191
191
  }
192
192
 
193
- if !state_verified && request.params["lti_storage_target"].present? && AtomicLti.use_post_message_storage
193
+ if !state_verified && AtomicLti.use_post_message_storage
194
194
  env["atomic.validated.state_validation"][:lti_storage_params] =
195
195
  build_lti_storage_params(request, platform)
196
196
  end
@@ -361,7 +361,7 @@ module AtomicLti
361
361
  target: request.params["lti_storage_target"],
362
362
  originSupportBroken: !AtomicLti.set_post_message_origin,
363
363
  platformOIDCUrl: platform.oidc_url,
364
- }
364
+ }.compact
365
365
  end
366
366
  end
367
367
  end
@@ -1,3 +1,3 @@
1
1
  module AtomicLti
2
- VERSION = "1.5.7".freeze
2
+ VERSION = "1.6.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atomic_lti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.7
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Petro
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-10-30 00:00:00.000000000 Z
13
+ date: 2023-12-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: pg
@@ -71,6 +71,7 @@ files:
71
71
  - app/lib/atomic_lti/exceptions.rb
72
72
  - app/lib/atomic_lti/lti.rb
73
73
  - app/lib/atomic_lti/open_id.rb
74
+ - app/lib/atomic_lti/paging_helper.rb
74
75
  - app/lib/atomic_lti/params.rb
75
76
  - app/lib/atomic_lti/role_enforcement_mode.rb
76
77
  - app/lib/atomic_lti/services/base.rb
@@ -132,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
133
  - !ruby/object:Gem::Version
133
134
  version: '0'
134
135
  requirements: []
135
- rubygems_version: 3.4.19
136
+ rubygems_version: 3.4.10
136
137
  signing_key:
137
138
  specification_version: 4
138
139
  summary: AtomicLti implements the LTI Advantage specification.