2020-10-27 00:50:13 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-04-28 04:06:18 +08:00
|
|
|
#
|
|
|
|
# Copyright (C) 2017 - present Instructure, Inc.
|
|
|
|
#
|
|
|
|
# This file is part of Canvas.
|
|
|
|
#
|
|
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
|
|
# Software Foundation, version 3 of the License.
|
|
|
|
#
|
|
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
|
|
# details.
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
2017-01-20 06:55:44 +08:00
|
|
|
module Services
|
|
|
|
class LiveEventsSubscriptionService
|
|
|
|
class << self
|
|
|
|
def available?
|
2022-04-19 06:17:43 +08:00
|
|
|
settings.present? && settings["app-host"].present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def disabled?
|
|
|
|
settings&.[]("disabled")
|
2017-01-20 06:55:44 +08:00
|
|
|
end
|
|
|
|
|
2017-02-09 03:04:05 +08:00
|
|
|
def tool_proxy_subscription(tool_proxy, subscription_id)
|
2019-08-23 00:31:23 +08:00
|
|
|
show(tool_proxy_jwt_body(tool_proxy), subscription_id)
|
2017-02-09 03:04:05 +08:00
|
|
|
end
|
|
|
|
|
2017-06-24 03:04:43 +08:00
|
|
|
def tool_proxy_subscriptions(tool_proxy, optional_headers = {})
|
2019-08-23 00:31:23 +08:00
|
|
|
index(tool_proxy_jwt_body(tool_proxy), optional_headers)
|
2017-01-20 06:55:44 +08:00
|
|
|
end
|
|
|
|
|
Add create to live event subscription service
fixes PLAT-2184
Test plan:
* With the subscription service and canvas configured to use the same
encryption and signing secret
* Start up the subscription service and a rails console
* In the console run the following commands
ToolProxy = Struct.new("ToolProxy", :guid, :product_family)
Family = Struct.new("Family", :developer_key)
f = Family.new(10000000000003)
tp = ToolProxy.new('hahahah', f)
subscription = {
"RootAccountId" => "1",
"EventTypes" => ["submission_created"],
"ContextType" => "quiz",
"ContextId" => "5001",
"Format" => "live-event",
"TransportType" => "sqs",
"TransportMetadata" => {
"Url" => "http://sqs.docker"
},
"UserId" => "3000",
}
res = Services::LiveEventsSubscriptionService.create_tool_proxy_subscription(tp, subscription)
* Ensure that you get a 200 response back with the newly created
subscription in it
* Ensure that dynamo has the new record
Change-Id: Ifa7f68983efbcb79058854fbd3ba802505b8d4b2
Reviewed-on: https://gerrit.instructure.com/101069
Tested-by: Jenkins
Reviewed-by: Matthew Wheeler <mwheeler@instructure.com>
Reviewed-by: Nathan Mills <nathanm@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Andrew Butterfield <abutterfield@instructure.com>
2017-02-04 06:57:17 +08:00
|
|
|
def create_tool_proxy_subscription(tool_proxy, subscription)
|
2019-08-20 23:37:58 +08:00
|
|
|
Rails.logger.info do
|
|
|
|
"in: LiveEventsSubscriptionService::create_tool_proxy_subscription, " \
|
2021-09-27 23:26:51 +08:00
|
|
|
"tool_proxy_id: #{tool_proxy.id}, subscription: #{subscription}"
|
2019-08-20 23:37:58 +08:00
|
|
|
end
|
2019-08-23 00:31:23 +08:00
|
|
|
create(tool_proxy_jwt_body(tool_proxy), subscription)
|
Add create to live event subscription service
fixes PLAT-2184
Test plan:
* With the subscription service and canvas configured to use the same
encryption and signing secret
* Start up the subscription service and a rails console
* In the console run the following commands
ToolProxy = Struct.new("ToolProxy", :guid, :product_family)
Family = Struct.new("Family", :developer_key)
f = Family.new(10000000000003)
tp = ToolProxy.new('hahahah', f)
subscription = {
"RootAccountId" => "1",
"EventTypes" => ["submission_created"],
"ContextType" => "quiz",
"ContextId" => "5001",
"Format" => "live-event",
"TransportType" => "sqs",
"TransportMetadata" => {
"Url" => "http://sqs.docker"
},
"UserId" => "3000",
}
res = Services::LiveEventsSubscriptionService.create_tool_proxy_subscription(tp, subscription)
* Ensure that you get a 200 response back with the newly created
subscription in it
* Ensure that dynamo has the new record
Change-Id: Ifa7f68983efbcb79058854fbd3ba802505b8d4b2
Reviewed-on: https://gerrit.instructure.com/101069
Tested-by: Jenkins
Reviewed-by: Matthew Wheeler <mwheeler@instructure.com>
Reviewed-by: Nathan Mills <nathanm@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Andrew Butterfield <abutterfield@instructure.com>
2017-02-04 06:57:17 +08:00
|
|
|
end
|
|
|
|
|
2019-08-23 00:31:23 +08:00
|
|
|
def update_tool_proxy_subscription(tool_proxy, _subscription_id, subscription)
|
|
|
|
update(tool_proxy_jwt_body(tool_proxy), subscription)
|
Add ability to update a subscription
fixes PLAT-2239
Test plan:
* With the subscription service and canvas configured to use the same
encryption key and signing secret
* Start up the subscription service and a rails console
* In the console run the following commands being sure to use your
developer key and the right subscription Id
ToolProxy = Struct.new("ToolProxy", :guid, :product_family)
Family = Struct.new("Family", :developer_key)
f = Family.new(10000000000003)
tp = ToolProxy.new('hahahah', f)
subscription = {
"Id" => "some uuid",
"RootAccountId" => "1",
"EventTypes" => ["submission_created"],
"ContextType" => "course",
"ContextId" => "5001",
"Format" => "live-event",
"TransportType" => "sqs",
"TransportMetadata" => {
"Url" => "http://sqs.docker"
},
}
res = Services::LiveEventsSubscriptionService.update_tool_proxy_subscription(tp, "the subscription id", subscription)
* Ensure that you get a 200 back with a copy of the updated subscription
* Ensure that dynamo has the full subscription persisted with the
changes you made
Change-Id: Ia2fd506f45f414e5a1940a9eef978d1b4c566397
Reviewed-on: https://gerrit.instructure.com/102341
Tested-by: Jenkins
Reviewed-by: Nathan Mills <nathanm@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Andrew Butterfield <abutterfield@instructure.com>
2017-02-16 06:34:31 +08:00
|
|
|
end
|
|
|
|
|
2017-02-09 03:12:11 +08:00
|
|
|
def destroy_tool_proxy_subscription(tool_proxy, subscription_id)
|
2019-08-20 23:37:58 +08:00
|
|
|
Rails.logger.info do
|
|
|
|
"in: LiveEventsSubscriptionService::destroy_tool_proxy_subscription, " \
|
2021-09-27 23:26:51 +08:00
|
|
|
"tool_proxy_id: #{tool_proxy.id}, subscription_id: #{subscription_id}"
|
2019-08-20 23:37:58 +08:00
|
|
|
end
|
2019-08-23 00:31:23 +08:00
|
|
|
destroy(tool_proxy_jwt_body(tool_proxy), subscription_id)
|
2017-02-09 03:12:11 +08:00
|
|
|
end
|
|
|
|
|
2017-04-18 03:51:48 +08:00
|
|
|
def destroy_all_tool_proxy_subscriptions(tool_proxy)
|
|
|
|
options = { headers: headers(tool_proxy_jwt_body(tool_proxy)) }
|
|
|
|
request(:delete, "/api/subscriptions", options)
|
|
|
|
end
|
|
|
|
|
2019-08-23 00:31:23 +08:00
|
|
|
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
|
|
|
|
|
2019-09-27 04:39:24 +08:00
|
|
|
def index(jwt_body, opts = {}, query: {})
|
2023-06-02 06:06:09 +08:00
|
|
|
options = { headers: headers(jwt_body, opts), query: }
|
2019-08-30 00:34:22 +08:00
|
|
|
request(:get, "/api/root_account_subscriptions", options)
|
2019-08-23 00:31:23 +08:00
|
|
|
end
|
|
|
|
|
2019-09-06 02:23:21 +08:00
|
|
|
def event_types_index(jwt_body, message_type, opts = {})
|
2019-08-31 03:30:34 +08:00
|
|
|
options = { headers: headers(jwt_body, opts) }
|
2019-09-06 02:23:21 +08:00
|
|
|
request(:get, "/api/event_types?message_type=#{message_type}", options)
|
2019-08-31 03:30:34 +08:00
|
|
|
end
|
|
|
|
|
2017-01-20 06:55:44 +08:00
|
|
|
private
|
2019-08-23 00:31:23 +08:00
|
|
|
|
2017-01-20 06:55:44 +08:00
|
|
|
def request(method, endpoint, options = {})
|
2017-01-27 07:29:30 +08:00
|
|
|
Canvas.timeout_protection("live-events-subscription-service-session", raise_on_timeout: true) do
|
|
|
|
HTTParty.send(method, "#{settings["app-host"]}#{endpoint}", options.merge(timeout: 10))
|
|
|
|
end
|
2017-01-20 06:55:44 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def headers(jwt_body, headers = {})
|
Add asymmetric encryption for service tokens
refs FOO-2410
test plan:
- in dynamic_settings.yml, add the following block:
```
store:
canvas:
services-jwt:
# these are all the same JWK but with different kid
# to generate a new key, run the following in a Canvas console:
#
# key = OpenSSL::PKey::RSA.generate(2048)
# key.public_key.to_jwk(kid: Time.now.utc.iso8601).to_json
jwk-past.json: "{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"uX1MpfEMQCBUMcj0sBYI-iFaG5Nodp3C6OlN8uY60fa5zSBd83-iIL3n_qzZ8VCluuTLfB7rrV_tiX727XIEqQ\",\"kid\":\"2018-05-18T22:33:20Z_a\",\"d\":\"pYwR64x-LYFtA13iHIIeEvfPTws50ZutyGfpHN-kIZz3k-xVpun2Hgu0hVKZMxcZJ9DkG8UZPqD-zTDbCmCyLQ\",\"p\":\"6OQ2bi_oY5fE9KfQOcxkmNhxDnIKObKb6TVYqOOz2JM\",\"q\":\"y-UBef95njOrqMAxJH1QPds3ltYWr8QgGgccmcATH1M\",\"dp\":\"Ol_xkL7rZgNFt_lURRiJYpJmDDPjgkDVuafIeFTS4Ic\",\"dq\":\"RtzDY5wXr5TzrwWEztLCpYzfyAuF_PZj1cfs976apsM\",\"qi\":\"XA5wnwIrwe5MwXpaBijZsGhKJoypZProt47aVCtWtPE\"}"
jwk-present.json: "{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"uX1MpfEMQCBUMcj0sBYI-iFaG5Nodp3C6OlN8uY60fa5zSBd83-iIL3n_qzZ8VCluuTLfB7rrV_tiX727XIEqQ\",\"kid\":\"2018-06-18T22:33:20Z_b\",\"d\":\"pYwR64x-LYFtA13iHIIeEvfPTws50ZutyGfpHN-kIZz3k-xVpun2Hgu0hVKZMxcZJ9DkG8UZPqD-zTDbCmCyLQ\",\"p\":\"6OQ2bi_oY5fE9KfQOcxkmNhxDnIKObKb6TVYqOOz2JM\",\"q\":\"y-UBef95njOrqMAxJH1QPds3ltYWr8QgGgccmcATH1M\",\"dp\":\"Ol_xkL7rZgNFt_lURRiJYpJmDDPjgkDVuafIeFTS4Ic\",\"dq\":\"RtzDY5wXr5TzrwWEztLCpYzfyAuF_PZj1cfs976apsM\",\"qi\":\"XA5wnwIrwe5MwXpaBijZsGhKJoypZProt47aVCtWtPE\"}"
jwk-future.json: "{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"uX1MpfEMQCBUMcj0sBYI-iFaG5Nodp3C6OlN8uY60fa5zSBd83-iIL3n_qzZ8VCluuTLfB7rrV_tiX727XIEqQ\",\"kid\":\"2018-07-18T22:33:20Z_c\",\"d\":\"pYwR64x-LYFtA13iHIIeEvfPTws50ZutyGfpHN-kIZz3k-xVpun2Hgu0hVKZMxcZJ9DkG8UZPqD-zTDbCmCyLQ\",\"p\":\"6OQ2bi_oY5fE9KfQOcxkmNhxDnIKObKb6TVYqOOz2JM\",\"q\":\"y-UBef95njOrqMAxJH1QPds3ltYWr8QgGgccmcATH1M\",\"dp\":\"Ol_xkL7rZgNFt_lURRiJYpJmDDPjgkDVuafIeFTS4Ic\",\"dq\":\"RtzDY5wXr5TzrwWEztLCpYzfyAuF_PZj1cfs976apsM\",\"qi\":\"XA5wnwIrwe5MwXpaBijZsGhKJoypZProt47aVCtWtPE\"}"
```
- Ensure /internal/services/jwks loads correctly
- In console, ensure `CanvasSecurity::ServicesJwt.decrypt(Base64.decode64(CanvasSecurity::ServicesJwt.for_user('localhost', User.first)))`
and `CanvasSecurity::ServicesJwt.decrypt(Base64.decode64(CanvasSecurity::ServicesJwt.for_user('localhost', User.first, symmetric: true)))`
both work and produce sensible looking output
Change-Id: I13c6c35cc92ed12d03bf97e89e590614e11c6d47
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/275160
QA-Review: August Thornton <august@instructure.com>
Product-Review: August Thornton <august@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Ethan Vizitei <evizitei@instructure.com>
Reviewed-by: Evan Battaglia <ebattaglia@instructure.com>
2021-10-05 03:20:31 +08:00
|
|
|
token = CanvasSecurity::ServicesJwt.generate(jwt_body, symmetric: true)
|
2017-01-20 06:55:44 +08:00
|
|
|
headers["Authorization"] = "Bearer #{token}"
|
|
|
|
headers
|
|
|
|
end
|
|
|
|
|
|
|
|
def settings
|
2022-01-13 05:28:32 +08:00
|
|
|
DynamicSettings.find("live-events-subscription-service", default_ttl: 5.minutes)
|
2017-01-20 06:55:44 +08:00
|
|
|
end
|
2017-02-09 03:04:05 +08:00
|
|
|
|
|
|
|
def tool_proxy_jwt_body(tool_proxy, options = {})
|
|
|
|
options.merge({
|
|
|
|
sub: "ltiToolProxy:#{tool_proxy.guid}",
|
2017-02-24 04:40:44 +08:00
|
|
|
DeveloperKey: tool_proxy.product_family.developer_key.global_id.to_s,
|
2017-04-08 01:05:31 +08:00
|
|
|
RootAccountId: (tool_proxy.context.global_root_account_id || tool_proxy.context.global_id).to_s,
|
|
|
|
RootAccountUUID: tool_proxy.context.root_account.uuid
|
2017-02-09 03:04:05 +08:00
|
|
|
})
|
|
|
|
end
|
2017-01-20 06:55:44 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|