add new ext_roles lti param for all the roles user has

this is a list of all the roles the user has in canvas
not just the ones for the current context.

This is how it should have been all along, but we can't
make the normal roles parameter do it this way because
tools depend on our current format. So send a new one
and tell people to  migrate over as they can

Test Plan:
 * do an lti launch, ext_roles should always have "user"
   and any of learner, instructure, administrator as appropriate

refs RD-518
closes PLAT-641

Change-Id: I8cf7e96ac88e1b17528cd3d717834762cc9b575b
Reviewed-on: https://gerrit.instructure.com/40875
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Brad Humphrey <brad@instructure.com>
QA-Review: Clare Strong <clare@instructure.com>
Product-Review: Bracken Mosbacker <bracken@instructure.com>
This commit is contained in:
Bracken Mosbacker 2014-09-10 09:44:06 -06:00
parent 2f89df10cc
commit 6656a06c47
14 changed files with 145 additions and 55 deletions

View File

@ -55,6 +55,9 @@ module Lti
variable_substitutor.add_substitution('$Canvas.course.id', lti_context.id)
variable_substitutor.add_substitution('$Canvas.course.sisSourceId', lti_context.sis_source_id)
end
# temporary until lti 2 infrastructure is in place
lti_helper = Lti::SubstitutionsHelper.new(@context, @root_account, @user)
variable_substitutor.add_substitution('$Canvas.xuser.allRoles', lti_helper.all_roles)
if opts[:custom_substitutions]
opts[:custom_substitutions].each do |key, value|

View File

@ -1,13 +1,14 @@
module Lti
class LtiUserCreator
# deprecated mapping
ENROLLMENT_MAP = {
StudentEnrollment => LtiOutbound::LTIRole::LEARNER,
TeacherEnrollment => LtiOutbound::LTIRole::INSTRUCTOR,
TaEnrollment => LtiOutbound::LTIRole::TEACHING_ASSISTANT,
DesignerEnrollment => LtiOutbound::LTIRole::CONTENT_DEVELOPER,
ObserverEnrollment => LtiOutbound::LTIRole::OBSERVER,
AccountUser => LtiOutbound::LTIRole::ADMIN,
StudentViewEnrollment => LtiOutbound::LTIRole::LEARNER
StudentEnrollment => LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER,
TeacherEnrollment => LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR,
TaEnrollment => LtiOutbound::LTIRoles::ContextNotNamespaced::TEACHING_ASSISTANT,
DesignerEnrollment => LtiOutbound::LTIRoles::ContextNotNamespaced::CONTENT_DEVELOPER,
ObserverEnrollment => LtiOutbound::LTIRoles::ContextNotNamespaced::OBSERVER,
AccountUser => LtiOutbound::LTIRoles::Institution::ADMIN,
StudentViewEnrollment => LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER
}
def initialize(canvas_user, canvas_root_account, canvas_tool, canvas_context, variable_substitutor = nil)

View File

@ -1,11 +1,34 @@
module LtiOutbound
class LTIRole
# As defined at: http://www.imsglobal.org/LTI/v1p1/ltiIMGv1p1.html#_Toc319560479
module LTIRoles
module System
NONE = 'urn:lti:sysrole:ims/lis/None'
SYS_ADMIN = 'urn:lti:sysrole:ims/lis/SysAdmin'
USER = 'urn:lti:sysrole:ims/lis/User'
end
module Institution
ADMIN = 'urn:lti:instrole:ims/lis/Administrator'
INSTRUCTOR = 'urn:lti:instrole:ims/lis/Instructor'
OBSERVER = 'urn:lti:instrole:ims/lis/Observer'
STUDENT = 'urn:lti:instrole:ims/lis/Student'
end
module Context
CONTENT_DEVELOPER = 'urn:lti:role:ims/lis/ContentDeveloper'
INSTRUCTOR = 'urn:lti:role:ims/lis/Instructor'
LEARNER = 'urn:lti:role:ims/lis/Learner'
OBSERVER = 'urn:lti:role:ims/lis/Learner/NonCreditLearner'
TEACHING_ASSISTANT = 'urn:lti:role:ims/lis/TeachingAssistant'
end
# This format is deprecated, but depended on by some
module ContextNotNamespaced
CONTENT_DEVELOPER = 'ContentDeveloper'
INSTRUCTOR = 'Instructor'
LEARNER = 'Learner'
ADMIN = 'urn:lti:instrole:ims/lis/Administrator'
CONTENT_DEVELOPER = 'ContentDeveloper'
OBSERVER = 'urn:lti:instrole:ims/lis/Observer'
OBSERVER = 'urn:lti:instrole:ims/lis/Observer' # actually inst role, but left for backwards compatibility
TEACHING_ASSISTANT = 'urn:lti:role:ims/lis/TeachingAssistant'
NONE = 'urn:lti:sysrole:ims/lis/None'
end
end
end

