# frozen_string_literal: true

#
# Copyright (C) 2016 - 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/>.
#

module Lti
  describe MessageAuthenticator do
    subject { described_class.new(launch_url, message.signed_post_params(tool.shared_secret)) }

    let(:launch_url) { "http://test.com/test" }
    let(:course) { Course.create! }
    let!(:tool) do
      course.context_external_tools.create!(
        {
          name: "test tool",
          domain: "test.com",
          consumer_key: "key",
          shared_secret: "secret"
        }
      )
    end

    let(:message) do
      m = ::IMS::LTI::Models::Messages::ContentItemSelection.new(
        {
          lti_message_type: "ContentItemSelection",
          lti_version: "LTI-1p0",
          content_items: Rails.root.join("spec/fixtures/lti/content_items.json").read,
          data: Canvas::Security.create_jwt({ content_item_id: "3" }),
          lti_msg: "",
          lti_log: "",
          lti_errormsg: "",
          lti_errorlog: ""
        }
      )
      m.launch_url = launch_url
      m.oauth_consumer_key = tool.consumer_key
      m
    end

    it "creates a message from the signed_params" do
      expect(subject.message.oauth_consumer_key).to eq tool.consumer_key
    end

    describe "#valid?" do
      it "validates a message" do
        expect(subject.valid?).to be true
      end

      context "content-item unique json serialization" do
        let(:launch_url) { "http://test.com/test" }
        let(:secret) { "secret" }
        let(:signed_params) do
          {
            :oauth_callback => "about:blank",
            :oauth_consumer_key => "key",
            :oauth_nonce => "89fc77055d2a051de296fc5d99987a20",
            :oauth_signature_method => "HMAC-SHA1",
            :oauth_timestamp => "1467842103",
            :oauth_version => "1.0",
            :oauth_signature => "TL8PLA/V43D21+JkGg8i9Cj+Dqg=",
            "lti_message_type" => "ContentItemSelection",
            "lti_version" => "LTI-1p0",
            "content_items" => <<~JSON.gsub(/\s*\n\s*/, ""),
              {
                "@graph":[{
                  "windowTarget":"",
                  "text":"Arch Linux",
                  "title":"Its your computer",
                  "url":"http://lti-tool-provider-example.dev/messages/blti",
                  "thumbnail":{
                    "height":128,
                    "width":128,
                    "@id":"http://www.runeaudio.com/assets/img/banner-archlinux.png"
                  },
                  "placementAdvice":{
                    "displayHeight":600,
                    "displayWidth":800,
                    "presentationDocumentTarget":"iframe"
                  },
                  "mediaType":"application/vnd.ims.lti.v1.ltilink",
                  "@type":"LtiLinkItem",
                  "@id":"http://lti-tool-provider-example.dev/messages/blti"
                }],
                "@context":"http://purl.imsglobal.org/ctx/lti/v1/ContentItem"
              }
            JSON
            "lti_msg" => "",
            "lti_log" => "",
            "lti_errormsg" => "",
            "lti_errorlog" => ""
          }
        end

        it "validates the message" do
          message_authenticator = MessageAuthenticator.new(launch_url, signed_params)
          Timecop.freeze(Time.zone.at(signed_params[:oauth_timestamp].to_i)) do
            expect(message_authenticator.valid?).to be true
          end
        end
      end

      it "returns the same value if called multiple times" do
        enable_cache do
          expect(Array.new(2) { subject.valid? }).to eq [true, true]
        end
      end

      it "rejects an invalid secret" do
        validator = described_class.new(launch_url, message.signed_post_params("invalid"))
        expect(validator.valid?).to be false
      end

      it "rejects a used nonce" do
        enable_cache do
          signed_params = message.signed_post_params(tool.shared_secret)
          validator1 = described_class.new(launch_url, signed_params)
          validator2 = described_class.new(launch_url, signed_params)
          expect(validator1.valid?).to be true
          expect(validator2.valid?).to be false
        end
      end

      it "rejects a message older than the NONCE_EXPIRATION" do
        enable_cache do
          validator = nil
          Timecop.freeze((described_class::NONCE_EXPIRATION + 1.minute).ago) do
            validator = described_class.new(launch_url, message.signed_post_params(tool.shared_secret))
          end
          expect(validator.valid?).to be false
        end
      end

      it "doesn't store the nonce if the signature is invalid" do
        enable_cache do
          validator = described_class.new(launch_url, message.signed_post_params("invalid"))
          expect(validator.valid?).to be false
          expect(Rails.cache.exist?(validator.send(:cache_key))).to be_falsey
        end
      end

      context "no lti_version param is passed along" do
        let(:signed_params) do
          m = message.signed_post_params(tool.shared_secret)
          m.delete(:lti_version)
          m
        end

        it "doesn't throw any errors and still shows as invalid" do
          message_authenticator = MessageAuthenticator.new(launch_url, signed_params)
          Timecop.freeze(Time.zone.at(signed_params[:oauth_timestamp].to_i)) do
            expect { message_authenticator.valid? }.not_to raise_error
            expect(message_authenticator.valid?).to be false
          end
        end
      end
    end
  end
end
