# frozen_string_literal: true

require "spec_helper"

RSpec.describe Doorkeeper::RedirectUriValidator do
  subject(:client) do
    FactoryBot.create(:application)
  end

  it "is valid when the uri is a uri" do
    client.redirect_uri = "https://example.com/callback"
    expect(client).to be_valid
  end

  # Most mobile and desktop operating systems allow apps to register a custom URL
  # scheme that will launch the app when a URL with that scheme is visited from
  # the system browser.
  #
  # @see https://www.oauth.com/oauth2-servers/redirect-uris/redirect-uris-native-apps/
  it "is valid when the uri is custom native URI" do
    client.redirect_uri = "myapp:/callback"
    expect(client).to be_valid
  end

  it "is valid when the uri has a query parameter" do
    client.redirect_uri = "https://example.com/abcd?xyz=123"
    expect(client).to be_valid
  end

  it "accepts nonstandard oob redirect uri" do
    client.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
    expect(client).to be_valid
  end

  it "accepts nonstandard oob:auto redirect uri" do
    client.redirect_uri = "urn:ietf:wg:oauth:2.0:oob:auto"
    expect(client).to be_valid
  end

  it "is invalid when the uri is not a uri" do
    client.redirect_uri = "]"
    expect(client).not_to be_valid
    expect(client.errors[:redirect_uri].first).to eq(I18n.t("activerecord.errors.models.doorkeeper/application.attributes.redirect_uri.invalid_uri"))
  end

  it "is invalid when the uri is relative" do
    client.redirect_uri = "/abcd"
    expect(client).not_to be_valid
    expect(client.errors[:redirect_uri].first).to eq(I18n.t("activerecord.errors.models.doorkeeper/application.attributes.redirect_uri.relative_uri"))
  end

  it "is invalid when the uri has a fragment" do
    client.redirect_uri = "https://example.com/abcd#xyz"
    expect(client).not_to be_valid
    expect(client.errors[:redirect_uri].first).to eq(I18n.t("activerecord.errors.models.doorkeeper/application.attributes.redirect_uri.fragment_present"))
  end

  it "is invalid when scheme resolves to localhost (needs an explict scheme)" do
    client.redirect_uri = "localhost:80"
    expect(client).to be_invalid
    expect(client.errors[:redirect_uri].first).to eq(I18n.t("activerecord.errors.models.doorkeeper/application.attributes.redirect_uri.unspecified_scheme"))
  end

  it "is invalid if an ip address" do
    client.redirect_uri = "127.0.0.1:8080"
    expect(client).to be_invalid
  end

  it "accepts an ip address based URI if a scheme is specified" do
    client.redirect_uri = "https://127.0.0.1:8080"
    expect(client).to be_valid
  end

  context "when force secured uri configured" do
    it "accepts a valid uri" do
      client.redirect_uri = "https://example.com/callback"
      expect(client).to be_valid
    end

    it "accepts custom scheme redirect uri (as per rfc8252 section 7.1)" do
      client.redirect_uri = "com.example.app:/oauth/callback"
      expect(client).to be_valid
    end

    it "accepts custom scheme redirect uri (as per rfc8252 section 7.1) #2" do
      client.redirect_uri = "com.example.app:/test"
      expect(client).to be_valid
    end

    it "accepts custom scheme redirect uri (common misconfiguration we have decided to allow)" do
      client.redirect_uri = "com.example.app://oauth/callback"
      expect(client).to be_valid
    end

    it "accepts custom scheme redirect uri (common misconfiguration we have decided to allow) #2" do
      client.redirect_uri = "com.example.app://test"
      expect(client).to be_valid
    end

    it "accepts a non secured protocol when disabled" do
      client.redirect_uri = "http://example.com/callback"
      allow(Doorkeeper.configuration).to receive(
        :force_ssl_in_redirect_uri,
      ).and_return(false)
      expect(client).to be_valid
    end

    it "accepts a non secured protocol when conditional option defined" do
      Doorkeeper.configure do
        orm DOORKEEPER_ORM
        force_ssl_in_redirect_uri { |uri| uri.host != "localhost" }
      end

      application = FactoryBot.build(:application, redirect_uri: "http://localhost/callback")
      expect(application).to be_valid

      application = FactoryBot.build(:application, redirect_uri: "https://test.com/callback")
      expect(application).to be_valid

      application = FactoryBot.build(:application, redirect_uri: "http://localhost2/callback")
      expect(application).not_to be_valid

      application = FactoryBot.build(:application, redirect_uri: "https://test.com/callback")
      expect(application).to be_valid
    end

    it "forbids redirect uri if required" do
      client.redirect_uri = "javascript://document.cookie"

      Doorkeeper.configure do
        orm DOORKEEPER_ORM
        forbid_redirect_uri { |uri| uri.scheme == "javascript" }
      end

      expect(client).to be_invalid
      expect(client.errors[:redirect_uri].first).to eq("is forbidden by the server.")

      client.redirect_uri = "https://localhost/callback"
      expect(client).to be_valid
    end

    it "invalidates the uri when the uri does not use a secure protocol" do
      client.redirect_uri = "http://example.com/callback"
      expect(client).not_to be_valid
      error = client.errors[:redirect_uri].first
      expect(error).to eq(I18n.t("activerecord.errors.models.doorkeeper/application.attributes.redirect_uri.secured_uri"))
    end
  end

  context "with multiple redirect uri" do
    it "invalidates the second uri when the first uri is native uri" do
      client.redirect_uri = "urn:ietf:wg:oauth:2.0:oob\nexample.com/callback"
      expect(client).to be_invalid
    end
  end

  context "with blank redirect URI" do
    it "forbids blank redirect uri by default" do
      client.redirect_uri = ""

      expect(client).to be_invalid
      expect(client.errors[:redirect_uri]).not_to be_blank
    end

    it "forbids blank redirect uri by custom condition" do
      Doorkeeper.configure do
        orm DOORKEEPER_ORM
        allow_blank_redirect_uri do |_grant_flows, application|
          application.name == "admin app"
        end
      end

      client.name = "test app"
      client.redirect_uri = ""

      expect(client).to be_invalid
      expect(client.errors[:redirect_uri]).not_to be_blank

      client.name = "admin app"
      expect(client).to be_valid
    end
  end
end