View File

@ -9,12 +9,12 @@ module LtiOutbound
def current_role_types
roles = current_roles.join(',') if current_roles && current_roles.size > 0
roles || LtiOutbound::LTIRole::NONE
roles || LtiOutbound::LTIRoles::System::NONE
end
def concluded_role_types
roles = concluded_roles.join(',') if concluded_roles && concluded_roles.size > 0
roles || LtiOutbound::LTIRole::NONE
roles || LtiOutbound::LTIRoles::System::NONE
end
def enrollment_state
@ -26,7 +26,7 @@ module LtiOutbound
end
def learner?
current_roles.any? { |e| e == LtiOutbound::LTIRole::LEARNER }
current_roles.any? { |e| e == LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER || e == LtiOutbound::LTIRoles::Context::LEARNER }
end
end

View File

@ -77,6 +77,7 @@ module LtiOutbound
hash['text'] = CGI::escape(selected_html) if selected_html
hash['roles'] = user.current_role_types # AccountAdmin, Student, Faculty or Observer
hash['ext_roles'] = '$Canvas.xuser.allRoles'
hash['custom_canvas_enrollment_state'] = '$Canvas.enrollment.enrollmentState'

View File

@ -18,16 +18,16 @@
require 'spec_helper'
describe LtiOutbound::LTIRole do
describe LtiOutbound::LTIRoles do
describe 'constants' do
it 'provides role constants' do
expect(LtiOutbound::LTIRole::INSTRUCTOR).to eq 'Instructor'
expect(LtiOutbound::LTIRole::LEARNER).to eq 'Learner'
expect(LtiOutbound::LTIRole::ADMIN).to eq 'urn:lti:instrole:ims/lis/Administrator'
expect(LtiOutbound::LTIRole::CONTENT_DEVELOPER).to eq 'ContentDeveloper'
expect(LtiOutbound::LTIRole::OBSERVER).to eq 'urn:lti:instrole:ims/lis/Observer'
expect(LtiOutbound::LTIRole::TEACHING_ASSISTANT).to eq 'urn:lti:role:ims/lis/TeachingAssistant'
expect(LtiOutbound::LTIRole::NONE).to eq 'urn:lti:sysrole:ims/lis/None'
expect(LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR).to eq 'Instructor'
expect(LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER).to eq 'Learner'
expect(LtiOutbound::LTIRoles::Institution::ADMIN).to eq 'urn:lti:instrole:ims/lis/Administrator'
expect(LtiOutbound::LTIRoles::ContextNotNamespaced::CONTENT_DEVELOPER).to eq 'ContentDeveloper'
expect(LtiOutbound::LTIRoles::ContextNotNamespaced::OBSERVER).to eq 'urn:lti:instrole:ims/lis/Observer'
expect(LtiOutbound::LTIRoles::ContextNotNamespaced::TEACHING_ASSISTANT).to eq 'urn:lti:role:ims/lis/TeachingAssistant'
expect(LtiOutbound::LTIRoles::System::NONE).to eq 'urn:lti:sysrole:ims/lis/None'
end
end
end

View File

