atomic_lti 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|