# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'contrast/components/interface'
require 'contrast/utils/invalid_configuration_util'

module Contrast
  module Framework
    module Rails
      module Patch
        # This module is used to analyze rails session storage configuration for assess vulnerabilities
        module AssessConfiguration
          include Contrast::Components::Interface

          access_component :agent, :analysis, :logging

          CS__SESSION_TIMEOUT_NAME = 'session-timeout'
          SAFE_SESSION_TIMEOUT = (30 * 60 * 1000)
          CS__SECURE_RULE_NAME = 'secure-flag-missing'
          CS__HTTPONLY_RULE_NAME = 'rails-http-only-disabled'

          class << self
            include Contrast::Utils::InvalidConfigurationUtil

            def analyze_session_store *args
              return if ASSESS.forcibly_disabled?

              apply_httponly_disabled(*args)
              apply_secure_cookie_disabled(*args)
              apply_session_timeout(*args)
            end

            private

            def vulnerable_setting? setting_key, safe_settings_value, original_args, safe_default: true, comparison_type: nil
              # In most cases, Rails is pretty nice and the default value is safe
              return !safe_default unless original_args && original_args.length > 1

              # If the user overrode some args, but not ours, fall back on the default
              rails_session_settings = original_args[1]
              return !safe_default unless rails_session_settings&.key?(setting_key)

              value = rails_session_settings[setting_key]

              return value.to_i > safe_settings_value.to_i if comparison_type&.to_sym == :greater_than

              value != safe_settings_value
            end

            def apply_session_timeout *args
              return if ASSESS.rule_disabled? CS__SESSION_TIMEOUT_NAME
              return unless vulnerable_setting?(:expire_after, SAFE_SESSION_TIMEOUT, args, comparison_type: :greater_than, safe_default: false)

              rails_session_settings = args[1]
              cs__report_finding(CS__SESSION_TIMEOUT_NAME, rails_session_settings, caller_locations(3, 2)[0])
            rescue StandardError => e
              begin
                logger.error('Unable to track call to set session timeout', e)
              rescue StandardError
                nil
              end
            end

            def apply_secure_cookie_disabled *args
              return if ASSESS.rule_disabled? CS__SECURE_RULE_NAME
              return unless vulnerable_setting?(:secure, true, args)

              rails_session_settings = args[1]
              cs__report_finding(CS__SECURE_RULE_NAME, rails_session_settings, caller_locations(3, 2)[0])
            rescue StandardError => e
              begin
                logger.error('Unable to track call to disable secure cookies', e)
              rescue StandardError
                nil
              end
            end

            def apply_httponly_disabled *args
              return if ASSESS.rule_disabled? CS__HTTPONLY_RULE_NAME
              return unless vulnerable_setting?(:httponly, true, args)

              rails_session_settings = args[1]
              cs__report_finding(CS__HTTPONLY_RULE_NAME, rails_session_settings, caller_locations(3, 2)[0])
            rescue StandardError => e
              begin
                logger.error('Unable to track call to disable httponly in session cookie', e)
              rescue StandardError
                nil
              end
            end
          end
        end
      end
    end
  end
end