require 'rails_helper'

RSpec.feature 'SAML logout' do
  include SamlAuthHelper

  let(:user) { create(:user, :fully_registered) }

  context 'with a SAML request' do
    context 'when logging out from the SP' do
      it 'contains all redirect_uris in CSP when user is logged out of the IDP' do
        sign_in_and_2fa_user(user)
        visit_saml_authn_request_url(
          overrides: {
            issuer: 'saml_sp_ial2',
          },
        )
        click_continue

        # Sign out of the IDP
        visit account_path
        first(:button, t('links.sign_out')).click
        expect(page).to have_current_path root_path

        # SAML logout request
        visit_saml_logout_request_url(
          overrides: {
            issuer: 'saml_sp_ial2',
          },
        )

        # contains all redirect_uris in content security policy
        expect(page.response_headers['Content-Security-Policy']).to include(
          "form-action 'self' http://example.com http://sub.example.com",
        )
      end

      it 'contains all redirect_uris in CSP when user is logged in to the IDP' do
        sign_in_and_2fa_user(user)
        visit_saml_authn_request_url(
          overrides: {
            issuer: 'saml_sp_ial2',
          },
        )
        click_continue

        # SAML logout request
        visit_saml_logout_request_url(
          overrides: {
            issuer: 'saml_sp_ial2',
          },
        )

        # contains all redirect_uris in content security policy
        expect(page.response_headers['Content-Security-Policy']).to include(
          "form-action 'self' http://example.com http://sub.example.com",
        )
      end
    end

    context 'the SP implements SLO' do
      it 'logs the user out and redirects to the SP' do
        sign_in_and_2fa_user(user)
        visit_saml_authn_request_url
        click_continue

        visit_saml_logout_request_url

        xmldoc = SamlResponseDoc.new('feature', 'logout_assertion')

        # It should contain a SAMLResponse
        expect(page.find('#SAMLResponse', visible: false)).to be_truthy

        # It should disable caching
        expect(page.response_headers['Pragma']).to eq 'no-cache'

        # It should contain an issuer nodeset
        expect(xmldoc.issuer_nodeset.length).to eq(1)
        expect(xmldoc.issuer_nodeset[0].content).to eq(
          "https://#{IdentityConfig.store.domain_name}/api/saml",
        )

        # It should not contain a SHA256 signature nodeset
        expect(xmldoc.signature_nodeset.length).to eq(0)

        # The user should be signed out
        visit account_path
        expect(page).to have_current_path root_path
      end
    end

    context 'the user is not signed in' do
      it 'redirects to the SP' do
        visit_saml_logout_request_url(
          overrides: {
            issuer: 'saml_sp_ial2',
            name_identifier_value: 'asdf-1234',
          },
        )

        # It should contain a SAMLResponse
        expect(page.find('#SAMLResponse', visible: false)).to be_truthy

        # The user should be signed out
        visit account_path
        expect(page).to have_current_path root_path
      end
    end

    context 'when signed in with another browser' do
      it 'redirects to the SP after concurrent session logout' do
        user = user_with_2fa

        perform_in_browser(:one) do
          visit_idp_from_sp_with_ial1(:saml)
          sign_in_live_with_2fa(user)
        end

        perform_in_browser(:two) do
          visit_idp_from_sp_with_ial1(:saml)
          sign_in_live_with_2fa(user)
        end

        perform_in_browser(:one) do
          visit_saml_logout_request_url(
            overrides: {
              issuer: SamlAuthHelper::SP_ISSUER,
            },
          )

          # It should contain a SAMLResponse
          expect(page.find('#SAMLResponse', visible: false)).to be_truthy

          # The user should be signed out
          visit account_path
          expect(page).to have_current_path root_path
        end
      end
    end

    context 'the saml request is invalid' do
      it 'renders an error' do
        sign_in_and_2fa_user(user)

        visit_saml_logout_request_url(
          overrides: {
            issuer: 'invalid_provider',
            name_identifier_value: 'asdf-1234',
            security: {
              logout_requests_signed: false,
            },
          },
        )

        expect(page).to have_current_path(
          api_saml_logout_path(path_year: SamlAuthHelper::PATH_YEAR),
          ignore_query: true,
        )
        expect(page.driver.status_code).to eq(400)

        # The user should be signed in
        visit account_path
        expect(page).to have_current_path(account_path)
      end
    end
  end

  context 'without a SAML request' do
    it 'logs the user out and redirects to the sign in page' do
      sign_in_and_2fa_user(user)

      visit api_saml_logout_path(path_year: SamlAuthHelper::PATH_YEAR)

      expect(page).to have_content(t('devise.sessions.signed_out'))
      expect(page).to have_current_path(root_path)

      # The user should be signed out
      visit account_path
      expect(page).to have_current_path(root_path)
    end
  end

  context 'remote logout' do
    let(:sp) { ServiceProvider.find_by(issuer: saml_settings.issuer) }
    let(:agency) { sp.agency }

    it "terminates the user's session remotely" do
      fake_analytics = FakeAnalytics.new
      allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)

      # set up SP identity and agency identity
      user = sign_in_live_with_2fa
      visit_saml_authn_request_url
      click_continue
      click_agree_and_continue
      click_button(t('forms.buttons.submit.default'))

      identity = ServiceProviderIdentity
        .find_by(user_id: user.id, service_provider: saml_settings.issuer)
      expect(OutOfBandSessionAccessor.new(identity.rails_session_id).exists?).to eq true

      # simulate a remote request
      agency_uuid = AgencyIdentity.find_by(user_id: user.id, agency_id: agency.id).uuid
      send_saml_remote_logout_request(overrides: { sessionindex: agency_uuid })

      expect(OutOfBandSessionAccessor.new(identity.rails_session_id).exists?).to eq false

      expect(fake_analytics.events['Remote Logout initiated']).to eq(
        [{ service_provider: sp.issuer, saml_request_valid: true }],
      )
      expect(fake_analytics.events['Remote Logout completed']).to eq(
        [{ service_provider: sp.issuer, user_id: user.uuid }],
      )

      # should be logged out...
      visit account_path
      expect(page).to have_current_path(root_path)
    end
  end
end
