atomic_lti 1.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +22 -0
- data/Rakefile +13 -0
- data/app/assets/config/atomic_lti_manifest.js +1 -0
- data/app/assets/stylesheets/atomic_lti/application.css +0 -0
- data/app/assets/stylesheets/atomic_lti/jwks.css +4 -0
- data/app/controllers/atomic_lti/jwks_controller.rb +17 -0
- data/app/helpers/atomic_lti/launch_helper.rb +4 -0
- data/app/jobs/atomic_lti/application_job.rb +4 -0
- data/app/lib/atomic_lti/auth_token.rb +35 -0
- data/app/lib/atomic_lti/authorization.rb +152 -0
- data/app/lib/atomic_lti/config.rb +213 -0
- data/app/lib/atomic_lti/deep_linking.rb +36 -0
- data/app/lib/atomic_lti/definitions.rb +169 -0
- data/app/lib/atomic_lti/exceptions.rb +87 -0
- data/app/lib/atomic_lti/lti.rb +94 -0
- data/app/lib/atomic_lti/open_id.rb +22 -0
- data/app/lib/atomic_lti/params.rb +135 -0
- data/app/lib/atomic_lti/services/base.rb +38 -0
- data/app/lib/atomic_lti/services/line_items.rb +90 -0
- data/app/lib/atomic_lti/services/names_and_roles.rb +74 -0
- data/app/lib/atomic_lti/services/results.rb +18 -0
- data/app/lib/atomic_lti/services/score.rb +69 -0
- data/app/lib/atomic_lti/services/score_canvas.rb +47 -0
- data/app/mailers/atomic_lti/application_mailer.rb +6 -0
- data/app/models/atomic_lti/application_record.rb +5 -0
- data/app/models/atomic_lti/context.rb +10 -0
- data/app/models/atomic_lti/deployment.rb +13 -0
- data/app/models/atomic_lti/install.rb +11 -0
- data/app/models/atomic_lti/jwk.rb +41 -0
- data/app/models/atomic_lti/oauth_state.rb +5 -0
- data/app/models/atomic_lti/open_id_state.rb +5 -0
- data/app/models/atomic_lti/platform.rb +5 -0
- data/app/models/atomic_lti/platform_instance.rb +8 -0
- data/app/views/atomic_lti/launches/index.html.erb +11 -0
- data/app/views/atomic_lti/shared/redirect.html.erb +15 -0
- data/app/views/layouts/atomic_lti/application.html.erb +14 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20220428175127_create_atomic_lti_platforms.rb +12 -0
- data/db/migrate/20220428175128_create_atomic_lti_platform_instances.rb +15 -0
- data/db/migrate/20220428175247_create_atomic_lti_installs.rb +11 -0
- data/db/migrate/20220428175305_create_atomic_lti_deployments.rb +13 -0
- data/db/migrate/20220428175336_create_atomic_lti_contexts.rb +15 -0
- data/db/migrate/20220428175423_create_atomic_lti_oauth_states.rb +10 -0
- data/db/migrate/20220503003528_create_atomic_lti_jwks.rb +12 -0
- data/db/migrate/20221010140920_create_open_id_state.rb +9 -0
- data/db/seeds.rb +29 -0
- data/lib/atomic_lti/engine.rb +9 -0
- data/lib/atomic_lti/error_handling_middleware.rb +33 -0
- data/lib/atomic_lti/open_id_middleware.rb +270 -0
- data/lib/atomic_lti/version.rb +3 -0
- data/lib/atomic_lti.rb +27 -0
- data/lib/tasks/atomic_lti_tasks.rake +4 -0
- metadata +129 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
module Services
|
3
|
+
class NamesAndRoles < AtomicLti::Services::Base
|
4
|
+
|
5
|
+
def initialize(lti_token:)
|
6
|
+
super(lti_token: lti_token)
|
7
|
+
end
|
8
|
+
|
9
|
+
def endpoint
|
10
|
+
url = @lti_token.dig(AtomicLti::Definitions::NAMES_AND_ROLES_CLAIM, "context_memberships_url")
|
11
|
+
raise AtomicLti::Exceptions::NamesAndRolesError, "Unable to access names and roles" unless url.present?
|
12
|
+
|
13
|
+
url
|
14
|
+
end
|
15
|
+
|
16
|
+
def url_for(query = nil)
|
17
|
+
url = endpoint.dup
|
18
|
+
url << "?#{query}" if query.present?
|
19
|
+
url
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.enabled?(lti_token)
|
23
|
+
return false unless lti_token&.dig(AtomicLti::Definitions::NAMES_AND_ROLES_CLAIM)
|
24
|
+
|
25
|
+
(AtomicLti::Definitions::NAMES_AND_ROLES_SERVICE_VERSIONS &
|
26
|
+
(lti_token.dig(AtomicLti::Definitions::NAMES_AND_ROLES_CLAIM, "service_versions") || [])).present?
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid?
|
30
|
+
self.class.enabled?(@lti_token)
|
31
|
+
end
|
32
|
+
|
33
|
+
# List names and roles
|
34
|
+
# limit query param - see 'Limit query parameter' section of NRPS spec
|
35
|
+
# to get differences - see 'Membership differences' section of NRPS spec
|
36
|
+
# query parameter of '{"role" => "Learner"}'
|
37
|
+
# will filter the memberships to just those which have a Learner role.
|
38
|
+
# query parameter of '{"rlid" => "49566-rkk96"}' will filter the memberships to just those which
|
39
|
+
# have access to the resource link with ID '49566-rkk96'
|
40
|
+
def list(query: {}, page_url: nil)
|
41
|
+
url = if page_url.present?
|
42
|
+
page_url
|
43
|
+
else
|
44
|
+
uri = Addressable::URI.parse(endpoint)
|
45
|
+
uri.query_values = (uri.query_values || {}).merge(query)
|
46
|
+
uri
|
47
|
+
end
|
48
|
+
verify_received_user_names(
|
49
|
+
HTTParty.get(
|
50
|
+
url,
|
51
|
+
headers: headers(
|
52
|
+
{
|
53
|
+
"Accept" => "application/vnd.ims.lti-nrps.v2.membershipcontainer+json",
|
54
|
+
},
|
55
|
+
),
|
56
|
+
),
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def verify_received_user_names(names_and_roles_memberships)
|
61
|
+
if names_and_roles_memberships&.body.present?
|
62
|
+
members = JSON.parse(names_and_roles_memberships.body)["members"]
|
63
|
+
if members.present? && members.all? { |member| member["name"].nil? }
|
64
|
+
raise(
|
65
|
+
AtomicLti::Exceptions::NamesAndRolesError,
|
66
|
+
"Unable to fetch user data. Your LTI key may be set to private.",
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
names_and_roles_memberships
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
module Services
|
3
|
+
# Canvas API docs: https://canvas.instructure.com/doc/api/result.html
|
4
|
+
class Results < AtomicLti::Services::Base
|
5
|
+
|
6
|
+
def list(line_item_id)
|
7
|
+
url = "#{line_item_id}/results"
|
8
|
+
HTTParty.get(url, headers: headers)
|
9
|
+
end
|
10
|
+
|
11
|
+
def show(line_item_id, result_id)
|
12
|
+
url = "#{line_item_id}/results/#{result_id}"
|
13
|
+
HTTParty.get(url, headers: headers)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
module Services
|
3
|
+
# Canvas docs: https://canvas.instructure.com/doc/api/score.html
|
4
|
+
class Score < AtomicLti::Services::Base
|
5
|
+
|
6
|
+
attr_accessor :id
|
7
|
+
|
8
|
+
def initialize(lti_token: nil, iss:nil, deployment_id: nil, id: nil)
|
9
|
+
super(lti_token: lti_token, iss: iss, deployment_id: deployment_id)
|
10
|
+
@id = id
|
11
|
+
end
|
12
|
+
|
13
|
+
def endpoint
|
14
|
+
if id.blank?
|
15
|
+
raise ::AtomicLti::Exceptions::ScoreError,
|
16
|
+
"Invalid id or no id provided. Unable to access scores. id should be in the form of a url."
|
17
|
+
end
|
18
|
+
uri = URI(id)
|
19
|
+
uri.path = uri.path+'/scores'
|
20
|
+
uri
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate(
|
24
|
+
user_id:,
|
25
|
+
score:,
|
26
|
+
max_score:,
|
27
|
+
comment: nil,
|
28
|
+
timestamp: Time.now,
|
29
|
+
activity_progress: "Completed",
|
30
|
+
grading_progress: "FullyGraded"
|
31
|
+
)
|
32
|
+
{
|
33
|
+
# The lti_user_id or the Canvas user_id
|
34
|
+
userId: user_id,
|
35
|
+
# The Current score received in the tool for this line item and user, scaled to
|
36
|
+
# the scoreMaximum
|
37
|
+
scoreGiven: score,
|
38
|
+
# Maximum possible score for this result; it must be present if scoreGiven is
|
39
|
+
# present.
|
40
|
+
scoreMaximum: max_score,
|
41
|
+
# Comment visible to the student about this score.
|
42
|
+
comment: comment,
|
43
|
+
# Date and time when the score was modified in the tool. Should use subsecond
|
44
|
+
# precision.
|
45
|
+
timestamp: timestamp.iso8601(3),
|
46
|
+
# Indicate to Canvas the status of the user towards the activity's completion.
|
47
|
+
# Must be one of Initialized, Started, InProgress, Submitted, Completed
|
48
|
+
activityProgress: activity_progress,
|
49
|
+
# Indicate to Canvas the status of the grading process. A value of
|
50
|
+
# PendingManual will require intervention by a grader. Values of NotReady,
|
51
|
+
# Failed, and Pending will cause the scoreGiven to be ignored. FullyGraded
|
52
|
+
# values will require no action. Possible values are NotReady, Failed, Pending,
|
53
|
+
# PendingManual, FullyGraded
|
54
|
+
gradingProgress: grading_progress,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def send(attrs)
|
59
|
+
content_type = { "Content-Type" => "application/vnd.ims.lis.v1.score+json" }
|
60
|
+
HTTParty.post(
|
61
|
+
endpoint,
|
62
|
+
body: attrs.to_json,
|
63
|
+
headers: headers(content_type),
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
module Services
|
3
|
+
# Canvas docs: https://canvas.instructure.com/doc/api/score.html
|
4
|
+
class ScoreCanvas < Score
|
5
|
+
|
6
|
+
def generate(
|
7
|
+
new_submission: true,
|
8
|
+
submission_type: nil,
|
9
|
+
submission_data: nil,
|
10
|
+
submitted_at: nil,
|
11
|
+
content_items: nil,
|
12
|
+
**standard_attrs
|
13
|
+
)
|
14
|
+
submission_data = {
|
15
|
+
# (EXTENSION field) flag to indicate that this is a new submission.
|
16
|
+
# Defaults to true unless submission_type is none.
|
17
|
+
new_submission: new_submission,
|
18
|
+
|
19
|
+
# (EXTENSION field) permissible values are: none, basic_lti_launch,
|
20
|
+
# online_text_entry, external_tool, online_upload, or online_url.
|
21
|
+
# Defaults to external_tool. Ignored if content_items are provided.
|
22
|
+
submission_type: submission_type,
|
23
|
+
|
24
|
+
# (EXTENSION field) submission data (URL or body text). Only used
|
25
|
+
# for submission_types basic_lti_launch, online_text_entry, online_url.
|
26
|
+
# Ignored if content_items are provided.
|
27
|
+
submission_data: submission_data,
|
28
|
+
|
29
|
+
# (EXTENSION field) Date and time that the submission was originally created.
|
30
|
+
# Should use ISO8601-formatted date with subsecond precision. This should match
|
31
|
+
# the data and time that the original submission happened in Canvas.
|
32
|
+
submitted_at: submitted_at,
|
33
|
+
|
34
|
+
# (EXTENSION field) Files that should be included with the submission. Each item
|
35
|
+
# should contain `type: file`, and a url pointing to the file. It can also contain
|
36
|
+
# a title, and an explicit MIME type if needed (otherwise, MIME type will be
|
37
|
+
# inferred from the title or url). If any items are present, submission_type
|
38
|
+
# will be online_upload.
|
39
|
+
content_items: content_items,
|
40
|
+
}.compact
|
41
|
+
|
42
|
+
super(**standard_attrs).
|
43
|
+
merge({ "https://canvas.instructure.com/lti/submission": submission_data })
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
class Context < ApplicationRecord
|
3
|
+
belongs_to :platform, primary_key: :iss, foreign_key: :iss
|
4
|
+
# belongs_to :deployment, primary_key: [:iss, :deployment_id], foreign_key: [:iss, :deployment_id] # TODO: this breaks rspec
|
5
|
+
|
6
|
+
validates :context_id, presence: true
|
7
|
+
validates :deployment_id, presence: true
|
8
|
+
validates :iss, presence: true
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
class Deployment < ApplicationRecord
|
3
|
+
belongs_to :platform, primary_key: :iss, foreign_key: :iss
|
4
|
+
belongs_to :install, primary_key: [:iss, :client_id], foreign_key: [:iss, :client_id], optional: true
|
5
|
+
|
6
|
+
# we won't have platform_guid during dynamic registration
|
7
|
+
# belongs_to :platform_instance, primary_key: :guid, foreign_key: :platform_guid, optional: true
|
8
|
+
|
9
|
+
validates :deployment_id, presence: true
|
10
|
+
validates :iss, presence: true
|
11
|
+
# validates :platform_guid#, presence: true
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
class Install < ApplicationRecord
|
3
|
+
belongs_to :platform, primary_key: :iss, foreign_key: :iss
|
4
|
+
|
5
|
+
validates :client_id, presence: true
|
6
|
+
validates :iss, presence: true
|
7
|
+
def deployments
|
8
|
+
AtomicLti::Deployment.where("iss = ? AND client_id = ?", iss, client_id)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
class Jwk < ApplicationRecord
|
3
|
+
before_create :generate_keys
|
4
|
+
|
5
|
+
def generate_keys
|
6
|
+
pkey = OpenSSL::PKey::RSA.generate(2048)
|
7
|
+
self.pem = pkey.to_pem
|
8
|
+
self.kid = pkey.to_jwk.thumbprint
|
9
|
+
end
|
10
|
+
|
11
|
+
def alg
|
12
|
+
"RS256"
|
13
|
+
end
|
14
|
+
|
15
|
+
def private_key
|
16
|
+
OpenSSL::PKey::RSA.new(pem)
|
17
|
+
end
|
18
|
+
|
19
|
+
def public_key
|
20
|
+
pkey = OpenSSL::PKey::RSA.new(pem)
|
21
|
+
pkey.public_key
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_json
|
25
|
+
pkey = OpenSSL::PKey::RSA.new(pem)
|
26
|
+
json = JSON::JWK.new(pkey.public_key, kid: kid).as_json
|
27
|
+
json["use"] = "sig"
|
28
|
+
json["alg"] = alg
|
29
|
+
json
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_pem
|
33
|
+
pkey = OpenSSL::PKey::RSA.new(pem)
|
34
|
+
pkey.public_key.to_pem
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.current_jwk
|
38
|
+
self.last
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<script type="text/javascript">
|
5
|
+
window.onload=function(){document.forms[0].submit();};
|
6
|
+
</script>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<form action="<%= @launch_url -%>" method="POST">
|
10
|
+
<% @launch_params.each do |name, value| -%>
|
11
|
+
<%= hidden_field_tag(name, value) %>
|
12
|
+
<% end -%>
|
13
|
+
</form>
|
14
|
+
</body>
|
15
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateAtomicLtiPlatforms < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_platforms do |t|
|
4
|
+
t.string :iss, null: false
|
5
|
+
t.string :jwks_url, null: false
|
6
|
+
t.string :token_url, null: false
|
7
|
+
t.string :oidc_url, null: false
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
add_index :atomic_lti_platforms, :iss, unique: true
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateAtomicLtiPlatformInstances < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_platform_instances do |t|
|
4
|
+
t.string :iss, null: false
|
5
|
+
t.string :guid, null: false
|
6
|
+
t.string :name
|
7
|
+
t.string :version
|
8
|
+
t.string :product_family_code
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
add_index :atomic_lti_platform_instances, [:guid, :iss],
|
13
|
+
unique: true, name: "index_atomic_lti_platform_instances_guid_iss"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateAtomicLtiInstalls < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_installs do |t|
|
4
|
+
t.string :iss, null: false
|
5
|
+
t.string :client_id, null: false
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
add_index :atomic_lti_installs, [:client_id, :iss],
|
9
|
+
unique: true, name: "index_atomic_lti_installs_c_id_guid"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateAtomicLtiDeployments < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_deployments do |t|
|
4
|
+
t.string :deployment_id, null: false
|
5
|
+
t.string :client_id, null: false
|
6
|
+
t.string :platform_guid
|
7
|
+
t.string :iss, null: false
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
add_index :atomic_lti_deployments, [:deployment_id, :iss],
|
11
|
+
unique: true, name: "index_atomic_lti_deployments_d_id_iss"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateAtomicLtiContexts < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_contexts do |t|
|
4
|
+
t.string :context_id, null: false
|
5
|
+
t.string :deployment_id, null: false
|
6
|
+
t.string :iss, null: false
|
7
|
+
t.string :label
|
8
|
+
t.string :title
|
9
|
+
t.string :types, array: true
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
add_index :atomic_lti_contexts, [:context_id, :deployment_id, :iss],
|
13
|
+
unique: true, name: "index_atomic_lti_contexts_c_id_d_id_iss"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class CreateAtomicLtiOauthStates < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_oauth_states do |t|
|
4
|
+
t.string :state, null: false
|
5
|
+
t.text :payload, null: false
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
add_index :atomic_lti_oauth_states, :state
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateAtomicLtiJwks < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :atomic_lti_jwks do |t|
|
4
|
+
t.string :kid
|
5
|
+
t.string :pem
|
6
|
+
t.string :domain
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
add_index :atomic_lti_jwks, :kid
|
10
|
+
add_index :atomic_lti_jwks, :domain
|
11
|
+
end
|
12
|
+
end
|
data/db/seeds.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Add default jwk
|
2
|
+
AtomicLti::Jwk.find_or_create_by(domain: nil)
|
3
|
+
|
4
|
+
# Add some platforms
|
5
|
+
AtomicLti::Platform.create_with(
|
6
|
+
jwks_url: "https://canvas.instructure.com/api/lti/security/jwks",
|
7
|
+
token_url: "https://canvas.instructure.com/login/oauth2/token",
|
8
|
+
oidc_url: "https://canvas.instructure.com/api/lti/authorize_redirect"
|
9
|
+
).find_or_create_by(iss: "https://canvas.instructure.com")
|
10
|
+
|
11
|
+
AtomicLti::Platform.create_with(
|
12
|
+
jwks_url: "https://canvas-beta.instructure.com/api/lti/security/jwks",
|
13
|
+
token_url: "https://canvas-beta.instructure.com/login/oauth2/token",
|
14
|
+
oidc_url: "https://canvas-beta.instructure.com/api/lti/authorize_redirect",
|
15
|
+
).find_or_create_by(iss: "https://canvas-beta.instructure.com")
|
16
|
+
|
17
|
+
|
18
|
+
AtomicLti::Install.create_with(iss: "https://canvas.instructure.com").find_or_create_by(client_id: "43460000000000525")
|
19
|
+
|
20
|
+
AtomicTenant::PinnedPlatformGuid.create(iss: "https://canvas.instructure.com", platform_guid: "4MRcxnx6vQbFXxhLb8005m5WXFM2Z2i8lQwhJ1QT:canvas-lms", application_id: 6, application_instance_id: 5)
|
21
|
+
|
22
|
+
|
23
|
+
# => #<AtomicTenant::LtiDeployment:0x00000001294e6018
|
24
|
+
# id: 1,
|
25
|
+
# iss: "https://canvas.instructure.com",
|
26
|
+
# deployment_id: "21089:1f5e1ee417cb2b17f86a1232122452ab3f6188f7",
|
27
|
+
# application_instance_id: 5,
|
28
|
+
# created_at: Tue, 16 Aug 2022 16:05:20.848365000 UTC +00:00,
|
29
|
+
# updated_at: Tue, 16 Aug 2022 16:05:20.848365000 UTC +00:00>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AtomicLti
|
2
|
+
class ErrorHandlingMiddleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def render_error(env, status, message)
|
8
|
+
format = "text/plain"
|
9
|
+
body = message
|
10
|
+
|
11
|
+
render(status, body, format)
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(status, body, format)
|
15
|
+
[status,
|
16
|
+
{
|
17
|
+
"Content-Type" => "#{format}; charset=\"UTF-8\"",
|
18
|
+
"Content-Length" => body.bytesize.to_s,
|
19
|
+
},
|
20
|
+
[body]]
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
@app.call(env)
|
25
|
+
|
26
|
+
rescue AtomicLti::Exceptions::AtomicLtiNotFoundException => e
|
27
|
+
render_error(env, 404, e.message)
|
28
|
+
|
29
|
+
rescue AtomicLti::Exceptions::AtomicLtiException => e
|
30
|
+
render_error(env, 500, e.message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|