@ -30,8 +30,8 @@ describe LtiOutbound::LTIUser do
it_behaves_like 'it has a proc attribute setter and getter for', :concluded_roles
it_behaves_like 'it has a proc attribute setter and getter for', :currently_active_in_course
let(:teacher_role) { LtiOutbound::LTIRole::INSTRUCTOR }
let(:learner_role) { LtiOutbound::LTIRole::LEARNER }
let(:teacher_role) { LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR }
let(:learner_role) { LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER }
describe '#current_role_types' do
it 'provides a string representation of current roles' do
@ -39,17 +39,17 @@ describe LtiOutbound::LTIUser do
user.current_roles = [teacher_role, learner_role]
end
expect(subject.current_role_types).to eq("#{LtiOutbound::LTIRole::INSTRUCTOR},#{LtiOutbound::LTIRole::LEARNER}")
expect(subject.current_role_types).to eq("#{LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR},#{LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER}")
end
it 'defaults to NONE if no roles exist' do
expect(subject.current_role_types).to eq(LtiOutbound::LTIRole::NONE)
expect(subject.current_role_types).to eq(LtiOutbound::LTIRoles::System::NONE)
end
it 'defaults to NONE if roles are empty' do
subject.current_roles = []
expect(subject.current_role_types).to eq(LtiOutbound::LTIRole::NONE)
expect(subject.current_role_types).to eq(LtiOutbound::LTIRoles::System::NONE)
end
end
@ -59,17 +59,17 @@ describe LtiOutbound::LTIUser do
user.concluded_roles = [teacher_role, learner_role]
end
expect(subject.concluded_role_types).to eq("#{LtiOutbound::LTIRole::INSTRUCTOR},#{LtiOutbound::LTIRole::LEARNER}")
expect(subject.concluded_role_types).to eq("#{LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR},#{LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER}")
end
it 'defaults if no roles exist' do
expect(subject.concluded_role_types).to eq(LtiOutbound::LTIRole::NONE)
expect(subject.concluded_role_types).to eq(LtiOutbound::LTIRoles::System::NONE)
end
it 'defaults to NONE if roles are empty' do
subject.concluded_roles = []
expect(subject.current_role_types).to eq(LtiOutbound::LTIRole::NONE)
expect(subject.current_role_types).to eq(LtiOutbound::LTIRoles::System::NONE)
end
end

View File

@ -45,7 +45,7 @@ describe LtiOutbound::ToolLaunch do
tool.name = 'tool_name'
tool.privacy_level = LtiOutbound::LTITool::PRIVACY_LEVEL_PUBLIC
end
teacher_role = LtiOutbound::LTIRole::INSTRUCTOR
teacher_role = LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR
@user = LtiOutbound::LTIUser.new.tap do |user|
user.avatar_url = 'avatar_url'
user.current_roles = 'current_roles'
@ -317,7 +317,7 @@ describe LtiOutbound::ToolLaunch do
describe '#for_assignment!' do
it 'includes assignment outcome service params for student' do
student_role = LtiOutbound::LTIRole::LEARNER
student_role = LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER
@user.current_roles = [student_role]
@tool_launch.for_assignment!(@assignment, '/my/test/url', '/my/other/test/url')

View File

@ -69,7 +69,7 @@ module Lti
'$Canvas.user.id' => @current_user.id,
'$Canvas.user.sisSourceId' => pseudonym ? pseudonym.sis_user_id : nil,
'$Canvas.user.loginId' => pseudonym ? pseudonym.unique_id : nil,
'$Canvas.user.prefersHighContrast' => -> { @current_user.prefers_high_contrast? ? 'true' : 'false' },
'$Canvas.user.prefersHighContrast' => -> { @current_user.prefers_high_contrast? ? 'true' : 'false' }
}
)
end
@ -86,6 +86,7 @@ module Lti
roles: lti_helper.current_lis_roles,
launch_presentation_locale: I18n.locale || I18n.default_locale.to_s,
launch_presentation_document_target: 'iframe',
ext_roles: lti_helper.all_roles,
# launch_presentation_width:,
# launch_presentation_height:,
# launch_presentation_return_url: return_url,

View File

