Skip to content

Commit a1decf6

Browse files
author
Donny Wong
committed
update changelog
1 parent d49caf9 commit a1decf6

File tree

4 files changed

+85
-29
lines changed

4 files changed

+85
-29
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Added new loading spinner icon for tables (#7602)
1010
- Added functionality to apply bonuses and penalties as a percentage of the student's earned marks to ExtraMark model (#7702)
1111
- Switched to consistent Font Awesome chevrons for expander icons (#7713)
12+
- Ensure only instructors and admins can link course, as LMS launch MarkUs button made available for all users (#7714)
1213

1314
### 🐛 Bug fixes
1415
- Fix name column search in graders table (#7693)

app/controllers/lti_deployments_controller.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ def redirect_login
142142
has_ta_role = lti_data[:user_roles].include?(LtiDeployment::LTI_ROLES[:ta])
143143
can_choose_course = has_privileged_role && !has_ta_role
144144
if can_choose_course
145-
# Only redirect to course picker if the course is not linked AND user has a privileged role
146145
redirect_to choose_course_lti_deployment_path(lti_deployment)
147146
else
148147
redirect_to course_not_set_up_lti_deployment_path(lti_deployment)

app/models/lti_deployment.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ class LtiDeployment < ApplicationRecord
2020
LTI_ROLES = { learner: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
2121
test_user: 'http://purl.imsglobal.org/vocab/lti/system/person#TestUser',
2222
ta: 'http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant',
23-
instructor: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor' }.freeze
24-
# Define the roles that are allowed to choose/link a course (Admin or Instructor)
23+
instructor: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
24+
admin: 'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator',
25+
sysadmin: 'http://purl.imsglobal.org/vocab/lis/v2/system/person#SysAdmin',
26+
account_admin: 'http://purl.imsglobal.org/vocab/lis/v2/system/person#AccountAdmin' }.freeze
2527
LTI_PRIVILEGED_ROLES = [LTI_ROLES[:instructor],
26-
'http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator',
27-
'http://purl.imsglobal.org/vocab/lis/v2/system/person#SysAdmin',
28-
'http://purl.imsglobal.org/vocab/lis/v2/system/person#AccountAdmin'].freeze
28+
LTI_ROLES[:admin],
29+
LTI_ROLES[:sysadmin],
30+
LTI_ROLES[:account_admin]].freeze
2931

3032
# Gets a list of all users in the LMS course associated with this deployment
3133
# with the learner role and creates roles and LTI IDs for each user.

spec/support/lti_controller_examples.rb

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@
1919
root_uri.query = launch_params.to_query
2020
root_uri.to_s
2121
end
22+
let(:mock_roles) { [LtiDeployment::LTI_ROLES[:instructor]] }
23+
24+
def create_pub_jwk
25+
@create_pub_jwk ||= JWT::JWK.new(OpenSSL::PKey::RSA.new(1024))
26+
end
27+
28+
def generate_payload(roles, nonce)
29+
{ aud: client_id,
30+
iss: host,
31+
nonce: nonce,
32+
LtiDeployment::LTI_CLAIMS[:deployment_id] => 'some_deployment_id',
33+
LtiDeployment::LTI_CLAIMS[:context] => { label: 'csc108', title: 'test' },
34+
LtiDeployment::LTI_CLAIMS[:custom] => { course_id: 1, user_id: 1 },
35+
LtiDeployment::LTI_CLAIMS[:user_id] => 'some_user_id',
36+
LtiDeployment::LTI_CLAIMS[:roles] => roles }
37+
end
38+
39+
def generate_lti_jwt(roles, nonce)
40+
payload = generate_payload(roles, nonce)
41+
pub_jwk = create_pub_jwk
42+
JWT.encode(payload, pub_jwk.keypair, 'RS256', { kid: pub_jwk.kid })
43+
end
44+
2245
describe '#launch' do
2346
context 'when launching with invalid parameters' do
2447
let(:lti_message_hint) { 'opaque string' }
@@ -98,6 +121,8 @@
98121
let(:nonce) { rand(10 ** 30).to_s.rjust(30, '0') }
99122

100123
before do
124+
stub_request(:get, jwk_url).to_return(status: 200, body: { keys: [create_pub_jwk.export] }.to_json)
125+
101126
lti_launch_data = {}
102127
lti_launch_data[:client_id] = client_id
103128
lti_launch_data[:iss] = host
@@ -129,27 +154,10 @@
129154
end
130155

131156
context 'with correct parameters' do
132-
let(:payload) do
133-
{ aud: client_id,
134-
iss: host,
135-
nonce: nonce,
136-
LtiDeployment::LTI_CLAIMS[:deployment_id] => 'some_deployment_id',
137-
LtiDeployment::LTI_CLAIMS[:context] => {
138-
label: 'csc108',
139-
title: 'test'
140-
},
141-
LtiDeployment::LTI_CLAIMS[:custom] => {
142-
course_id: 1,
143-
user_id: 1
144-
},
145-
LtiDeployment::LTI_CLAIMS[:user_id] => 'some_user_id' }
146-
end
147-
let(:pub_jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(1024)) }
148-
let(:lti_jwt) { JWT.encode(payload, pub_jwk.keypair, 'RS256', { kid: pub_jwk.kid }) }
157+
let(:lti_jwt) { generate_lti_jwt(mock_roles, nonce) }
149158

150159
before do
151160
session[:client_id] = client_id
152-
stub_request(:get, jwk_url).to_return(status: 200, body: { keys: [pub_jwk.export] }.to_json)
153161
end
154162

155163
it 'successfully decodes the jwt and redirects' do
@@ -190,6 +198,53 @@
190198
end
191199
end
192200

201+
context 'with LTI role authorization' do
202+
let(:admin_lti_uri) { LtiDeployment::LTI_ROLES[:admin] }
203+
let(:student_lti_uri) { LtiDeployment::LTI_ROLES[:learner] }
204+
let(:ta_lti_uri) { LtiDeployment::LTI_ROLES[:ta] }
205+
let(:instructor_lti_uri) { LtiDeployment::LTI_ROLES[:instructor] }
206+
207+
context 'when LTI role is Instructor' do
208+
let(:lti_jwt) { generate_lti_jwt([instructor_lti_uri], nonce) }
209+
210+
it 'redirects to course chooser' do
211+
request.headers['Referer'] = host
212+
post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
213+
expect(response).to redirect_to(choose_course_lti_deployment_path(LtiDeployment.first))
214+
end
215+
end
216+
217+
context 'when LTI role is Admin' do
218+
let(:lti_jwt) { generate_lti_jwt([admin_lti_uri], nonce) }
219+
220+
it 'redirects to course chooser' do
221+
request.headers['Referer'] = host
222+
post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
223+
expect(response).to redirect_to(choose_course_lti_deployment_path(LtiDeployment.first))
224+
end
225+
end
226+
227+
context 'when LTI role is Student' do
228+
let(:lti_jwt) { generate_lti_jwt([student_lti_uri], nonce) }
229+
230+
it 'redirects to "not set up" page' do
231+
request.headers['Referer'] = host
232+
post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
233+
expect(response).to redirect_to(course_not_set_up_lti_deployment_path(LtiDeployment.first))
234+
end
235+
end
236+
237+
context 'when LTI role is TA (even with Instructor claim)' do
238+
let(:lti_jwt) { generate_lti_jwt([ta_lti_uri, instructor_lti_uri], nonce) }
239+
240+
it 'redirects to "not set up" page' do
241+
request.headers['Referer'] = host
242+
post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
243+
expect(response).to redirect_to(course_not_set_up_lti_deployment_path(LtiDeployment.first))
244+
end
245+
end
246+
end
247+
193248
context 'get' do
194249
it 'returns an error if not logged in' do
195250
request.headers['Referer'] = host
@@ -212,7 +267,8 @@
212267
lms_course_name: 'Introduction to Computer Science',
213268
lms_course_label: 'CSC108',
214269
lms_course_id: 1,
215-
lti_user_id: 'user_id' }
270+
lti_user_id: 'user_id',
271+
user_roles: mock_roles }
216272
end
217273
let(:payload) do
218274
{ aud: client_id,
@@ -225,14 +281,12 @@
225281
LtiDeployment::LTI_CLAIMS[:custom] => {
226282
course_id: 1,
227283
user_id: 1
228-
} }
284+
},
285+
LtiDeployment::LTI_CLAIMS[:roles] => mock_roles }
229286
end
230-
let(:pub_jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(1024)) }
231-
let(:lti_jwt) { JWT.encode(payload, pub_jwk.keypair, 'RS256', { kid: pub_jwk.kid }) }
232287

233288
before do
234289
cookies.permanent.encrypted[:lti_data] = { value: JSON.generate(lti_data), expires: 5.minutes.from_now }
235-
stub_request(:get, jwk_url).to_return(status: 200, body: { keys: [pub_jwk.export] }.to_json)
236290
end
237291

238292
it 'successfully decodes the jwt and redirects' do

0 commit comments

Comments
 (0)