# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/api/dtm_pb' cs__scoped_require 'contrast/framework/base_support' cs__scoped_require 'contrast/framework/rails/patch/support' cs__scoped_require 'contrast/framework/view_technologies_descriptor' cs__scoped_require 'contrast/utils/string_utils' module Contrast module Framework module Rails # Used when Rails is present to define framework specific behavior class Support < BaseSupport extend Contrast::Framework::Rails::Patch::Support class << self RAILS_VIEWS = [ Contrast::Framework::ViewTechnologiesDescriptor.new('app/assets', 'coffee', %w[CoffeeScript]), Contrast::Framework::ViewTechnologiesDescriptor.new('app/assets', 'scss', %w[SASS]), Contrast::Framework::ViewTechnologiesDescriptor.new('app/views', 'html', %w[HTML5]), Contrast::Framework::ViewTechnologiesDescriptor.new('app/views', 'html.erb', %w[HTML5 ERB]), Contrast::Framework::ViewTechnologiesDescriptor.new('app/views', 'html.haml', %w[HTML5 HAML]), Contrast::Framework::ViewTechnologiesDescriptor.new('public', 'html', %w[HTML5]) ].cs__freeze RAILS_MODULE_NAME_VERSION = Gem::Version.new('6.0.0') def detection_class 'Rails' end def version ::Rails.version end def application_name app_class = ::Rails.application.cs__class # Rails version 6.0.0 deprecated Rails::Application#parent_name, in Rails 6.1.0 that method will be removed entirely # and instead we need to use parent_module_name return app_class.parent_module_name if Gem::Version.new(::Rails.version) >= RAILS_MODULE_NAME_VERSION app_class.parent_name end def application_root ::Rails.root end def server_type 'rails' end def scan_views scan_view_directories(RAILS_VIEWS) end def collect_routes routes = ::Rails.application.routes.routes.map do |route| route_to_coverage(route) end routes end def current_route request return unless ::Rails.cs__respond_to?(:application) # returns array of arrays [[match_data, path_parameters, route]], sorted by # precedence # match_data: ActionDispatch::Journey::Path::Pattern::MatchData # path_parameters: hash of various things # route: ActionDispatch::Journey::Route full_routes = ::Rails.application.routes.router.send(:find_routes, request.rack_request) return if full_routes.empty? full_route = full_routes[0] # [match_data, path_parameters, route] return unless full_route route = full_route[2] # route w/ highest precedence return unless route route_to_coverage(route) end def retrieve_request env rails_env = ::Rails.application.env_config.merge(env) ::ActionDispatch::Request.new(rails_env || env) end AC_INSTANCE = 'action_controller.instance' def streaming? env return false unless defined?(::ActionController::Live) env[AC_INSTANCE].cs__class.included_modules.include?(::ActionController::Live) end private # Convert ActionDispatch::Journey::Route to Contrast::Api::Dtm::RouteCoverage def route_to_coverage route route_coverage = Contrast::Api::Dtm::RouteCoverage.new route_coverage.route = "#{ route.defaults[:controller] }##{ route.defaults[:action] }" verb = source_or_string(route.verb) route_coverage.verb = Contrast::Utils::StringUtils.force_utf8(verb) url = source_or_string(route.path.spec) route_coverage.url = Contrast::Utils::StringUtils.force_utf8(url) route_coverage end end end end end end