@ -21,6 +21,23 @@
module Lti
class SubstitutionsHelper
LIS_ROLE_MAP = {
'user' => LtiOutbound::LTIRoles::System::USER,
'siteadmin' => LtiOutbound::LTIRoles::System::SYS_ADMIN,
'teacher' => LtiOutbound::LTIRoles::Institution::INSTRUCTOR,
'student' => LtiOutbound::LTIRoles::Institution::STUDENT,
'admin' => LtiOutbound::LTIRoles::Institution::ADMIN,
AccountUser => LtiOutbound::LTIRoles::Institution::ADMIN,
StudentEnrollment => LtiOutbound::LTIRoles::Context::LEARNER,
TeacherEnrollment => LtiOutbound::LTIRoles::Context::INSTRUCTOR,
TaEnrollment => LtiOutbound::LTIRoles::Context::TEACHING_ASSISTANT,
DesignerEnrollment => LtiOutbound::LTIRoles::Context::CONTENT_DEVELOPER,
ObserverEnrollment => LtiOutbound::LTIRoles::Context::OBSERVER,
StudentViewEnrollment => LtiOutbound::LTIRoles::Context::LEARNER
}
def initialize(context, root_account, user)
@context = context
@root_account = root_account
@ -43,6 +60,19 @@ module Lti
enrollments.map { |enrollment| Lti::LtiUserCreator::ENROLLMENT_MAP[enrollment.class] }.uniq
end
def all_roles
if @user
context_roles = course_enrollments.map { |enrollment| LIS_ROLE_MAP[enrollment.class] }
institution_roles = @user.roles.map { |role| LIS_ROLE_MAP[role] }
if Account.site_admin.account_users_for(@user).present?
institution_roles << LIS_ROLE_MAP['siteadmin']
end
(context_roles + institution_roles).uniq.sort.join(',')
else
[LtiOutbound::LTIRoles::System::NONE]
end
end
def course_enrollments
return [] unless @context.is_a?(Course)
@current_course_enrollments ||= @context.current_enrollments.where(user_id: @user.id)
@ -60,7 +90,7 @@ module Lti
def current_lis_roles
enrollments = course_enrollments + account_enrollments
enrollments.size > 0 ? enrollments_to_lis_roles(enrollments).join(',') : LtiOutbound::LTIRole::NONE
enrollments.size > 0 ? enrollments_to_lis_roles(enrollments).join(',') : LtiOutbound::LTIRoles::System::NONE
end
def concluded_course_enrollments
@ -69,7 +99,7 @@ module Lti
end
def concluded_lis_roles
concluded_course_enrollments.size > 0 ? enrollments_to_lis_roles(concluded_course_enrollments).join(',') : LtiOutbound::LTIRole::NONE
concluded_course_enrollments.size > 0 ? enrollments_to_lis_roles(concluded_course_enrollments).join(',') : LtiOutbound::LTIRoles::System::NONE
end
def current_canvas_roles

View File

