Add DataServices create action
closes PLAT-4757 Test Plan: - create a subscription using the lti service, note that it works Change-Id: Ia7cb10e4f2c1fd1e6d4a13be2f3d25b2f05e9bc7 Reviewed-on: https://gerrit.instructure.com/206291 Tested-by: Jenkins Reviewed-by: Weston Dransfield <wdransfield@instructure.com> QA-Review: Marc Phillips <mphillips@instructure.com> Product-Review: Marc Phillips <mphillips@instructure.com>
This commit is contained in:
parent
e46919ae2c
commit
d2e26c9295
|
@ -17,15 +17,51 @@
|
|||
|
||||
module Lti
|
||||
class DataServicesController < ApplicationController
|
||||
include Ims::Concerns::LtiServices
|
||||
include Ims::Concerns::AdvantageServices
|
||||
MIME_TYPE = 'application/vnd.canvas.dataservices+json'.freeze
|
||||
|
||||
ACTION_SCOPE_MATCHERS = {
|
||||
create: all_of(TokenScopes::LTI_CREATE_DATA_SERVICE_SUBSCRIPTION_SCOPE)
|
||||
}.freeze.with_indifferent_access
|
||||
|
||||
rescue_from Lti::SubscriptionsValidator::InvalidContextType do
|
||||
render json: {error: 'Invalid context type for subscription'}, status: :bad_request
|
||||
end
|
||||
|
||||
rescue_from Lti::SubscriptionsValidator::ContextNotFound do
|
||||
render json: {error: 'Invalid context for subscription - context not found.'}, status: :bad_request
|
||||
end
|
||||
|
||||
before_action :verify_service_configured
|
||||
|
||||
# @API Create a Webhook Subscription
|
||||
# Creates a webook subscription for the specified event type and
|
||||
# context.
|
||||
#
|
||||
# @argument subscription[ContextId] [Required, String]
|
||||
# The id of the context for the subscription.
|
||||
#
|
||||
# @argument subscription[ContextType] [Required, String]
|
||||
# The type of context for the subscription. Must be 'assignment',
|
||||
# 'account', or 'course'.
|
||||
#
|
||||
# @argument subscription[EventTypes] [Required, Array]
|
||||
# Array of strings representing the event types for
|
||||
# the subscription.
|
||||
#
|
||||
# @argument subscription[Format] [Required, String]
|
||||
# Format to deliver the live events. Must be 'live-event' or 'caliper'.
|
||||
#
|
||||
# @argument subscription[TransportMetadata] [Required, Object]
|
||||
# An object with a single key: 'Url'. Example: { "Url": "sqs.example" }
|
||||
#
|
||||
# @argument subscription[TransportType] [Required, String]
|
||||
# Must be either 'sqs' or 'https'.
|
||||
def create
|
||||
render json: {info: 'Not yet Implemented'}, content_type: MIME_TYPE
|
||||
sub = params.require(:subscription)
|
||||
SubscriptionsValidator.validate_subscription_context!(sub)
|
||||
response = Services::LiveEventsSubscriptionService.create(jwt_body, sub.to_unsafe_h)
|
||||
forward_service_response(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -33,5 +69,28 @@ module Lti
|
|||
def scopes_matcher
|
||||
ACTION_SCOPE_MATCHERS.fetch(action_name, self.class.none)
|
||||
end
|
||||
|
||||
def verify_service_configured
|
||||
unless Services::LiveEventsSubscriptionService.available?
|
||||
render json: {error: 'Subscription service not configured'}, status: :internal_server_error
|
||||
end
|
||||
end
|
||||
|
||||
def forward_service_response(service_response)
|
||||
render json: service_response.body, status: service_response.code, content_type: MIME_TYPE
|
||||
end
|
||||
|
||||
def jwt_body
|
||||
{
|
||||
sub: SecureRandom.uuid,
|
||||
DeveloperKey: developer_key.global_id.to_s,
|
||||
RootAccountId: context.global_id,
|
||||
RootAccountUUID: context.uuid
|
||||
}
|
||||
end
|
||||
|
||||
def context
|
||||
Account.active.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,8 @@ module Lti
|
|||
end
|
||||
class ToolNotInContext < StandardError
|
||||
end
|
||||
class ContextNotFound < StandardError
|
||||
end
|
||||
|
||||
CONTEXT_WHITELIST = {
|
||||
'root_account' => Account,
|
||||
|
@ -59,19 +61,30 @@ module Lti
|
|||
check_tool_context!
|
||||
end
|
||||
|
||||
def self.validate_subscription_context!(subscription)
|
||||
raise ContextNotFound unless retrieve_context(subscription).present?
|
||||
true
|
||||
end
|
||||
|
||||
def self.retrieve_context(subscription)
|
||||
model = CONTEXT_WHITELIST[subscription[:ContextType]]
|
||||
raise InvalidContextType unless model
|
||||
|
||||
case subscription[:ContextType]
|
||||
when "root_account"
|
||||
model.find_by(uuid: subscription[:ContextId])
|
||||
else
|
||||
model.find(subscription[:ContextId])
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
raise ContextNotFound
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscription_context
|
||||
@_subscription_context ||= begin
|
||||
model = CONTEXT_WHITELIST[subscription[:ContextType]]
|
||||
raise InvalidContextType unless model
|
||||
|
||||
case subscription[:ContextType]
|
||||
when "root_account"
|
||||
model.find_by(uuid: subscription[:ContextId])
|
||||
else
|
||||
model.find(subscription[:ContextId])
|
||||
end
|
||||
SubscriptionsValidator.retrieve_context(subscription)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,13 +23,11 @@ module Services
|
|||
end
|
||||
|
||||
def tool_proxy_subscription(tool_proxy, subscription_id)
|
||||
options = { headers: headers(tool_proxy_jwt_body(tool_proxy)) }
|
||||
request(:get, "/api/subscriptions/#{subscription_id}", options)
|
||||
show(tool_proxy_jwt_body(tool_proxy), subscription_id)
|
||||
end
|
||||
|
||||
def tool_proxy_subscriptions(tool_proxy, optional_headers = {})
|
||||
options = { headers: headers(tool_proxy_jwt_body(tool_proxy), optional_headers) }
|
||||
request(:get, '/api/subscriptions', options)
|
||||
index(tool_proxy_jwt_body(tool_proxy), optional_headers)
|
||||
end
|
||||
|
||||
def create_tool_proxy_subscription(tool_proxy, subscription)
|
||||
|
@ -37,19 +35,11 @@ module Services
|
|||
"in: LiveEventsSubscriptionService::create_tool_proxy_subscription, "\
|
||||
"tool_proxy_id: #{tool_proxy.id}, subscription: #{subscription}"
|
||||
end
|
||||
options = {
|
||||
headers: headers(tool_proxy_jwt_body(tool_proxy), { 'Content-Type' => 'application/json' }),
|
||||
body: subscription.to_json
|
||||
}
|
||||
request(:post, '/api/subscriptions', options)
|
||||
create(tool_proxy_jwt_body(tool_proxy), subscription)
|
||||
end
|
||||
|
||||
def update_tool_proxy_subscription(tool_proxy, subscription_id, subscription)
|
||||
options = {
|
||||
headers: headers(tool_proxy_jwt_body(tool_proxy), { 'Content-Type' => 'application/json' }),
|
||||
body: subscription.to_json
|
||||
}
|
||||
request(:put, "/api/subscriptions/#{subscription_id}", options)
|
||||
def update_tool_proxy_subscription(tool_proxy, _subscription_id, subscription)
|
||||
update(tool_proxy_jwt_body(tool_proxy), subscription)
|
||||
end
|
||||
|
||||
def destroy_tool_proxy_subscription(tool_proxy, subscription_id)
|
||||
|
@ -57,8 +47,7 @@ module Services
|
|||
"in: LiveEventsSubscriptionService::destroy_tool_proxy_subscription, "\
|
||||
"tool_proxy_id: #{tool_proxy.id}, subscription_id: #{subscription_id}"
|
||||
end
|
||||
options = { headers: headers(tool_proxy_jwt_body(tool_proxy)) }
|
||||
request(:delete, "/api/subscriptions/#{subscription_id}", options)
|
||||
destroy(tool_proxy_jwt_body(tool_proxy), subscription_id)
|
||||
end
|
||||
|
||||
def destroy_all_tool_proxy_subscriptions(tool_proxy)
|
||||
|
@ -66,7 +55,39 @@ module Services
|
|||
request(:delete, "/api/subscriptions", options)
|
||||
end
|
||||
|
||||
def create(jwt_body, subscription)
|
||||
options = {
|
||||
headers: headers(jwt_body, { 'Content-Type' => 'application/json' }),
|
||||
body: subscription.to_json
|
||||
}
|
||||
request(:post, '/api/subscriptions', options)
|
||||
end
|
||||
|
||||
def show(jwt_body, subscription_id)
|
||||
options = { headers: headers(jwt_body) }
|
||||
request(:get, "/api/subscriptions/#{subscription_id}", options)
|
||||
end
|
||||
|
||||
def update(jwt_body, subscription)
|
||||
options = {
|
||||
headers: headers(jwt_body, { 'Content-Type' => 'application/json' }),
|
||||
body: subscription.to_json
|
||||
}
|
||||
request(:put, "/api/subscriptions/#{subscription['Id']}", options)
|
||||
end
|
||||
|
||||
def destroy(jwt_body, subscription_id)
|
||||
options = { headers: headers(jwt_body) }
|
||||
request(:delete, "/api/subscriptions/#{subscription_id}", options)
|
||||
end
|
||||
|
||||
def index(jwt_body, opts)
|
||||
options = { headers: headers(jwt_body, opts) }
|
||||
request(:get, '/api/subscriptions', options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, endpoint, options = {})
|
||||
Canvas.timeout_protection("live-events-subscription-service-session", raise_on_timeout: true) do
|
||||
HTTParty.send(method, "#{settings['app-host']}#{endpoint}", options.merge(timeout: 10))
|
||||
|
|
|
@ -23,13 +23,33 @@ require_dependency "lti/public_jwk_controller"
|
|||
|
||||
describe Lti::DataServicesController do
|
||||
describe '#create' do
|
||||
include WebMock::API
|
||||
|
||||
include_context 'advantage services context'
|
||||
|
||||
let(:subscription) do
|
||||
{
|
||||
ContextId: root_account.uuid,
|
||||
ContextType: 'root_account',
|
||||
EventTypes: ['discussion_topic_created'],
|
||||
Format: 'live-event',
|
||||
TransportMetadata: { Url: 'sqs.example' },
|
||||
TransportType: 'sqs'
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Canvas::Security::ServicesJwt).to receive(:encryption_secret).and_return('setecastronomy92' * 2)
|
||||
allow(Canvas::Security::ServicesJwt).to receive(:signing_secret).and_return('donttell' * 10)
|
||||
allow(HTTParty).to receive(:send).and_return(double(body: subscription, code: 200))
|
||||
end
|
||||
|
||||
it_behaves_like 'lti services' do
|
||||
let(:action) { :create }
|
||||
let(:expected_mime_type) { described_class::MIME_TYPE }
|
||||
let(:scope_to_remove) { "https://canvas.instructure.com/lti/data_services/scope/create"}
|
||||
let(:params_overrides) do
|
||||
{ developer_key: { public_jwk: {} }, account_id: root_account.id }
|
||||
{ subscription: subscription, account_id: root_account.id }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -192,11 +192,11 @@ module Services
|
|||
it 'makes the expected request' do
|
||||
allow(tool_proxy).to receive(:context).and_return(root_account_context)
|
||||
allow(root_account_context).to receive(:root_account).and_return(root_account_object)
|
||||
subscription = { 'my' => 'subscription' }
|
||||
subscription = { 'my' => 'subscription', 'Id' => '1234' }
|
||||
|
||||
expect(HTTParty).to receive(:send) do |method, endpoint, options|
|
||||
expect(method).to eq(:put)
|
||||
expect(endpoint).to eq('http://example.com/api/subscriptions/subscription_id')
|
||||
expect(endpoint).to eq('http://example.com/api/subscriptions/1234')
|
||||
expect(options[:headers]['Content-Type']).to eq('application/json')
|
||||
jwt = Canvas::Security::ServicesJwt.new(options[:headers]['Authorization'].gsub('Bearer ',''), false).original_token
|
||||
expect(jwt['DeveloperKey']).to eq('10000000000003')
|
||||
|
|
Loading…
Reference in New Issue