spec: allow a tool to to hit LTI API in a provider state
This stubs a few parts of our JWT verification process so that an expired JWT can be used to make API calls when the contract test build runs. The JWT still needs to be valid, aside from the iat and exp timestamps, and should be signed with the key in the live-events-lti repository. test plan: - Set up the live-events-subscription-service locally - Run the contract tests for the live-events-lti repo and save the generated JSON file somewhere - In canvas-lms, run rails pact:verify:at[./path_to_contracts.json] - It should pass fixes PLAT-5101 Change-Id: Iff5a6a91aa2ad9868511d3396c73d8225587f640 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/216596 Tested-by: Jenkins Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Marc Phillips <mphillips@instructure.com> QA-Review: Tucker Mcknight <tmcknight@instructure.com> Product-Review: Tucker Mcknight <tmcknight@instructure.com>
This commit is contained in:
parent
560ca2712d
commit
cf928b37cd
|
@ -15,39 +15,112 @@
|
|||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module LtiProviderStateHelper
|
||||
|
||||
def self.set_lti_context_id(account)
|
||||
# Set lti_context_id to the same one used when generating the contracts
|
||||
# in the live-events-lti repo.
|
||||
account.update!(lti_context_id: "794d72b707af6ea82cfe3d5d473f16888a8366c7")
|
||||
end
|
||||
|
||||
def self.jwk
|
||||
{
|
||||
"kty" => 'RSA',
|
||||
"e" => 'test',
|
||||
"n" => 'test',
|
||||
"kid" => 'test',
|
||||
"alg" => 'RS256',
|
||||
"use" => 'test',
|
||||
"iss" => 'test',
|
||||
"aud" => 'http://example.org/login/oauth2/token',
|
||||
"sub" => 'test',
|
||||
"exp" => (Time.zone.now + 10.minutes).to_i,
|
||||
"iat" => Time.zone.now.to_i,
|
||||
"jti" => 'test',
|
||||
}
|
||||
end
|
||||
|
||||
def self.developer_key(jwk)
|
||||
account = Pact::Canvas.base_state.account
|
||||
developer_key = account.developer_keys.create!(
|
||||
public_jwk: jwk,
|
||||
public_jwk_url: 'example.org',
|
||||
scopes: [
|
||||
"https://canvas.instructure.com/lti/public_jwk/scope/update",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/create",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/show",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/update",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/list",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/destroy",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/list_event_types",
|
||||
"https://canvas.instructure.com/lti/feature_flags/scope/show",
|
||||
]
|
||||
)
|
||||
enable_developer_key_account_binding!(developer_key)
|
||||
developer_key.developer_key_account_bindings.first.workflow_state = 'on'
|
||||
developer_key.developer_key_account_bindings.first.save!
|
||||
|
||||
developer_key
|
||||
end
|
||||
|
||||
def self.create_external_tool(developer_key)
|
||||
configuration = {
|
||||
"title":"Canvas Data Services",
|
||||
"scopes":[
|
||||
"https://canvas.instructure.com/lti/public_jwk/scope/update",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/create",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/show",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/update",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/list",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/destroy",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/list_event_types",
|
||||
"https://canvas.instructure.com/lti/feature_flags/scope/show"
|
||||
],
|
||||
"public_jwk_url":"http://live-events-lti/api/jwks",
|
||||
"description":"Data service management for Canvas LMS",
|
||||
"target_link_uri":"http://live-events-lti/resource_link_request",
|
||||
"oidc_initiation_url":"http://live-events-lti/login",
|
||||
"extensions":[
|
||||
{
|
||||
"platform":"canvas.instructure.com",
|
||||
"domain":"http://live-events-lti",
|
||||
"privacy_level":"public",
|
||||
"settings":{
|
||||
"placements":[
|
||||
{
|
||||
"text":"Data Services",
|
||||
"enabled":true,
|
||||
"placement":"account_navigation",
|
||||
"target_link_uri":"http://live-events-lti/resource_link_request",
|
||||
"required_permissions":"manage_data_services"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"custom_fields":{
|
||||
"canvas_account_uuid":"$vnd.Canvas.root_account.uuid",
|
||||
"canvas_api_domain":"$Canvas.api.domain",
|
||||
"canvas_user_uuid":"$Canvas.user.globalId",
|
||||
"canvas_high_contrast_enabled":"$Canvas.user.prefersHighContrast"
|
||||
}
|
||||
}
|
||||
tool_config = Lti::ToolConfiguration.create!(developer_key: developer_key, settings: configuration, privacy_level: 'public')
|
||||
external_tool = tool_config.new_external_tool(developer_key.account)
|
||||
external_tool.save!
|
||||
end
|
||||
end
|
||||
|
||||
Pact.provider_states_for PactConfig::Consumers::ALL do
|
||||
|
||||
provider_state 'an account with an LTI developer key' do
|
||||
set_up do
|
||||
account = Pact::Canvas.base_state.account
|
||||
jwk = {
|
||||
"kty" => 'RSA',
|
||||
"e" => 'test',
|
||||
"n" => 'test',
|
||||
"kid" => 'test',
|
||||
"alg" => 'RS256',
|
||||
"use" => 'test',
|
||||
"iss" => 'test',
|
||||
"aud" => 'http://example.org/login/oauth2/token',
|
||||
"sub" => 'test',
|
||||
"exp" => (Time.zone.now + 10.minutes).to_i,
|
||||
"iat" => Time.zone.now.to_i,
|
||||
"jti" => 'test',
|
||||
}
|
||||
developer_key = account.developer_keys.create!(
|
||||
public_jwk: jwk,
|
||||
public_jwk_url: 'example.org',
|
||||
scopes: [
|
||||
"https://canvas.instructure.com/lti/public_jwk/scope/update",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/create",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/show",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/update",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/list",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/destroy",
|
||||
"https://canvas.instructure.com/lti/data_services/scope/list_event_types",
|
||||
"https://canvas.instructure.com/lti/feature_flags/scope/show",
|
||||
]
|
||||
)
|
||||
LtiProviderStateHelper.set_lti_context_id(account)
|
||||
|
||||
jwk = LtiProviderStateHelper.jwk
|
||||
developer_key = LtiProviderStateHelper.developer_key(jwk)
|
||||
|
||||
allow_any_instance_of(Canvas::Oauth::Provider).
|
||||
to receive(:key).and_return(developer_key)
|
||||
|
||||
|
@ -58,6 +131,45 @@ Pact.provider_states_for PactConfig::Consumers::ALL do
|
|||
|
||||
provider_state 'a course with live events' do
|
||||
set_up do
|
||||
jwk = LtiProviderStateHelper.jwk
|
||||
developer_key = LtiProviderStateHelper.developer_key(jwk)
|
||||
LtiProviderStateHelper.create_external_tool(developer_key)
|
||||
|
||||
account = Pact::Canvas.base_state.account
|
||||
LtiProviderStateHelper.set_lti_context_id(account)
|
||||
|
||||
allow_any_instance_of(Canvas::Oauth::Provider).
|
||||
to receive(:key).and_return(developer_key)
|
||||
|
||||
allow_any_instance_of(Canvas::Oauth::ClientCredentialsProvider).
|
||||
to receive(:get_jwk_from_url).and_return(jwk)
|
||||
|
||||
# The jwt_signing_key file is the same one used to sign the JWTs in the contract
|
||||
# tests in the live-events-lti repo. Make that key be the one that Canvas uses
|
||||
# to decode JWTs.
|
||||
lti_tool_key = OpenSSL::PKey::RSA.new(File.read('../../jwt_signing_key'))
|
||||
allow(Canvas::Security).to receive(:encryption_keys).and_return([lti_tool_key])
|
||||
|
||||
# The JWT in the contracts will be expired; tell Canvas to accept it anyway.
|
||||
a_long_time = Time.zone.now.to_i + 3600
|
||||
allow(Setting).to receive(:get).and_call_original
|
||||
allow(Setting).to receive(:get).with("oauth2_jwt_iat_ago_in_seconds", anything).and_return(a_long_time.to_s)
|
||||
allow_any_instance_of(Canvas::Security::JwtValidator).to receive(:exp).and_return(true)
|
||||
|
||||
# DynamicSettings is not available on Jenkins -- need to stub it to return these values.
|
||||
allow(Canvas::DynamicSettings).to receive(:find).with(any_args).and_call_original
|
||||
allow(Canvas::DynamicSettings).to receive(:find).with("canvas").and_return(
|
||||
{
|
||||
"signing-secret" => "astringthatisactually32byteslong",
|
||||
"encryption-secret" => "astringthatisactually32byteslong"
|
||||
}
|
||||
)
|
||||
allow(Canvas::DynamicSettings).to receive(:find).
|
||||
with('live-events-subscription-service', any_args).and_return({
|
||||
'app-host' => ENV.fetch('SUBSCRIPTION_SERVICE_HOST', 'http://les.docker:80')
|
||||
})
|
||||
|
||||
# Always set ignore_expiration to true when calling the decode_jwt method.
|
||||
Canvas::Security.class_eval do
|
||||
@old_decode_jwt = self.method(:decode_jwt)
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA/x5VIHHRvgKe5siSCn6NjfDTlsiitmldZ6o7EDy7+xWY8KVR
|
||||
ynR0Nl+lgQ7Pcp5B5wTGrur+awv/sA6lPp72H/l8EJhFR9NOelEzW4zzRFswMa+N
|
||||
hA2FEOLM9u4744SilbXu0rlLnMCFtS0PVa01Gh7kGvZ4KM534egdXo2ZSAieeH7G
|
||||
bZ0ZjRJfbv6l7Ctc+sKaAtPcpPrCc86WpmgVOePZa4VsOkfkbchdejrxHYxtHoua
|
||||
F06AsVJXdwor/gkYS1ahh5SO/NgRo+J8vNXIibzY17LLHN7matO3HcZoN4c8rNII
|
||||
hWiWJckd3GKb2b/HXq/5qoYGkFGVVVP0sOkrXQIDAQABAoIBAQCUHWcs5AfyuhDJ
|
||||
Sk9HmnvSmawukaOuJfQduH58CdbVio91v3WCBiRmYRd0m0WjdPAsEODNMw+s1JWJ
|
||||
AKe9eIrKu4zlEZK/hZW9fCFGGMovuIV9gz+1GChWSmbXQi8xA5NlOfBDFWMpybiX
|
||||
HGcXxezbkm26nbfbcSu2040hlTIV2ApyZV5Wy5/rrU0/IC3OeQ+XNSE3vDRZCnpz
|
||||
E1k0NLVV8zGaAF4sILBnYA+q5RKlhn+1+ajWixnHug7G8XB6ZvNNkElaaroGigiw
|
||||
K/VeMvwwl/O4cZ4eSGPkYmtU1uCw2tX4PM/9IMLz4xT+Ff2BxomPnL8gvUujfbfP
|
||||
KOksFrRBAoGBAP+9es3qnO1BdjCCLV00eLCeC0Wb9D+FZof2v1efJvjUqDKiL5Z1
|
||||
ZmBav6HT+owX6uE/bmPejk5KXFsq+CjcCW9w5gwn2GA0p4B3fs9axDnZq+FL2r69
|
||||
hoCoVKI6utvmqjWOuALogtiUOirmP013N7neI3/rVI03iHNGy+RVYm7tAoGBAP9g
|
||||
sO1BIB0RRoAod3F8YiFGlRSZfJTRs6AiSNSotfLYNrx6DQBgDbWsiLq26YfE2e1D
|
||||
6bqoVMJkkRYpA+FYU2MJ0oF7E+v56s0bRMBBKKObxDW+2Q98/i9go85LEWC7jGKb
|
||||
UHU/PH4xDlq8TzeJoOeB1qYMbgSrG+8/GOfXK7AxAoGAEAfEhtvJ8mVED05ZoZoE
|
||||
Zq3Bbx+Tc9fc0XD6FXf4bWiHEoVwDjJVtHx7vp0W+2kUZAIh3Ui6CtZGa8CJxaXl
|
||||
QYMGKITm30DtrvPOkxjRa/7k8z5Z+9LNd4sVowWjaN1QlgLYLfZ9HS5NZxr/pM9w
|
||||
QspV11Lc/e0ZNICfjzR68xECgYEAwXyK0FdFc4CBP9xpEuzAlKGbli3sO/zd8XfI
|
||||
Yocow8OZRRfb/erIuFruhTjMmvdEfgW0cp3TCi2T14xfyj5Xf3QTr9KGd4W0po4A
|
||||
ewFjPwJnmKjuYFO9ajv4H/a0RewTIyq1vP+aX6nfTFPcWSHHbV/sN4a3XIYf9haC
|
||||
UjWufiECgYB90++PvEdQI31ryw51rlZBvm4V2oa1tSvsW9qOIevc72hF1VgGfUAz
|
||||
GojzzT7Q1yT90+U/IKuUgzQzdEasmoy0qnvZGhD+LcqaTA9pLX1jplQVAAYO1q2a
|
||||
JUvBaReqn/v5v/f8F1iL0cSMQezUD2f/dezlwq8uaHBs7RhPw/Iogg==
|
||||
-----END RSA PRIVATE KEY-----
|
Loading…
Reference in New Issue