@ -31,7 +31,7 @@ module Lti
let(:account) { Account.new(root_account: root_account) }
let(:course) { Course.new(account: account) }
let(:user) { User.new }
let(:substitution_helper) { mock }
let(:substitution_helper) { stub_everything }
before(:each) {
subject.domain_root_account = root_account
@ -210,6 +210,12 @@ module Lti
subject.default_lti_params[:roles].should == 'Learner'
end
it "generates ext_roles" do
Lti::SubstitutionsHelper.stubs(:new).returns(substitution_helper)
substitution_helper.stubs(:all_roles).returns('Admin,User')
subject.default_lti_params[:ext_roles].should == 'Admin,User'
end
it "generates launch_presentation_locale" do
subject.default_lti_params[:launch_presentation_locale].should == :en
end
@ -220,6 +226,7 @@ module Lti
it "generates user_id" do
subject.current_user = user
user.stubs(:roles).returns(['User'])
subject.default_lti_params[:user_id].should == Lti::Asset.opaque_identifier_for(user)
end
end

View File

@ -105,6 +105,29 @@ module Lti
end
end
describe '#all_roles' do
it 'converts multiple roles' do
subject.stubs(:course_enrollments).returns([StudentEnrollment.new, TeacherEnrollment.new, DesignerEnrollment.new, ObserverEnrollment.new, TaEnrollment.new, AccountUser.new])
user.stubs(:roles).returns(['user', 'student', 'teacher', 'admin'])
roles = subject.all_roles
roles.should include LtiOutbound::LTIRoles::System::USER
roles.should include LtiOutbound::LTIRoles::Institution::STUDENT
roles.should include LtiOutbound::LTIRoles::Institution::INSTRUCTOR
roles.should include LtiOutbound::LTIRoles::Institution::ADMIN
roles.should include LtiOutbound::LTIRoles::Context::LEARNER
roles.should include LtiOutbound::LTIRoles::Context::INSTRUCTOR
roles.should include LtiOutbound::LTIRoles::Context::CONTENT_DEVELOPER
roles.should include LtiOutbound::LTIRoles::Context::OBSERVER
roles.should include LtiOutbound::LTIRoles::Context::TEACHING_ASSISTANT
end
it "returns none if no user" do
helper = SubstitutionsHelper.new(course, root_account, nil)
helper.all_roles.should == [LtiOutbound::LTIRoles::System::NONE]
end
end
describe '#course_enrollments' do
it 'returns an empty array if the context is not a course' do
helper = SubstitutionsHelper.new(account, root_account, user)

View File

@ -127,6 +127,7 @@ describe "LTI integration tests" do
'resource_link_id' => canvas_tool.opaque_identifier_for(canvas_course),
'resource_link_title' => canvas_tool.name,
'roles' => 'Instructor,urn:lti:role:ims/lis/TeachingAssistant',
'ext_roles' => "urn:lti:instrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Instructor,urn:lti:role:ims/lis/Instructor,urn:lti:role:ims/lis/TeachingAssistant,urn:lti:sysrole:ims/lis/User",
'tool_consumer_info_product_family_code' => 'canvas',
'tool_consumer_info_version' => 'cloud',
'tool_consumer_instance_contact_email' => HostUrl.outgoing_email_address,
@ -210,7 +211,7 @@ describe "LTI integration tests" do
hash['custom_canvas_account_id'].should == sub_account.id.to_s
hash['custom_canvas_account_sis_id'].should == 'accountsis'
hash['custom_canvas_user_login_id'].should == @user.pseudonyms.first.unique_id
hash['custom_variable_canvas_membership_concluded_roles'].should == LtiOutbound::LTIRole::NONE
hash['custom_variable_canvas_membership_concluded_roles'].should == LtiOutbound::LTIRoles::System::NONE
end
it "should add account and user info in launch data for user profile launch" do

View File

@ -88,7 +88,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::LEARNER]
enrollments.should == [LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER]
end
it "collects current active student view enrollments" do
@ -96,7 +96,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::LEARNER]
enrollments.should == [LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER]
end
it "collects current active teacher enrollments" do
@ -104,7 +104,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::INSTRUCTOR]
enrollments.should == [LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR]
end
it "collects current active ta enrollments" do
@ -112,7 +112,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::TEACHING_ASSISTANT]
enrollments.should == [LtiOutbound::LTIRoles::ContextNotNamespaced::TEACHING_ASSISTANT]
end
it "collects current active course designer enrollments" do
@ -120,7 +120,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::CONTENT_DEVELOPER]
enrollments.should == [LtiOutbound::LTIRoles::ContextNotNamespaced::CONTENT_DEVELOPER]
end
it "collects current active account user enrollments from an account" do
@ -128,7 +128,7 @@ describe Lti::LtiUserCreator do
enrollments = account_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::ADMIN]
enrollments.should == [LtiOutbound::LTIRoles::Institution::ADMIN]
end
it "collects current active account user enrollments from a course" do
@ -136,7 +136,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::ADMIN]
enrollments.should == [LtiOutbound::LTIRoles::Institution::ADMIN]
end
it "collects current active account user enrollments for the root account of a course" do
@ -144,7 +144,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.current_roles
enrollments.should == [LtiOutbound::LTIRole::ADMIN]
enrollments.should == [LtiOutbound::LTIRoles::Institution::ADMIN]
end
it "does not include enrollments from other courses" do
@ -213,7 +213,7 @@ describe Lti::LtiUserCreator do
enrollments = course_user_creator.convert.concluded_roles
enrollments.should == [LtiOutbound::LTIRole::LEARNER]
enrollments.should == [LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER]
end
it "does not return any course enrollments when the context is an account" do
@ -282,7 +282,7 @@ describe Lti::LtiUserCreator do
hash = {variable: '$Canvas.membership.concludedRoles'}
variable_substitutor.substitute!(hash)
hash[:variable].should == [LtiOutbound::LTIRole::LEARNER,LtiOutbound::LTIRole::INSTRUCTOR].join(',')
hash[:variable].should == [LtiOutbound::LTIRoles::ContextNotNamespaced::LEARNER,LtiOutbound::LTIRoles::ContextNotNamespaced::INSTRUCTOR].join(',')
end
it "adds roles" do