tool proxy custom tcp and tp authorization code
fixes PLAT-2323 test plan: -attempt to register a TP using the authorization code workflow -it should let you create a tool proxy -attempt to register a TP using the client credentials JWT workflow -it should give you a 401 -attempt to register using a custom Tool Consumer Profile -it should let you there is a problem with using developer keys to register tools since we aren't using a onetime token it no longer requires a admin to kick off the process to install the tool. We will need to do something to address this, and ensure they have permission to install in this context Change-Id: I95ed14a8f818f02dab8340dfde3cc6327c06c793 Reviewed-on: https://gerrit.instructure.com/103267 QA-Review: August Thornton <august@instructure.com> Reviewed-by: Weston Dransfield <wdransfield@instructure.com> Reviewed-by: Andrew Butterfield <abutterfield@instructure.com> Product-Review: Nathan Mills <nathanm@instructure.com> Tested-by: Jenkins
This commit is contained in:
parent
7381e14ab2
commit
03aa58570e
|
@ -67,4 +67,8 @@ module Lti::Ims::AccessTokenHelper
|
|||
raise 'the method #lti2_service_name must be defined in the class'
|
||||
end
|
||||
|
||||
def render_unauthorized
|
||||
render json: {error: 'unauthorized'}, status: :unauthorized
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -42,6 +42,8 @@ module Lti
|
|||
#
|
||||
class AuthorizationController < ApplicationController
|
||||
|
||||
skip_before_action :load_user, :require_user
|
||||
|
||||
SERVICE_DEFINITIONS = [
|
||||
{
|
||||
id: 'vnd.Canvas.authorization',
|
||||
|
@ -53,12 +55,15 @@ module Lti
|
|||
|
||||
class InvalidGrant < RuntimeError; end
|
||||
JWT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer'.freeze
|
||||
AUTHORIZATION_CODE_GRANT_TYPE = 'authorization_code'.freeze
|
||||
GRANT_TYPES = [JWT_GRANT_TYPE, AUTHORIZATION_CODE_GRANT_TYPE].freeze
|
||||
|
||||
rescue_from JSON::JWS::VerificationFailed,
|
||||
JSON::JWT::InvalidFormat,
|
||||
JSON::JWS::UnexpectedAlgorithm,
|
||||
Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
Lti::Oauth2::AuthorizationValidator::SecretNotFound,
|
||||
Lti::Oauth2::AuthorizationValidator::MissingAuthorizationCode,
|
||||
InvalidGrant do
|
||||
render json: {error: 'invalid_grant'}, status: :bad_request
|
||||
end
|
||||
|
@ -67,7 +72,14 @@ module Lti
|
|||
# Returns an access token that can be used to access other LTI services
|
||||
#
|
||||
# @argument grant_type [Required, String]
|
||||
# should contain the exact value of: "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
# When using registration provided credentials it should contain the exact value of:
|
||||
# "urn:ietf:params:oauth:grant-type:jwt-bearer" once a tool proxy is created
|
||||
# When using developer credentials it should have the value of: "authorization_code" and pass
|
||||
# the optional argument `code` defined below
|
||||
#
|
||||
# @argument code [optional, String]
|
||||
# Only used in conjunction with a grant type of "authorization_code". Should contain the "reg_key" from the
|
||||
# registration message
|
||||
#
|
||||
# @argument assertion [Required, AuthorizationJWT]
|
||||
# The AuthorizationJWT here should be the JWT in a string format
|
||||
|
@ -79,12 +91,17 @@ module Lti
|
|||
#
|
||||
# @returns [AccessToken]
|
||||
def authorize
|
||||
raise InvalidGrant if params[:grant_type] != JWT_GRANT_TYPE
|
||||
raise InvalidGrant unless GRANT_TYPES.include?(params[:grant_type])
|
||||
raise InvalidGrant if params[:assertion].blank?
|
||||
jwt_validator = Lti::Oauth2::AuthorizationValidator.new(jwt: params[:assertion], authorization_url: lti_oauth2_authorize_url)
|
||||
code = params[:code]
|
||||
jwt_validator = Lti::Oauth2::AuthorizationValidator.new(
|
||||
jwt: params[:assertion],
|
||||
authorization_url: lti_oauth2_authorize_url,
|
||||
code: code
|
||||
)
|
||||
jwt_validator.validate!
|
||||
render json: {
|
||||
access_token: Lti::Oauth2::AccessToken.create_jwt(aud: request.host, sub: jwt_validator.sub).to_s,
|
||||
access_token: Lti::Oauth2::AccessToken.create_jwt(aud: request.host, sub: jwt_validator.sub, reg_key: code).to_s,
|
||||
token_type: 'bearer',
|
||||
expires_in: Setting.get('lti.oauth2.access_token.expiration', 1.hour.to_s)
|
||||
}
|
||||
|
|
|
@ -56,14 +56,19 @@ module Lti
|
|||
def create
|
||||
if oauth2_request?
|
||||
dev_key = DeveloperKey.find_cached(access_token.sub)
|
||||
render_new_tool_proxy(context, SecureRandom.uuid, dev_key) and return if authorized_lti2_tool
|
||||
begin
|
||||
validate_access_token!
|
||||
reg_key = access_token.reg_key
|
||||
reg_secret = RegistrationRequestService.retrieve_registration_password(context, reg_key) if reg_key
|
||||
render_new_tool_proxy(context, reg_key, dev_key) and return if reg_secret.present?
|
||||
rescue Lti::Oauth2::InvalidTokenError
|
||||
render_unauthorized and return
|
||||
end
|
||||
else
|
||||
tool_proxy_guid = oauth_consumer_key
|
||||
secret = RegistrationRequestService.retrieve_registration_password(context, oauth_consumer_key)
|
||||
render_new_tool_proxy(context, SecureRandom.uuid) and return if secret.present? && oauth_authenticated_request?(secret)
|
||||
render_new_tool_proxy(context, oauth_consumer_key) and return if secret.present? && oauth_authenticated_request?(secret)
|
||||
end
|
||||
|
||||
render json: {error: 'unauthorized'}, status: :unauthorized
|
||||
render_unauthorized
|
||||
end
|
||||
|
||||
def re_reg
|
||||
|
|
|
@ -37,11 +37,13 @@ module Lti
|
|||
|
||||
end
|
||||
|
||||
def process_tool_proxy_json(json:, context:, guid:, tool_proxy_to_update: nil, tc_half_shared_secret: nil, developer_key: nil, tcp_uuid: Lti::ToolConsumerProfile::DEFAULT_TCP_UUID)
|
||||
def process_tool_proxy_json(json:, context:, guid:, tool_proxy_to_update: nil, tc_half_shared_secret: nil, developer_key: nil)
|
||||
@tc_half_secret = tc_half_shared_secret
|
||||
|
||||
tp = IMS::LTI::Models::ToolProxy.new.from_json(json)
|
||||
tp.tool_proxy_guid = guid
|
||||
tcp_uuid = tp.tool_consumer_profile&.match(/tool_consumer_profile\/([a-fA-f0-9\-]+)/)&.captures&.first
|
||||
tcp_uuid ||= developer_key&.tool_consumer_profile&.uuid
|
||||
tcp_uuid ||= Lti::ToolConsumerProfile::DEFAULT_TCP_UUID
|
||||
begin
|
||||
validate_proxy!(tp, context, developer_key, tcp_uuid)
|
||||
rescue Lti::ToolProxyService::InvalidToolProxyError
|
||||
|
|
|
@ -5,10 +5,10 @@ module Lti
|
|||
|
||||
ISS = 'Canvas'.freeze
|
||||
|
||||
attr_reader :aud, :sub
|
||||
attr_reader :aud, :sub, :reg_key
|
||||
|
||||
def self.create_jwt(aud:, sub:)
|
||||
new(aud: aud, sub: sub)
|
||||
def self.create_jwt(aud:, sub:, reg_key: nil)
|
||||
new(aud: aud, sub: sub, reg_key: reg_key)
|
||||
end
|
||||
|
||||
def self.from_jwt(aud:, jwt:)
|
||||
|
@ -20,8 +20,9 @@ module Lti
|
|||
raise InvalidTokenError, e
|
||||
end
|
||||
|
||||
def initialize(aud:, sub:, jwt: nil)
|
||||
def initialize(aud:, sub:, jwt: nil, reg_key: nil)
|
||||
@_jwt = jwt if jwt
|
||||
@reg_key = reg_key || (jwt && decoded_jwt['reg_key'])
|
||||
@aud = aud
|
||||
@sub = sub
|
||||
end
|
||||
|
@ -62,6 +63,7 @@ module Lti
|
|||
nbf: Setting.get('lti.oauth2.access_token.nbf', 30.seconds).to_i.seconds.ago,
|
||||
jti: SecureRandom.uuid
|
||||
}
|
||||
body[:reg_key] = @reg_key if @reg_key
|
||||
Canvas::Security.create_jwt(body)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,10 +9,13 @@ module Lti
|
|||
end
|
||||
class InvalidAuthJwt < StandardError
|
||||
end
|
||||
class MissingAuthorizationCode < StandardError
|
||||
end
|
||||
|
||||
def initialize(jwt:, authorization_url:)
|
||||
def initialize(jwt:, authorization_url:, code: nil)
|
||||
@raw_jwt = jwt
|
||||
@authorization_url = authorization_url
|
||||
@code = code
|
||||
end
|
||||
|
||||
def jwt
|
||||
|
@ -52,9 +55,13 @@ module Lti
|
|||
end
|
||||
|
||||
def developer_key
|
||||
@_developer_key ||= DeveloperKey.find_cached(unverified_jwt[:sub])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
return nil
|
||||
@_developer_key ||= begin
|
||||
dev_key = DeveloperKey.find_cached(unverified_jwt[:sub])
|
||||
raise MissingAuthorizationCode if dev_key && @code.blank?
|
||||
dev_key
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def sub
|
||||
|
|
|
@ -35,7 +35,6 @@ module Lti
|
|||
let(:raw_jwt) do
|
||||
raw_jwt = JSON::JWT.new(
|
||||
{
|
||||
iss: tool_proxy.guid,
|
||||
sub: tool_proxy.guid,
|
||||
aud: lti_oauth2_authorize_url,
|
||||
exp: 1.minute.from_now,
|
||||
|
@ -43,21 +42,21 @@ module Lti
|
|||
jti: SecureRandom.uuid
|
||||
}
|
||||
)
|
||||
raw_jwt.kid = tool_proxy.guid
|
||||
raw_jwt
|
||||
end
|
||||
|
||||
let(:auth_endpoint) { '/api/lti/authorize' }
|
||||
let(:jwt_string) do
|
||||
raw_jwt.sign(tool_proxy.shared_secret, :HS256).to_s
|
||||
end
|
||||
let(:params) do
|
||||
{
|
||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
assertion: jwt_string
|
||||
}
|
||||
end
|
||||
|
||||
describe "POST 'authorize'" do
|
||||
let(:auth_endpoint) { '/api/lti/authorize' }
|
||||
let(:assertion) do
|
||||
raw_jwt.sign(tool_proxy.shared_secret, :HS256).to_s
|
||||
end
|
||||
let(:params) do
|
||||
{
|
||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
assertion: assertion
|
||||
}
|
||||
end
|
||||
|
||||
it 'responds with 200' do
|
||||
post auth_endpoint, params
|
||||
|
@ -101,6 +100,46 @@ module Lti
|
|||
expect(response.body).to eq({error: 'invalid_grant'}.to_json)
|
||||
end
|
||||
|
||||
context "developer credentials" do
|
||||
|
||||
let(:raw_jwt) do
|
||||
raw_jwt = JSON::JWT.new(
|
||||
{
|
||||
sub: developer_key.global_id,
|
||||
aud: lti_oauth2_authorize_url,
|
||||
exp: 1.minute.from_now,
|
||||
iat: Time.zone.now.to_i,
|
||||
jti: SecureRandom.uuid,
|
||||
}
|
||||
)
|
||||
raw_jwt
|
||||
end
|
||||
|
||||
let(:jwt_string) do
|
||||
raw_jwt.sign(developer_key.api_key, :HS256).to_s
|
||||
end
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
grant_type: 'authorization_code',
|
||||
assertion: jwt_string,
|
||||
code: 'reg_key'
|
||||
}
|
||||
end
|
||||
|
||||
it "rejects the request if a reg_key isn't provided and grant_type is auth code" do
|
||||
post auth_endpoint, params.delete(:code)
|
||||
expect(response.code).to eq '400'
|
||||
end
|
||||
|
||||
it "accepts a developer key with a reg key" do
|
||||
post auth_endpoint, params
|
||||
expect(response.code).to eq '200'
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,7 +63,9 @@ module Lti
|
|||
describe "POST #create" do
|
||||
|
||||
before(:each) do
|
||||
OAuth::Signature.stubs(:build).returns(mock(verify: true))
|
||||
mock_oauth_sig = mock('oauth_signature')
|
||||
mock_oauth_sig.stubs(:verify).returns(true)
|
||||
OAuth::Signature.stubs(:build).returns(mock_oauth_sig)
|
||||
OAuth::Helper.stubs(:parse_header).returns({'oauth_consumer_key' => 'key'})
|
||||
Lti::RegistrationRequestService.stubs(:retrieve_registration_password).returns('password')
|
||||
end
|
||||
|
@ -111,18 +113,73 @@ module Lti
|
|||
expect(response).to eq 201
|
||||
expect(JSON.parse(body).keys).to match_array ["@context", "@type", "@id", "tool_proxy_guid", "tc_half_shared_secret"]
|
||||
end
|
||||
|
||||
context "custom tool consumer profile" do
|
||||
let(:account) {Account.create!}
|
||||
let(:dev_key) do
|
||||
dev_key = DeveloperKey.create(api_key: 'test-api-key')
|
||||
DeveloperKey.stubs(:find_cached).returns(dev_key)
|
||||
dev_key
|
||||
end
|
||||
let!(:tcp) do
|
||||
dev_key.create_tool_consumer_profile!(
|
||||
services: Lti::ToolConsumerProfile::RESTRICTED_SERVICES,
|
||||
capabilities: Lti::ToolConsumerProfile::RESTRICTED_CAPABILITIES,
|
||||
uuid: SecureRandom.uuid,
|
||||
developer_key: dev_key
|
||||
)
|
||||
end
|
||||
let(:tcp_url) {polymorphic_url([account, :tool_consumer_profile], tool_consumer_profile_id: tcp.uuid)}
|
||||
let(:access_token) do
|
||||
aud = host rescue (@request || request).host
|
||||
Lti::Oauth2::AccessToken.create_jwt(aud: aud, sub: developer_key.global_id, reg_key: 'reg_key')
|
||||
end
|
||||
let(:request_headers) { {Authorization: "Bearer #{access_token}"} }
|
||||
it 'supports using a specified custom TCP' do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
tool_proxy_fixture = File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))
|
||||
tp = IMS::LTI::Models::ToolProxy.new.from_json(tool_proxy_fixture)
|
||||
message = tp.tool_profile.resource_handlers.first.messages.first
|
||||
tp.tool_consumer_profile = tcp_url
|
||||
message.enabled_capability = *Lti::ToolConsumerProfile::RESTRICTED_CAPABILITIES
|
||||
headers = {'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json'}
|
||||
headers.merge!(request_headers)
|
||||
response = post "/api/lti/accounts/#{@course.account.id}/tool_proxy.json", tp.to_json, headers
|
||||
expect(response).to eq 201
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "POST #create with JWT access token" do
|
||||
let(:access_token) do
|
||||
aud = host rescue (@request || request).host
|
||||
Lti::Oauth2::AccessToken.create_jwt(aud: aud, sub: developer_key.global_id, reg_key: 'reg_key')
|
||||
end
|
||||
let(:request_headers) { {Authorization: "Bearer #{access_token}"} }
|
||||
|
||||
it 'accepts valid JWT access tokens' do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
Lti::RegistrationRequestService.
|
||||
stubs(:retrieve_registration_password).with(@course.account, 'reg_key').returns('password')
|
||||
tool_proxy_fixture = File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))
|
||||
json = JSON.parse(tool_proxy_fixture)
|
||||
json[:format] = 'json'
|
||||
json[:account_id] = @course.account.id
|
||||
response = post "/api/lti/accounts/#{@course.account.id}/tool_proxy.json", tool_proxy_fixture, request_headers
|
||||
expect(response).to eq 201
|
||||
end
|
||||
|
||||
it 'returns a 401 if the reg_key is not valid' do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
tool_proxy_fixture = File.read(File.join(Rails.root, 'spec', 'fixtures', 'lti', 'tool_proxy.json'))
|
||||
json = JSON.parse(tool_proxy_fixture)
|
||||
json[:format] = 'json'
|
||||
json[:account_id] = @course.account.id
|
||||
response = post "/api/lti/accounts/#{@course.account.id}/tool_proxy.json", tool_proxy_fixture, dev_key_request_headers
|
||||
expect(response).to eq 201
|
||||
expect(response).to eq 401
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "POST #reregistration" do
|
||||
|
|
|
@ -81,6 +81,11 @@ module Lti
|
|||
expect(Canvas::Security.decode_jwt(access_token.to_s)['sub']).to eq sub
|
||||
end
|
||||
|
||||
it "includes the reg_key if passed in" do
|
||||
access_token = Lti::Oauth2::AccessToken.create_jwt(aud: aud, sub: sub, reg_key: 'reg_key')
|
||||
expect(Canvas::Security.decode_jwt(access_token.to_s)['reg_key']).to eq('reg_key')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe ".from_jwt" do
|
||||
|
|
|
@ -4,18 +4,14 @@ module Lti
|
|||
module Oauth2
|
||||
describe AuthorizationValidator do
|
||||
|
||||
let(:developer_key) do
|
||||
developer_key_mock = mock("developer_key")
|
||||
developer_key_mock.stubs(:active?).returns(true)
|
||||
developer_key_mock
|
||||
end
|
||||
|
||||
let(:product_family) do
|
||||
product_family_mock = mock("product_family")
|
||||
product_family_mock.stubs(:developer_key).returns(developer_key)
|
||||
product_family_mock.stubs(:developer_key).returns(dev_key)
|
||||
product_family_mock
|
||||
end
|
||||
|
||||
let(:account) { Account.create! }
|
||||
|
||||
let(:tool_proxy) do
|
||||
tool_proxy_mock = mock("tool_proxy")
|
||||
tool_proxy_mock.stubs(:guid).returns("3b7f3b02-b481-4f63-a6b0-129dee85abee")
|
||||
|
@ -40,7 +36,7 @@ module Lti
|
|||
)
|
||||
raw_jwt
|
||||
end
|
||||
let(:dev_key){ DeveloperKey.create! }
|
||||
let(:dev_key) { DeveloperKey.create! }
|
||||
let(:raw_jwt_dev_key) do
|
||||
raw_jwt = JSON::JWT.new(
|
||||
{
|
||||
|
@ -54,7 +50,7 @@ module Lti
|
|||
raw_jwt
|
||||
end
|
||||
|
||||
let(:authValidator) do
|
||||
let(:auth_validator) do
|
||||
AuthorizationValidator.new(
|
||||
jwt: raw_jwt.sign(tool_proxy.shared_secret, :HS256).to_s,
|
||||
authorization_url: auth_url
|
||||
|
@ -69,13 +65,13 @@ module Lti
|
|||
describe "#jwt" do
|
||||
|
||||
it "returns the decoded JWT" do
|
||||
expect(authValidator.jwt.signature).to eq raw_jwt.sign(tool_proxy.shared_secret, :HS256).signature
|
||||
expect(auth_validator.jwt.signature).to eq raw_jwt.sign(tool_proxy.shared_secret, :HS256).signature
|
||||
end
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if any of the assertions are missing" do
|
||||
raw_jwt.delete 'exp'
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the following assertions are missing: exp"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the following assertions are missing: exp"
|
||||
end
|
||||
|
||||
it 'raises JSON::JWT:InvalidFormat if the JWT format is invalid' do
|
||||
|
@ -97,32 +93,32 @@ module Lti
|
|||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the 'exp' is to far in the future" do
|
||||
raw_jwt['exp'] = 5.minutes.from_now.to_i
|
||||
Setting.set('lti.oauth2.authorize.max.expiration', 1.minute.to_i)
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'exp' must not be any further than #{60.seconds} seconds in the future"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'exp' must not be any further than #{60.seconds} seconds in the future"
|
||||
end
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the 'exp' is in the past" do
|
||||
raw_jwt['exp'] = 5.minutes.ago.to_i
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt, "the JWT has expired"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt, "the JWT has expired"
|
||||
end
|
||||
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the 'iat' to old" do
|
||||
raw_jwt['iat'] = 10.minutes.ago.to_i
|
||||
Setting.set('lti.oauth2.authorize.max_iat_age', 5.minutes.to_s)
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'iat' must be less than #{5.minutes} seconds old"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'iat' must be less than #{5.minutes} seconds old"
|
||||
end
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the 'iat' is in the future" do
|
||||
raw_jwt['iat'] = 10.minutes.from_now.to_i
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'iat' must not be in the future"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'iat' must not be in the future"
|
||||
end
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the 'jti' has already been used" do
|
||||
enable_cache do
|
||||
authValidator.jwt
|
||||
auth_validator.jwt
|
||||
duplicate_jwt = AuthorizationValidator.new(
|
||||
jwt: raw_jwt.sign(tool_proxy.shared_secret, :HS256).to_s,
|
||||
authorization_url: auth_url
|
||||
|
@ -134,8 +130,8 @@ module Lti
|
|||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the 'aud' is not the authorization endpoint" do
|
||||
raw_jwt['aud'] = 'http://google.com/invalid'
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'aud' must be the LTI Authorization endpoint"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the 'aud' must be the LTI Authorization endpoint"
|
||||
end
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::SecretNotFound if no ToolProxy or developer key" do
|
||||
|
@ -148,30 +144,38 @@ module Lti
|
|||
end
|
||||
|
||||
context "JWT signed with dev key" do
|
||||
let(:authValidator) do
|
||||
AuthorizationValidator.new(
|
||||
jwt: raw_jwt_dev_key.sign(dev_key.api_key, :HS256).to_s,
|
||||
authorization_url: auth_url
|
||||
)
|
||||
end
|
||||
let(:auth_validator) do
|
||||
AuthorizationValidator.new(
|
||||
jwt: raw_jwt_dev_key.sign(dev_key.api_key, :HS256).to_s,
|
||||
authorization_url: auth_url,
|
||||
code: 'reg_key'
|
||||
)
|
||||
end
|
||||
|
||||
it "returns the decoded JWT" do
|
||||
expect(authValidator.jwt.signature).to eq raw_jwt_dev_key.sign(dev_key.api_key, :HS256).signature
|
||||
end
|
||||
it 'throws an exception if no code is provided' do
|
||||
auth_validator = AuthorizationValidator.new(
|
||||
jwt: raw_jwt_dev_key.sign(dev_key.api_key, :HS256).to_s,
|
||||
authorization_url: auth_url)
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::MissingAuthorizationCode
|
||||
end
|
||||
|
||||
it "returns the decoded JWT" do
|
||||
expect(auth_validator.jwt.signature).to eq raw_jwt_dev_key.sign(dev_key.api_key, :HS256).signature
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#developer_key" do
|
||||
let(:authValidator) do
|
||||
let(:auth_validator) do
|
||||
AuthorizationValidator.new(
|
||||
jwt: raw_jwt_dev_key.sign(dev_key.api_key, :HS256).to_s,
|
||||
authorization_url: auth_url
|
||||
authorization_url: auth_url,
|
||||
code: '123'
|
||||
)
|
||||
end
|
||||
|
||||
it 'gets the correct developer key' do
|
||||
expect(authValidator.developer_key).to eq dev_key
|
||||
expect(auth_validator.developer_key).to eq dev_key
|
||||
end
|
||||
|
||||
it 'returns nil if developer key not found' do
|
||||
|
@ -195,7 +199,8 @@ module Lti
|
|||
it 'returns the developer key global id if dev key is present' do
|
||||
validator = AuthorizationValidator.new(
|
||||
jwt: raw_jwt_dev_key.sign(dev_key.api_key, :HS256).to_s,
|
||||
authorization_url: auth_url
|
||||
authorization_url: auth_url,
|
||||
code: '123'
|
||||
)
|
||||
expect(validator.sub).to eq dev_key.global_id
|
||||
end
|
||||
|
@ -204,30 +209,30 @@ module Lti
|
|||
describe "#tool_proxy" do
|
||||
|
||||
it 'returns the tool_proxy from the uuid specified in the sub' do
|
||||
expect(authValidator.tool_proxy).to eq tool_proxy
|
||||
expect(auth_validator.tool_proxy).to eq tool_proxy
|
||||
end
|
||||
|
||||
it "raises Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt if the Tool Proxy is not using a split secret" do
|
||||
tool_proxy.stubs(:raw_data).returns({'enabled_capability' => []})
|
||||
expect { authValidator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the Tool Proxy must be using a split secret"
|
||||
expect { auth_validator.jwt }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the Tool Proxy must be using a split secret"
|
||||
end
|
||||
|
||||
it "accepts OAuth.splitSecret capability for backwards compatability" do
|
||||
tool_proxy.stubs(:raw_data).returns({'enabled_capability' => ['OAuth.splitSecret']})
|
||||
expect(authValidator.tool_proxy).to eq tool_proxy
|
||||
expect(auth_validator.tool_proxy).to eq tool_proxy
|
||||
end
|
||||
|
||||
it "requires an associated developer_key on the product_family" do
|
||||
product_family.stubs(:developer_key).returns nil
|
||||
expect{authValidator.tool_proxy}.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the Tool Proxy must be associated to a developer key"
|
||||
expect { auth_validator.tool_proxy }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the Tool Proxy must be associated to a developer key"
|
||||
end
|
||||
|
||||
it "requires an associated developer_key on the product_family" do
|
||||
developer_key.stubs(:active?).returns false
|
||||
expect{authValidator.tool_proxy}.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the Developer Key is not active"
|
||||
dev_key.stubs(:active?).returns false
|
||||
expect { auth_validator.tool_proxy }.to raise_error Lti::Oauth2::AuthorizationValidator::InvalidAuthJwt,
|
||||
"the Developer Key is not active"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue