Code Monkey home page Code Monkey logo

ims-lti's People

Contributors

ardena avatar augiethornton avatar bracken avatar brodock avatar ccutrer avatar defektive avatar derigible avatar ebrohman avatar evanbattaglia avatar jbasdf avatar jfederico avatar lucaspiffer avatar osdamv avatar rhombl4 avatar rivernate avatar simonista avatar slaughter550 avatar tpickett66 avatar tucker-m avatar wbhumphrey avatar westonkd avatar whitmer avatar zachpendleton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ims-lti's Issues

Add `rexml` as explicit dependency

Starting with Ruby 3.0, rexml is no longer a default gem, so it should be explicitly listed as a dependency to ensure it's installed. It is required in lib/ims/lti.rb; this may fail with a LoadError when run on Ruby 3.

This affects multiple branches of this gem, so ideally it should be backported to the 1.2 series as well as master.

How do I manage outcomes?

First of all, thanks for this gem. So far, I have successfully used this gem to launch a tool from a tool provider and to create user accounts and/or login in users at the same time.

The problem I'm having is that I can't figure out how to manage outcomes, I'd like to be able to make a request for the grades of a specific student, I'm not sure how to go about this.

Any help would be much appreciated.

How to use this gem?

Are there any examples of how to handle the registration process and how to pass data back to the consumer after launch?

LoadError in Controller#index / Cannot load such file -> ims/lit

Hi Guys,

I'm developing in a Rails 4.0 / Ruby 2 environment - and encountered s the above issue when using the following 'require' statement in my consumer code (as per your wiki guidance):

require 'ims/lit'

To resolve this issue, I modified the require statement to

require 'ims'

This is just a heads up more than anything... maybe adding a note to the wiki page might be useful. Many thanks for developing this gem, really helped fastrack my LTI implementation knowledge.

Mark

admin role detection

I think it would be pretty simple to add:

def admin?
  has_role?("urn:lti:instrole:ims/lis/administrator")
end

To detect the admin user. I wouldn't even complain if the roles returned followed a pattern, but that urn is a bit unwieldly!

Word 'instructor' not capitalized in RoleChecks module

This is minor and doesn't affect the RoleChecks because you downcase the strings anyway, but for the sake of consistency the word instructor should be capitalized here in the module. The 'Instructor' role, according to the IMS vocabulary here, starts with a capital letter.

Need to send scores back to LMS from teacher end

Hi I am using this gem for my ruby based application ,
And I want to send the scores back to the LMS , but the problem is in my platform a teacher can grade the assignments now I want to send the scores back to the LMS when the teacher view the application from inside LMS,

say @teacher_loogedin.student_1_grade is the grade of the student when teacher is logged in his/her account inside LMS using my LTI provider tool , now I want to send back the score to the LMS and LMS should record the score on behalf of the student 1 , can I do this ?

I have written a function to accomplish this but here score is only the parameter , how to use a student specific parameter like student_email so that the score is reflected for that particular student only.

here is my function, but how to achieve my desired goal ??

def submitscore
    @tp = IMS::LTI::ToolProvider.new(@@launch_params[:oauth_consumer_key],
    Rails.configuration.lti_settings[@@launch_params[:oauth_consumer_key]],
    @@launch_params)
    # add extension
    @tp.extend IMS::LTI::Extensions::OutcomeData::ToolProvider

    if [email protected]_service?
      @message = "This tool wasn't lunched as an outcome service"
      puts "This tool wasn't lunched as an outcome service"
      render(:launch_error)
    end

    res = @tp.post_extended_replace_result!(score: params[:result]) # here I want to pass a parameter of student email such that the corresponding student can be graded

    if res.success?
      puts "Score Submitted"
    else
      puts "Error during score submission"
    end
    redirect_to @@launch_params[:launch_presentation_return_url]
  end

my problem is also similar to this but I need to solve this using this gem , please help me

OAuth Still Failing with gem version 2.2.1

Thanks for you help with getting me on the 2.0 version of the gem. My LTI tool provider launches are still failing though on 2.2.1, and this time they don't clear even with a reload of page, so in some ways it's a step back, but I have a feeling something else is going on. I'm creating an authenticator thusly:

authenticator = IMS::LTI::Services::MessageAuthenticator.new(@request.url, @params, secret)
authenticator.valid_signature?

That still returns false.

Here is a dump of the console output for the request, params, and validator that may help, hopefully:

`App 9783 stdout: --- OAUTH Key ====> ocill-lti-key
App 9783 stdout: --- OAUTH Secret ====> REDACTED!
App 9783 stdout: --- REQUEST URL https://lrc-tesuto.lrc.lsa.umich.edu/ocill/launch/create
App 9783 stdout: --- REQUEST PARAMS {"oauth_consumer_key"=>"ocill-lti-key", "oauth_signature_method"=>"HMAC-SHA1", "oauth_timestamp"=>"1510067228", "oauth_nonce"=>"70h3qDQemquvfZeKZ03OIJt5Me06hmoUuA4DEscIgQ", "oauth_version"=>"1.0", "context_id"=>"de25c863939f3cacfc02480738d072d95597842c", "context_label"=>"johnathb Sandbox", "context_title"=>"A Canvas training course for johnathb", "custom_canvas_assignment_points_possible"=>"10", "custom_canvas_assignment_title"=>"OCILL Staging Test", "custom_canvas_enrollment_state"=>"active", "ext_ims_lis_basic_outcome_url"=>"https://umich.instructure.com/api/lti/v1/tools/10530/ext_grade_passback", "ext_lti_assignment_id"=>"f9139721-be1f-473a-91b5-6a08a386074c", "ext_outcome_data_values_accepted"=>"url,text", "ext_outcome_result_total_score_accepted"=>"true", "ext_outcomes_tool_placement_url"=>"https://umich.instructure.com/api/lti/v1/turnitin/outcomes_placement/10530", "ext_roles"=>"urn:lti:instrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Instructor,urn:lti:instrole:ims/lis/Student,urn:lti:role:ims/lis/Instructor,urn:lti:sysrole:ims/lis/User", "launch_presentation_document_target"=>"iframe", "launch_presentation_locale"=>"en", "launch_presentation_return_url"=>"https://umich.instructure.com/courses/56125/external_content/success/external_tool_redirect", "lis_outcome_service_url"=>"https://umich.instructure.com/api/lti/v1/tools/10530/grade_passback", "lti_message_type"=>"basic-lti-launch-request", "lti_version"=>"LTI-1p0", "oauth_callback"=>"about:blank", "resource_link_id"=>"52e0499f9ed929340aec886dd08c7035e45471f5", "resource_link_title"=>"OCILL Staging Test", "roles"=>"Instructor", "tool_consumer_info_product_family_code"=>"canvas", "tool_consumer_info_version"=>"cloud", "tool_consumer_instance_contact_email"=>"[email protected]", "tool_consumer_instance_guid"=>"7db438071375c02373713c12c73869ff2f470b68.umich.instructure.com", "tool_consumer_instance_name"=>"University of Michigan - Ann Arbor", "user_id"=>"1ec00186b63b783a994fd2d5b7648ba6cdfe9807", "oauth_signature"=>"7jq4lBIBYoMzIeShPZbPNJjOrEs=", "controller"=>"launch", "action"=>"create"}

App 9783 stdout: --- AUTHENTICATOR INSPECT ===> #<IMS::LTI::Services::MessageAuthenticator:0x007fd77b5535c0 @launch_url="https://lrc-tesuto.lrc.lsa.umich.edu/ocill/launch/create", @params={"oauth_consumer_key"=>"ocill-lti-key", "oauth_signature_method"=>"HMAC-SHA1", "oauth_timestamp"=>"1510067228", "oauth_nonce"=>"70h3qDQemquvfZeKZ03OIJt5Me06hmoUuA4DEscIgQ", "oauth_version"=>"1.0", "context_id"=>"de25c863939f3cacfc02480738d072d95597842c", "context_label"=>"johnathb Sandbox", "context_title"=>"A Canvas training course for johnathb", "custom_canvas_assignment_points_possible"=>"10", "custom_canvas_assignment_title"=>"OCILL Staging Test", "custom_canvas_enrollment_state"=>"active", "ext_ims_lis_basic_outcome_url"=>"https://umich.instructure.com/api/lti/v1/tools/10530/ext_grade_passback", "ext_lti_assignment_id"=>"f9139721-be1f-473a-91b5-6a08a386074c", "ext_outcome_data_values_accepted"=>"url,text", "ext_outcome_result_total_score_accepted"=>"true", "ext_outcomes_tool_placement_url"=>"https://umich.instructure.com/api/lti/v1/turnitin/outcomes_placement/10530", "ext_roles"=>"urn:lti:instrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Instructor,urn:lti:instrole:ims/lis/Student,urn:lti:role:ims/lis/Instructor,urn:lti:sysrole:ims/lis/User", "launch_presentation_document_target"=>"iframe", "launch_presentation_locale"=>"en", "launch_presentation_return_url"=>"https://umich.instructure.com/courses/56125/external_content/success/external_tool_redirect", "lis_outcome_service_url"=>"https://umich.instructure.com/api/lti/v1/tools/10530/grade_passback", "lti_message_type"=>"basic-lti-launch-request", "lti_version"=>"LTI-1p0", "oauth_callback"=>"about:blank", "resource_link_id"=>"52e0499f9ed929340aec886dd08c7035e45471f5", "resource_link_title"=>"OCILL Staging Test", "roles"=>"Instructor", "tool_consumer_info_product_family_code"=>"canvas", "tool_consumer_info_version"=>"cloud", "tool_consumer_instance_contact_email"=>"[email protected]", "tool_consumer_instance_guid"=>"7db438071375c02373713c12c73869ff2f470b68.umich.instructure.com", "tool_consumer_instance_name"=>"University of Michigan - Ann Arbor", "user_id"=>"1ec00186b63b783a994fd2d5b7648ba6cdfe9807", "oauth_signature"=>"7jq4lBIBYoMzIeShPZbPNJjOrEs=", "controller"=>"launch", "action"=>"create"}, @options={:consumer_key=>"ocill-lti-key", :signature_method=>"HMAC-SHA1", :timestamp=>"1510067228", :nonce=>"70h3qDQemquvfZeKZ03OIJt5Me06hmoUuA4DEscIgQ", :version=>"1.0", :callback=>"about:blank"}, @parsed_params={:context_id=>"de25c863939f3cacfc02480738d072d95597842c", :context_label=>"johnathb Sandbox", :context_title=>"A Canvas training course for johnathb", :custom_canvas_assignment_points_possible=>"10", :custom_canvas_assignment_title=>"OCILL Staging Test", :custom_canvas_enrollment_state=>"active", :ext_ims_lis_basic_outcome_url=>"https://umich.instructure.com/api/lti/v1/tools/10530/ext_grade_passback", :ext_lti_assignment_id=>"f9139721-be1f-473a-91b5-6a08a386074c", :ext_outcome_data_values_accepted=>"url,text", :ext_outcome_result_total_score_accepted=>"true", :ext_outcomes_tool_placement_url=>"https://umich.instructure.com/api/lti/v1/turnitin/outcomes_placement/10530", :ext_roles=>"urn:lti:instrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Instructor,urn:lti:instrole:ims/lis/Student,urn:lti:role:ims/lis/Instructor,urn:lti:sysrole:ims/lis/User", :launch_presentation_document_target=>"iframe", :launch_presentation_locale=>"en", :launch_presentation_return_url=>"https://umich.instructure.com/courses/56125/external_content/success/external_tool_redirect", :lis_outcome_service_url=>"https://umich.instructure.com/api/lti/v1/tools/10530/grade_passback", :lti_message_type=>"basic-lti-launch-request", :lti_version=>"LTI-1p0", :resource_link_id=>"52e0499f9ed929340aec886dd08c7035e45471f5", :resource_link_title=>"OCILL Staging Test", :roles=>"Instructor", :tool_consumer_info_product_family_code=>"canvas", :tool_consumer_info_version=>"cloud", :tool_consumer_instance_contact_email=>"[email protected]", :tool_consumer_instance_guid=>"7db438071375c02373713c12c73869ff2f470b68.umich.instructure.com", :tool_consumer_instance_name=>"University of Michigan - Ann Arbor", :user_id=>"1ec00186b63b783a994fd2d5b7648ba6cdfe9807", :controller=>"launch", :action=>"create"}, @consumer_key="ocill-lti-key", @Signature="7jq4lBIBYoMzIeShPZbPNJjOrEs=", @secret="REDACTED!", @message=#<IMS::LTI::Models::Messages::BasicLTILaunchRequest:0x007fd7836bb6d0 @custom_params={"custom_canvas_assignment_points_possible"=>"10", "custom_canvas_assignment_title"=>"OCILL Staging Test", "custom_canvas_enrollment_state"=>"active"}, @ext_params={"ext_ims_lis_basic_outcome_url"=>"https://umich.instructure.com/api/lti/v1/tools/10530/ext_grade_passback", "ext_lti_assignment_id"=>"f9139721-be1f-473a-91b5-6a08a386074c", "ext_outcome_data_values_accepted"=>"url,text", "ext_outcome_result_total_score_accepted"=>"true", "ext_outcomes_tool_placement_url"=>"https://umich.instructure.com/api/lti/v1/turnitin/outcomes_placement/10530", "ext_roles"=>"urn:lti:instrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Instructor,urn:lti:instrole:ims/lis/Student,urn:lti:role:ims/lis/Instructor,urn:lti:sysrole:ims/lis/User"}, @unknown_params={"controller"=>"launch", "action"=>"create"}, @oauth_consumer_key="ocill-lti-key", @oauth_signature_method="HMAC-SHA1", @oauth_timestamp="1510067228", @oauth_nonce="70h3qDQemquvfZeKZ03OIJt5Me06hmoUuA4DEscIgQ", @oauth_version="1.0", @context_id="de25c863939f3cacfc02480738d072d95597842c", @context_label="johnathb Sandbox", @context_title="A Canvas training course for johnathb", @launch_presentation_document_target="iframe", @launch_presentation_locale="en", @launch_presentation_return_url="https://umich.instructure.com/courses/56125/external_content/success/external_tool_redirect", @lis_outcome_service_url="https://umich.instructure.com/api/lti/v1/tools/10530/grade_passback", @lti_message_type="basic-lti-launch-request", @lti_version="LTI-1p0", @oauth_callback="about:blank", @resource_link_id="52e0499f9ed929340aec886dd08c7035e45471f5", @resource_link_title="OCILL Staging Test", @roles="Instructor", @tool_consumer_info_product_family_code="canvas", @tool_consumer_info_version="cloud", @tool_consumer_instance_contact_email="[email protected]", @tool_consumer_instance_guid="7db438071375c02373713c12c73869ff2f470b68.umich.instructure.com", @tool_consumer_instance_name="University of Michigan - Ann Arbor", @user_id="1ec00186b63b783a994fd2d5b7648ba6cdfe9807", @oauth_signature="7jq4lBIBYoMzIeShPZbPNJjOrEs=", @launch_url="https://lrc-tesuto.lrc.lsa.umich.edu/ocill/launch/create">, @simple_oauth_header=#<SimpleOAuth::Header:0x007fd7836bb108 @method="POST", @uri=#<URI::HTTPS https://lrc-tesuto.lrc.lsa.umich.edu/ocill/launch/create>, @params={:context_id=>"de25c863939f3cacfc02480738d072d95597842c", :context_label=>"johnathb Sandbox", :context_title=>"A Canvas training course for johnathb", :custom_canvas_assignment_points_possible=>"10", :custom_canvas_assignment_title=>"OCILL Staging Test", :custom_canvas_enrollment_state=>"active", :ext_ims_lis_basic_outcome_url=>"https://umich.instructure.com/api/lti/v1/tools/10530/ext_grade_passback", :ext_lti_assignment_id=>"f9139721-be1f-473a-91b5-6a08a386074c", :ext_outcome_data_values_accepted=>"url,text", :ext_outcome_result_total_score_accepted=>"true", :ext_outcomes_tool_placement_url=>"https://umich.instructure.com/api/lti/v1/turnitin/outcomes_placement/10530", :ext_roles=>"urn:lti:instrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Instructor,urn:lti:instrole:ims/lis/Student,urn:lti:role:ims/lis/Instructor,urn:lti:sysrole:ims/lis/User", :launch_presentation_document_target=>"iframe", :launch_presentation_locale=>"en", :launch_presentation_return_url=>"https://umich.instructure.com/courses/56125/external_content/success/external_tool_redirect", :lis_outcome_service_url=>"https://umich.instructure.com/api/lti/v1/tools/10530/grade_passback", :lti_message_type=>"basic-lti-launch-request", :lti_version=>"LTI-1p0", :resource_link_id=>"52e0499f9ed929340aec886dd08c7035e45471f5", :resource_link_title=>"OCILL Staging Test", :roles=>"Instructor", :tool_consumer_info_product_family_code=>"canvas", :tool_consumer_info_version=>"cloud", :tool_consumer_instance_contact_email=>"[email protected]", :tool_consumer_instance_guid=>"7db438071375c02373713c12c73869ff2f470b68.umich.instructure.com", :tool_consumer_instance_name=>"University of Michigan - Ann Arbor", :user_id=>"1ec00186b63b783a994fd2d5b7648ba6cdfe9807", :controller=>"launch", :action=>"create"}, @options={:nonce=>"70h3qDQemquvfZeKZ03OIJt5Me06hmoUuA4DEscIgQ", :signature_method=>"HMAC-SHA1", :timestamp=>"1510067228", :version=>"1.0", :consumer_key=>"ocill-lti-key", :callback=>"about:blank", :consumer_secret=>"REDACTED!"}>>
`

LTI Advantage support

IMS has recently announced that it's abandoning LTI 2.0 in favor of a new concept called LTI Advantage, which seems to be including LTI 1.1 + several extensions. So then I'm wondering what version of this gem is compatible with that. Should we still be using version 2 or should we go back to version 1? How is it going to evolve from here? Thanks.

undefined method `code' for nil:NilClass: LTI integration

Hello all. We are working on an LTI Integration with canvas in our rails 3 app. However, we are receiving the error

undefined method code' for nil:NilClass

when calling

if provider.valid_request?(request, false)

We have traced this error to the oauth gem, however can not determine why the request is being passed as nil. It is populating when we debug it through logger or a raise and we can not tell if it is getting lost in our handoff to the lti gem or the gem's hand off to oauth.

Does anyone know anything about this?

unhelpful exception

I forgot to save the lis_outcome_service_url, and when I tried to update in console, I get:

>> @tp.post_replace_result!(0.5) 
IMS::LTI::InvalidLTIConfigError: 

Which has no message. In my regular code I was trying:

rescue Exception => e
        Rails.logger.debug "**********EXCEPTION**********"
        Rails.logger.debug $!
        Rails.logger.debug e

but all I got was "*_EXCEPTION_" which made it hard to figure out *why it was wrong. :D

XML Errors Submitting to Canvas

I think my issue is probably related to #12 but I'm not sure how to debug things.

When I post scores to canvas, sometimes I get the REXML exception, which does appear to be in the result page that's returned. The problem is I'm not sure if there's a way that I can test where things are going wrong? I've been using the "Test Student" account for my course, and that seems to work fine, but when others post grades there are issues.

disable course navigation by default

Hi,
I am new Canvas and LTI.
I just started working on an LTI 2.0 app.

I am able to launch the app but the problem is that it is enabled for all courses.
By default, course navigation should be disabled and the teacher should explicitly enable it.

  1. Set course_navigation[default] to disabled while launching.
  2. Use external_tools API to update it

api/v1/accounts/{id}/external_tools/{id}
Now if i just want to update the app after launch then the issue is that I don't see the app in the external_tools list API which I launched using LTI-2 however, I am getting those apps which i am launching using this API

curl -X POST '{host}/api/v1/accounts/1/external_tools' \
-H "Authorization: Bearer {Tocken]" \
-F 'name=LTI Example' \
-F 'consumer_key=asdfg' \
-F 'shared_secret=lkjh' \
-F 'url=https://example.com/ims/lti' \
-F 'privacy_level=name_only' \
-F 'course_navigation[default]=disabled' \
-F 'course_navigation[text]=LTI Test' \
-F 'course_navigation[enabled]=true' \
-F 'user_navigation[visibility]=public'

So

  1. Is there a method or parameter that I can use to disable the course navigation by default while launching the app?

  2. Why external_tools list API is not returning apps which are launched using ims-lti gem?

The behavior is reproducible using this https://github.com/instructure/lti_tool_provider_example app

Thank you.

bug in generate_identifier in version 1.1.7

Method IMS::LTI::generate_identifier tries to generate a unique identifier by calling UUID.new. When that's inserted in a message this it to_s on the UUID object, which generates an internal representation, not the identifier string. The code needs to call UUID.new.generate to get the actual string.

I see this has been changed in version 1.1.8 to use SecureRandom.uuid instead, which I think is a good move, but that is only available in ruby 1.9. So maybe it's worth fixing version 1.1.7 for people still using ruby 1.8.

Thanks,
Octav

ToolProxyRegistrationService.tool_consumer profile request does not set correct headers or `lti_version`

The GET request issued to fetch the tool consumer profile does not set the correct Accept header of application/vnd.ims.lti.v2.toolconsumerprofile+json, and does not add the lti_version parameter to the request.
Both of these requirements are specified here
https://www.imsglobal.org/lti/model/uml/purl.imsglobal.org/vocab/lti/v2/lti/ToolConsumerProfile/service.html
in section 3.1. Attempting the request without either results in a 400 error when running the IMS LTI certification test suite.

Does this support LTI 1.1 ?

If not, do you know of another Ruby gem that supports LTI 1.1 ?

thanks in advance,this looks like a great project

1.2.4 to 2.2.3, what replicates the ToolConsumer functionality

I need to upgrade an app from v1.2.4 to v2.2.3 (as we are bumping Ruby from v2.7.2 to v3.1.2)

We have simple code which uses IMS::LTI::ToolConsumer.new, IMS::LTI::ToolConsumer.launch_url, and IMS::LTI::ToolConsumer.generate_launch_data. I cannot find and hents on how to change this code to use the new available classes (since IMS::LTI::ToolConsumer has now been deleted).

REXML Exception when using post_replace_result method

When I try to POST a grade from an LTI provider similarly to the Sinatra example app like this:

res = @tp.post_replace_result!(params['score'])

I'm getting a #<REXML::ParseException: Missing end tag for 'link' (got "head") exception. Full stack trace below.

Any suggestions on how to track this one down? Is this in this Gem or something on my end? I've gotten this to work before, but I'm a little lost with this one.

Thanks!


<REXML::ParseException: Missing end tag for 'link' (got "head")

Line: 236
Position: 18074
Last 80 unconsumed characters:

/home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/rexml/parsers/baseparser.rb:338:in pull_event' /home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/rexml/parsers/baseparser.rb:183:inpull'
/home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/rexml/parsers/treeparser.rb:22:in parse' /home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/rexml/document.rb:281:inbuild'
/home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/rexml/document.rb:43:in initialize' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/bundler/gems/ims-lti-544874ea0690/lib/ims/lti/outcome_response.rb:108:innew'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/bundler/gems/ims-lti-544874ea0690/lib/ims/lti/outcome_response.rb:108:in process_xml' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/bundler/gems/ims-lti-544874ea0690/lib/ims/lti/outcome_response.rb:78:inprocess_post_response'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/bundler/gems/ims-lti-544874ea0690/lib/ims/lti/outcome_request.rb:134:in post_outcome_request' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/bundler/gems/ims-lti-544874ea0690/lib/ims/lti/outcome_request.rb:81:inpost_replace_result!'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/bundler/gems/ims-lti-544874ea0690/lib/ims/lti/tool_provider.rb:130:in post_replace_result!' /vagrant/projects/apprennet-com/app/models/lti_session.rb:65:insend_grade_postback'
/vagrant/projects/apprennet-com/app/controllers/lti_launcher_controller.rb:35:in test_grade_postback' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/implicit_render.rb:4:insend_action'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/abstract_controller/base.rb:167:in process_action' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/rendering.rb:10:inprocess_action'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/abstract_controller/callbacks.rb:18:in block in process_action' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:414:in_run__677254598__process_action__38662926__callbacks'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:405:in __run_callback' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:385:in_run_process_action_callbacks'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:81:in run_callbacks' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/abstract_controller/callbacks.rb:17:inprocess_action'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/rescue.rb:29:in process_action' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/instrumentation.rb:30:inblock in process_action'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in block in instrument' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:ininstrument'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in instrument' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/instrumentation.rb:29:inprocess_action'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/params_wrapper.rb:207:in process_action' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/abstract_controller/base.rb:121:inprocess'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/abstract_controller/rendering.rb:45:in process' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal.rb:203:indispatch'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal/rack_delegation.rb:14:in dispatch' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_controller/metal.rb:246:inblock in action'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/routing/route_set.rb:73:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/routing/route_set.rb:73:indispatch'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/routing/route_set.rb:36:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/journey-1.0.4/lib/journey/router.rb:68:inblock in call'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/journey-1.0.4/lib/journey/router.rb:56:in each' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/journey-1.0.4/lib/journey/router.rb:56:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/routing/route_set.rb:612:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/mongoid-2.7.1/lib/rack/mongoid/middleware/identity_map.rb:33:inblock in call'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/mongoid-2.7.1/lib/mongoid.rb:133:in unit_of_work' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/mongoid-2.7.1/lib/rack/mongoid/middleware/identity_map.rb:33:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/warden-1.2.3/lib/warden/manager.rb:35:in block in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/warden-1.2.3/lib/warden/manager.rb:34:incatch'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/warden-1.2.3/lib/warden/manager.rb:34:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/best_standards_support.rb:17:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/etag.rb:23:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/conditionalget.rb:25:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/head.rb:14:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/params_parser.rb:21:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/flash.rb:242:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/session/abstract/id.rb:210:incontext'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/session/abstract/id.rb:205:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/cookies.rb:341:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/dragonfly-0.9.15/lib/dragonfly/cookie_monster.rb:9:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/callbacks.rb:28:inblock in call'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:405:in _run__366245983__call__1040513793__callbacks' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:405:in__run_callback'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:385:in _run_call_callbacks' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:81:inrun_callbacks'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/callbacks.rb:27:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/reloader.rb:65:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/remote_ip.rb:31:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/debug_exceptions.rb:16:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/show_exceptions.rb:56:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/railties-3.2.13/lib/rails/rack/logger.rb:32:incall_app'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/railties-3.2.13/lib/rails/rack/logger.rb:16:in block in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/activesupport-3.2.13/lib/active_support/tagged_logging.rb:22:intagged'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/railties-3.2.13/lib/rails/rack/logger.rb:16:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/request_id.rb:22:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/methodoverride.rb:21:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/runtime.rb:17:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/lock.rb:15:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/actionpack-3.2.13/lib/action_dispatch/middleware/static.rb:63:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/dragonfly-0.9.15/lib/dragonfly/middleware.rb:13:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-cache-1.2/lib/rack/cache/context.rb:136:inforward'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-cache-1.2/lib/rack/cache/context.rb:245:in fetch' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-cache-1.2/lib/rack/cache/context.rb:185:inlookup'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-cache-1.2/lib/rack/cache/context.rb:66:in call!' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-cache-1.2/lib/rack/cache/context.rb:51:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/railties-3.2.13/lib/rails/engine.rb:479:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/railties-3.2.13/lib/rails/application.rb:223:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/content_length.rb:14:in call' /home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/railties-3.2.13/lib/rails/rack/log_tailer.rb:17:incall'
/home/vagrant/.rvm/gems/ruby-2.0.0-p0/gems/rack-1.4.5/lib/rack/handler/webrick.rb:59:in service' /home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/httpserver.rb:138:inservice'
/home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/httpserver.rb:94:in run' /home/vagrant/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/server.rb:295:inblock in start_thread'
...
Missing end tag for 'link' (got "head")
Line: 236
Position: 18074
Last 80 unconsumed characters:

Line: 236
Position: 18074
Last 80 unconsumed characters:

Trying to build a tool provider that uses content-item message –– wondering if this library could be used

We're attempting to build a product for the various LMS services and hoping to us Content-Item Message –– at the moment I have a launch to a Rails application working, and am trying to find information on how to successfully POST back to the LMS either server-side or via javascript-triggered form submission –– have ben unable to find documentation that has enabled me to get this working. From some issues I've seen posted here, it looks like this library could potentially handle this, but not sure yet.

OAuth::RequestProxy::UnknownRequestType (ActionDispatch::Request)

I'm trying to connect desire2learn to our tool provider.

This is logged at the line:

if provider.valid_request?(request)

I copied this line from the example in the documentation. shouldn't it just return false if the request is malformed or otherwise messed up?

Here's the request parameters from our desire2learn test portal:

Parameters: {
"launch_presentation_locale"=>"EN-US__",
"tool_consumer_info_version"=>"10.1.0 SP5",
"tool_consumer_info_product_family_code"=>"desire2learn",
"tool_consumer_instance_guid"=>"",
"tool_consumer_instance_name"=>"",
"tool_consumer_instance_description"=>"",
"tool_consumer_instance_contact_email"=>"",
"context_id"=>"6670",
"context_title"=>"Sandbox Course 1",
"context_label"=>"Sandbox _101",
"context_type"=>"CourseOffering",
"ext_tc_profile_url"=>"https://notreal.desire2learndemo.com/d2l/api/ext/1.0/lti/tcservices",
"ext_d2l_token_id"=>"2602",
"ext_d2l_link_id"=>"8",
"ext_d2l_token_digest"=>"3n+aoHDWP3bh9Vq2lbB+ERC07vw=",
"resource_link_id"=>"74891df6-8638-1e24-994b-6b57ce61886d_439707169",
"resource_link_title"=>"TestProvider",
"resource_link_description"=>"TestProvider",
"lis_result_sourcedid"=>"755de886-03ca-4e78-988a-629a9ae70382",
"lis_outcome_service_url"=>"https://notreal.desire2learndemo.com/d2l/le/lti/Outcome",
"lti_version"=>"LTI-1p0",
"lti_message_type"=>"basic-lti-launch-request",
"oauth_version"=>"1.0",
"oauth_nonce"=>"141431938",
"oauth_timestamp"=>"1371067056",
"oauth_signature_method"=>"HMAC-SHA1",
"oauth_consumer_key"=>"TEST",
"oauth_callback"=>"about:blank",
"oauth_signature"=>"3PytuKS5veuuydipM2MWj2lis+M=",
"basiclti_submit"=>"Launch Endpoint with BasicLTI Data",
"id"=>"1"}

LTI Launch fails for content item selection with latest IMS-LTI gem

The IMS::LTI::Services::MessageAuthenticator initialization parses the params object into options and parsed_params.

The course level navigation results in an @options that looks like this:

{:consumer_key=>"lti-example-dev", :signature_method=>"HMAC-SHA1", :timestamp=>"1498094063", :nonce=>"sTtZphI5tfJarJfZQksP1Bdm6nILujuMntjpVk10bc", :version=>"1.0", :callback=>"about:blank"}

The content item selection results in an @options that looks like this:

{:consumer_key=>"lti-example-dev", :signature_method=>"HMAC-SHA1", :timestamp=>"1498094088", :nonce=>"TTNKN94qqjX8vmehAIlsod7GhXLkQpRqy52FxYamG4", :version=>"1.0"}

The primary difference is that the content item selection does not have a callback.

In message_authenticator.rb there's this bit of code that generates the simple_oauth_header. By default it always adds in a callback even if the request doesn't include that value. If I remove the callback then the signature succeeds. Is there a reason to always have a default callback value?

def simple_oauth_header @simple_oauth_header ||= begin @simple_oauth_header = SimpleOAuth::Header.new( :post, launch_url, @parsed_params, @options.merge( { consumer_key: consumer_key, consumer_secret: @secret, callback: 'about:blank' } ) ) @simple_oauth_header end end

I also discussed this issue in the Canvas community. For reference, here's a link:
https://community.canvaslms.com/message/72970-lti-launch-fails-for-content-item-selection-with-latest-ims-lti-gem

generate_request_xml

The OutcomeRequest#generate_request_xml method is very useful as a communication debugging tool, it would be great if it were made public, so I don't have to edit the source when I need it. The similar OutcomeResponse#generate_response_xml is already public.

And both of them should accept options, so I can pass an indent to the builder. So something like this:

def generate_request_xml options = {}
  builder = Builder::XmlMarkup.new(options)

Thanks,
Octav

OAuth::RequestProxy::UnknownRequestType (ActionDispatch::Request):

I did update for rails app form 4 to 5 recently.

Then IMS::LTI::ToolProvider is not working properly.

Here is the code:


    private_key = res[:body]["private_key"]
    provider = IMS::LTI::ToolProvider.new(public_key, private_key, params.to_unsafe_h)
    unless provider.valid_request?(request)
      if provider.context_student? || provider.institution_student?
        return render :participant_wait_for_activity
      else
        return render :invalid_key
      end
    end

I got an error when provider.valid_request?(request) is excuted.

Here is the error:
OAuth::RequestProxy::UnknownRequestType (ActionDispatch::Request):

Remove the `warn` for unknown parameters in message.rb

The warn on line 124 of /models/messages/message.rb means that any time you create a message with standard LTI parameters (user_id, lis_person_contact_email_primary) it will flood your logs with warnings because they are not in the whitelist. Can we either add all the standard LTI parameters into the whitelist or just remove the warn line?

Ideally there would be an option like "error_on_unknown_paramter" which would make it so it would throw an error in an known paramter, otherwise it just continues.

Question: does this lib still not support LTI1.3?

Hello,

I am echoing issue #158 which is 4 years old. I was wondering if this lib still does not support LTI 1.3?

I have been reading the code in lib/ims/lti/services/message_authenticator.rb and found functions for JWT support. So I thought maybe it has been implemented since?

Thank you.

Strategy to Send Grade Later

Hi,

I would like the instructor to be able to grade the work done by students on my app, before sending the grade back to the LMS.

I thought to store the intructor lti params as a session object and create a new provider object (IMS::LTI::ToolProvider.new) when they go to grade the student's work (and create one for each students' assignment grade, just before creating it, modify the sourcedid and out service url). However, this large amount of data cannot be stored in the session (cookie overflow) and from what I have read online, it would be a bad practice to modify the session to handle the size of params.

I also thought I could modify the provider object of the instructor like so:

provider.lis_result_sourcedid = "sourcedid of a particular student" provider.lis_outcome_service_url = "outcome url of a particular student"

But it looks like this is not possible either. What do you suggest is the best way to solve this problem? Thank you very much.

Port number in launch URL causes tool consumer to produce bad request

I spent days debugging an issue where I was using both a ToolProvider and ToolConsumer in an integration test. I was running a local Rails server on the default port (3000) and it turned out that validation would always fail when the port number was included in the launch_url which I used to build the ToolConsumer's request.

Here's a simplified version of my code:

tool_consumer = IMS::LTI::ToolConsumer.new(
  "testkey",
  "deep dark secret",
  { "resource_link_id" => 1234, "launch_url" => "http://localhost:3000/auth" }
)
post "http://localhost:3000/auth", tool_consumer.generate_launch_data

And in the auth controller:

tool_provider = IMS::LTI::ToolProvider.new("testkey", "deep dark secret", params)
tool_provider.valid_request!(request)

This would always throw an OAuth::Unauthorized exception. If I took out the port in the launch_url it authenticated just fine.

According to this part of the OAuth spec (http://oauth.net/core/1.0/#sig_url), the port and query string should always be removed from the launch url. I believe this is why the validation fails - the RequestValidator module calls OAuth::Signature.build() and verify() directly, and that module appears to be following the spec by dropping the port from the request digest. It looks like generate_launch_data is already dropping the query string, and drops the port if it's the default one. What it should be doing is dropping the port in all cases.

What's the next step? Assuming you guys agree this is a bug, I can provide a pull request that fixes it as well as some tests to verify correctness.

License missing from gemspec

RubyGems.org doesn't report a license for your gem. This is because it is not specified in the gemspec of your last release.

via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

Including a license in your gemspec is an easy way for rubygems.org and other tools to check how your gem is licensed. As you can imagine, scanning your repository for a LICENSE file or parsing the README, and then attempting to identify the license or licenses is much more difficult and more error prone. So, even for projects that already specify a license, including a license in your gemspec is a good practice. See, for example, how rubygems.org uses the gemspec to display the rails gem license.

There is even a License Finder gem to help companies/individuals ensure all gems they use meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough issue that even Bundler now generates gems with a default 'MIT' license.

I hope you'll consider specifying a license in your gemspec. If not, please just close the issue with a nice message. In either case, I'll follow up. Thanks for your time!

Appendix:

If you need help choosing a license (sorry, I haven't checked your readme or looked for a license file), GitHub has created a license picker tool. Code without a license specified defaults to 'All rights reserved'-- denying others all rights to use of the code.
Here's a list of the license names I've found and their frequencies

p.s. In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally looking for download data) and decided to collect license metadata,too, and make issues for gemspecs not specifying a license as a public service :). See the previous link or my blog post about this project for more information.

Version 1.2 regresses oauth dependency

In version 1.1.13, the oauth dependency was relaxed to > 0.4.5, < 0.6. However, in commit 6a91689, @rivernate overwrote that change and restored a hard dependency on exactly oauth 0.4.5, and that regression was released in ims-lti 1.2.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.