add lti-advantage gem
refs PLAT-3500 Change-Id: I72c7166bd58053017fba0cca0c4be271de4a2b45 Reviewed-on: https://gerrit.instructure.com/157551 Tested-by: Jenkins Reviewed-by: Marc Alan Phillips <mphillips@instructure.com> Product-Review: Marc Alan Phillips <mphillips@instructure.com> QA-Review: Marc Alan Phillips <mphillips@instructure.com>
This commit is contained in:
parent
77f4603177
commit
f1c4c90bac
|
@ -0,0 +1,13 @@
|
|||
/.bundle/
|
||||
/.yardoc
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
|
||||
Gemfile.lock
|
|
@ -0,0 +1,3 @@
|
|||
--format documentation
|
||||
--color
|
||||
--require spec_helper
|
|
@ -0,0 +1,6 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
||||
|
||||
# Specify your gem's dependencies in ims-lti.gemspec
|
||||
gemspec
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Nathan Mills
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,39 @@
|
|||
# Ims::Lti
|
||||
|
||||
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ims/lti`. To experiment with that code, run `bin/console` for an interactive prompt.
|
||||
|
||||
TODO: Delete this and the text above, and describe your gem
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'ims-lti'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install ims-lti
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Write usage instructions here
|
||||
|
||||
## Development
|
||||
|
||||
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||
|
||||
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ims-lti.
|
||||
|
||||
## License
|
||||
|
||||
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@ -0,0 +1,6 @@
|
|||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
|
@ -0,0 +1,6 @@
|
|||
module LtiAdvantage
|
||||
require_relative 'lti_advantage/type_validator'
|
||||
require_relative 'lti_advantage/claims'
|
||||
require_relative 'lti_advantage/messages'
|
||||
require_relative 'lti_advantage/version'
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
module LtiAdvantage::Claims
|
||||
require_relative 'claims/context'
|
||||
require_relative 'claims/launch_presentation'
|
||||
require_relative 'claims/lis'
|
||||
require_relative 'claims/platform'
|
||||
require_relative 'claims/resource_link'
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
require 'active_model'
|
||||
|
||||
module LtiAdvantage::Claims
|
||||
# Class represeting an LTI 1.3 message "context" claim.
|
||||
# https://purl.imsglobal.org/spec/lti/claim/context
|
||||
class Context
|
||||
include ActiveModel::Model
|
||||
attr_accessor :id, :label, :title, :type
|
||||
validates_presence_of :id
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
require 'active_model'
|
||||
|
||||
module LtiAdvantage::Claims
|
||||
# Class represeting an LTI 1.3 message "launch_presentation" claim.
|
||||
# https://purl.imsglobal.org/spec/lti/claim/launch_presentation
|
||||
class LaunchPresentation
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :document_target, :height, :locale, :return_url, :width
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
require 'active_model'
|
||||
|
||||
module LtiAdvantage::Claims
|
||||
# Class represeting an LTI 1.3 message "lis" claim.
|
||||
# https://purl.imsglobal.org/spec/lti/claim/lis
|
||||
class Lis
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :course_offering_sourcedid, :course_section_sourcedid, :person_sourcedid
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
require 'active_model'
|
||||
|
||||
module LtiAdvantage::Claims
|
||||
# Class represeting an LTI 1.3 message "tool_platform" claim.
|
||||
# http://purl.imsglobal.org/lti/claim/tool_platform
|
||||
class Platform
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :contact_email,
|
||||
:description,
|
||||
:guid,
|
||||
:name,
|
||||
:product_family_code,
|
||||
:url,
|
||||
:version
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
require 'active_model'
|
||||
|
||||
module LtiAdvantage::Claims
|
||||
# Class represeting an LTI 1.3 message "resource_link" claim.
|
||||
# https://purl.imsglobal.org/spec/lti/claim/resource_link
|
||||
class ResourceLink
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :description, :id, :title
|
||||
validates_presence_of :id
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
module LtiAdvantage
|
||||
module Messages
|
||||
require_relative 'messages/jwt_message'
|
||||
require_relative 'messages/resource_link_request'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,89 @@
|
|||
module LtiAdvantage::Messages
|
||||
# Abstract base class for all LTI 1.3 JWT message types
|
||||
class JwtMessage
|
||||
include ActiveModel::Model
|
||||
|
||||
REQUIRED_CLAIMS = %i[
|
||||
aud
|
||||
azp
|
||||
deployment_id
|
||||
exp
|
||||
iat
|
||||
iss
|
||||
message_type
|
||||
nonce
|
||||
sub
|
||||
version
|
||||
].freeze
|
||||
|
||||
TYPED_ATTRIBUTES = {
|
||||
aud: Array,
|
||||
context: LtiAdvantage::Claims::Context,
|
||||
custom: Hash,
|
||||
extensions: Hash,
|
||||
launch_presentation: LtiAdvantage::Claims::LaunchPresentation,
|
||||
lis: LtiAdvantage::Claims::Lis,
|
||||
tool_platform: LtiAdvantage::Claims::Platform,
|
||||
roles: Array,
|
||||
role_scope_mentor: Array
|
||||
}.freeze
|
||||
|
||||
attr_accessor *REQUIRED_CLAIMS
|
||||
attr_accessor :address,
|
||||
:birthdate,
|
||||
:context,
|
||||
:custom,
|
||||
:email,
|
||||
:email_verified,
|
||||
:extensions,
|
||||
:family_name,
|
||||
:gender,
|
||||
:given_name,
|
||||
:launch_presentation,
|
||||
:lis,
|
||||
:locale,
|
||||
:middle_name,
|
||||
:name,
|
||||
:nickname,
|
||||
:phone_number,
|
||||
:phone_number_verified,
|
||||
:picture,
|
||||
:tool_platform,
|
||||
:preferred_username,
|
||||
:profile,
|
||||
:roles,
|
||||
:role_scope_mentor,
|
||||
:updated_at,
|
||||
:website,
|
||||
:zoneinfo
|
||||
|
||||
def context
|
||||
@context ||= TYPED_ATTRIBUTES[:context].new
|
||||
end
|
||||
|
||||
def extensions
|
||||
@extensions ||= TYPED_ATTRIBUTES[:extensions].new
|
||||
end
|
||||
|
||||
def launch_presentation
|
||||
@launch_presentation ||= TYPED_ATTRIBUTES[:launch_presentation].new
|
||||
end
|
||||
|
||||
def lis
|
||||
@lis ||= TYPED_ATTRIBUTES[:lis].new
|
||||
end
|
||||
|
||||
def roles
|
||||
@roles ||= TYPED_ATTRIBUTES[:roles].new
|
||||
end
|
||||
|
||||
def role_scope_mentor
|
||||
@role_scope_mentor ||= TYPED_ATTRIBUTES[:role_scope_mentor].new
|
||||
end
|
||||
|
||||
def tool_platform
|
||||
@tool_platform ||= TYPED_ATTRIBUTES[:tool_platform].new
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
module LtiAdvantage::Messages
|
||||
# Class represeting an LTI 1.3 LtiResourceLinkRequest.
|
||||
class ResourceLinkRequest < JwtMessage
|
||||
# Required claims for this message type
|
||||
REQUIRED_CLAIMS = superclass::REQUIRED_CLAIMS + %i[
|
||||
resource_link
|
||||
].freeze
|
||||
|
||||
# Claims to type check
|
||||
TYPED_ATTRIBUTES = superclass::TYPED_ATTRIBUTES.merge(
|
||||
resource_link: LtiAdvantage::Claims::ResourceLink
|
||||
)
|
||||
|
||||
attr_accessor *REQUIRED_CLAIMS
|
||||
|
||||
validates_presence_of *REQUIRED_CLAIMS
|
||||
validates_with LtiAdvantage::TypeValidator
|
||||
|
||||
# Returns a new instance of LtiResourceLinkRequest.
|
||||
#
|
||||
# @param [Hash] attributes for message initialization.
|
||||
# @return [LtiResourceLinkRequest]
|
||||
def initialize(params = {})
|
||||
self.message_type = "LtiResourceLinkRequest"
|
||||
self.version = "1.3.0"
|
||||
super
|
||||
end
|
||||
|
||||
def resource_link
|
||||
@resource_link ||= TYPED_ATTRIBUTES[:resource_link].new
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
require 'active_model'
|
||||
|
||||
module LtiAdvantage
|
||||
class TypeValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
record.instance_variables.each do |v|
|
||||
value = record.instance_variable_get(v)
|
||||
attr = v.to_s[1..-1].to_sym
|
||||
|
||||
# verify the value is of the correct type
|
||||
validate_type(attr, value, record)
|
||||
|
||||
# verify the value itself is valid
|
||||
validate_nested_models(attr, value, record)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_type(attr, value, record)
|
||||
expected_type = record.class::TYPED_ATTRIBUTES[attr]
|
||||
return if value.nil? || expected_type.nil?
|
||||
return if value.instance_of? expected_type
|
||||
record.errors.add(attr, "#{attr} must be an intance of #{expected_type}")
|
||||
end
|
||||
|
||||
def validate_nested_models(attr, value, record)
|
||||
return validate_nested_array(attr, value, record) if value.instance_of? Array
|
||||
return unless value.respond_to?(:invalid?)
|
||||
record.errors.add(attr, value.errors.messages) if value.invalid?
|
||||
end
|
||||
|
||||
def validate_nested_array(attr, value, record)
|
||||
value.each { |v| validate_nested_models(attr, v, record) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
module LtiAdvantage
|
||||
VERSION = "0.1.0"
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
lib = File.expand_path("../lib", __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require "lti_advantage/version"
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "lti-advantage"
|
||||
spec.version = LtiAdvantage::VERSION
|
||||
spec.authors = ["Instructure"]
|
||||
spec.email = ["opensource@instructure.com"]
|
||||
|
||||
spec.summary = %q{Ruby library for creating IMS LTI tool providers and consumers}
|
||||
spec.homepage = "http://github.com/instructure/lti-advantage"
|
||||
spec.license = "MIT"
|
||||
|
||||
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
||||
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
||||
if spec.respond_to?(:metadata)
|
||||
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
||||
else
|
||||
raise "RubyGems 2.0 or newer is required to protect against " \
|
||||
"public gem pushes."
|
||||
end
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
||||
f.match(%r{^(test|spec|features)/})
|
||||
end
|
||||
spec.bindir = "exe"
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_runtime_dependency 'json-jwt', '~> 1.5'
|
||||
spec.add_runtime_dependency "activemodel", "~> 5.2"
|
||||
|
||||
spec.add_development_dependency "redcarpet"
|
||||
spec.add_development_dependency "bundler", "~> 1.16"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "rspec", "~> 3.0"
|
||||
end
|
|
@ -0,0 +1,154 @@
|
|||
module LtiAdvantage::Messages
|
||||
RSpec.describe ResourceLinkRequest do
|
||||
let(:message) { ResourceLinkRequest.new }
|
||||
let(:valid_message) do
|
||||
ResourceLinkRequest.new(
|
||||
aud: ['129aeb8c-a267-4551-bb5f-e6fc308fcecf'],
|
||||
azp: '163440e5-1c75-4c28-a07c-43e8a9cd3110',
|
||||
sub: '7da708b6-b6cf-483b-b899-11831c685b6f',
|
||||
deployment_id: 'ee493d2e-9f2e-4eca-b2a0-122413887caa',
|
||||
iat: 1529681618,
|
||||
exp: 1529681634,
|
||||
iss: 'https://platform.example.edu',
|
||||
nonce: '5a234202-6f0e-413d-8793-809db7a95930',
|
||||
resource_link: LtiAdvantage::Claims::ResourceLink.new(id: 1),
|
||||
roles: ['foo']
|
||||
)
|
||||
end
|
||||
|
||||
describe 'initializer' do
|
||||
it 'defaults "message_type" to "LtiResourceLinkRequest' do
|
||||
expect(message.message_type).to eq 'LtiResourceLinkRequest'
|
||||
end
|
||||
|
||||
it 'defaults "version" to "1.3.0' do
|
||||
expect(message.version).to eq '1.3.0'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'attributes' do
|
||||
it 'initializes the context when it is referenced' do
|
||||
message.context.id = 23
|
||||
expect(message.context.id).to eq 23
|
||||
end
|
||||
|
||||
it 'initializes "resource_link" when it is referenced' do
|
||||
message.resource_link.id = 23
|
||||
expect(message.resource_link.id).to eq 23
|
||||
end
|
||||
|
||||
it 'initalizes "launch_presentation" when it is referenced' do
|
||||
message.launch_presentation.width = 100
|
||||
expect(message.launch_presentation.width).to eq 100
|
||||
end
|
||||
|
||||
it 'initalizes "tool_platform" when it is referenced' do
|
||||
message.tool_platform.name = 'foo'
|
||||
expect(message.tool_platform.name).to eq 'foo'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it 'is not valid if required claims are missing' do
|
||||
expect(message).to be_invalid
|
||||
end
|
||||
|
||||
it 'is valid if all required claims are present' do
|
||||
expect(valid_message).to be_valid
|
||||
end
|
||||
|
||||
it 'validates sub claims' do
|
||||
message = ResourceLinkRequest.new(
|
||||
aud: ['129aeb8c-a267-4551-bb5f-e6fc308fcecf'],
|
||||
azp: '163440e5-1c75-4c28-a07c-43e8a9cd3110',
|
||||
sub: '7da708b6-b6cf-483b-b899-11831c685b6f',
|
||||
deployment_id: 'ee493d2e-9f2e-4eca-b2a0-122413887caa',
|
||||
iat: 1529681618,
|
||||
exp: 1529681634,
|
||||
iss: 'https://platform.example.edu',
|
||||
nonce: '5a234202-6f0e-413d-8793-809db7a95930',
|
||||
resource_link: LtiAdvantage::Claims::ResourceLink.new(id: 1),
|
||||
roles: ['foo'],
|
||||
context: LtiAdvantage::Claims::Context.new
|
||||
)
|
||||
message.validate
|
||||
expect(message.errors.messages[:context]).to match_array [
|
||||
{ id: ["can't be blank"] }
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "aud" is an array' do
|
||||
message.aud = 'invalid-claim'
|
||||
message.validate
|
||||
expect(message.errors.messages[:aud]).to match_array [
|
||||
'aud must be an intance of Array'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "extensions" is an array' do
|
||||
message.extensions = 'invalid-claim'
|
||||
message.validate
|
||||
expect(message.errors.messages[:extensions]).to match_array [
|
||||
'extensions must be an intance of Hash'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "roles" is an array' do
|
||||
message.roles = 'invalid-claim'
|
||||
message.validate
|
||||
expect(message.errors.messages[:roles]).to match_array [
|
||||
'roles must be an intance of Array'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "role_scope_mentor" is an array' do
|
||||
message.role_scope_mentor = 'invalid-claim'
|
||||
message.validate
|
||||
expect(message.errors.messages[:role_scope_mentor]).to match_array [
|
||||
'role_scope_mentor must be an intance of Array'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "context" is a Context' do
|
||||
message.context = 'foo'
|
||||
message.validate
|
||||
expect(message.errors.messages[:context]).to match_array [
|
||||
'context must be an intance of LtiAdvantage::Claims::Context'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "launch_presentation" is a LaunchPresentation' do
|
||||
message.launch_presentation = 'foo'
|
||||
message.validate
|
||||
expect(message.errors.messages[:launch_presentation]).to match_array [
|
||||
'launch_presentation must be an intance of LtiAdvantage::Claims::LaunchPresentation'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "lis" is an Lis' do
|
||||
message.lis = 'foo'
|
||||
message.validate
|
||||
expect(message.errors.messages[:lis]).to match_array [
|
||||
'lis must be an intance of LtiAdvantage::Claims::Lis'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "tool_platform" is an Platform' do
|
||||
message.tool_platform = 'foo'
|
||||
message.validate
|
||||
expect(message.errors.messages[:tool_platform]).to match_array [
|
||||
'tool_platform must be an intance of LtiAdvantage::Claims::Platform'
|
||||
]
|
||||
end
|
||||
|
||||
it 'verifies that "resource_link" is an Platform' do
|
||||
message.resource_link = 'foo'
|
||||
message.validate
|
||||
expect(message.errors.messages[:resource_link]).to match_array [
|
||||
'resource_link must be an intance of LtiAdvantage::Claims::ResourceLink'
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
RSpec.describe LtiAdvantage do
|
||||
it "has a version number" do
|
||||
expect(LtiAdvantage::VERSION).not_to be nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
require "bundler/setup"
|
||||
require "lti_advantage"
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Enable flags like --only-failures and --next-failure
|
||||
config.example_status_persistence_file_path = ".rspec_status"
|
||||
|
||||
# Disable RSpec exposing methods globally on `Module` and `main`
|
||||
config.disable_monkey_patching!
|
||||
|
||||
config.expect_with :rspec do |c|
|
||||
c.syntax = :expect
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue