# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/api/dtm.pb' require 'contrast/framework/base_support' require 'contrast/framework/rails/patch/support' require 'contrast/utils/string_utils' module Contrast module Framework module Rails # Used when Rails is present to define framework specific behavior class Support extend Contrast::Framework::BaseSupport extend Contrast::Framework::Rails::Patch::Support class << self 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 collect_routes find_all_routes(::Rails.application, []) end # Find the current route, based on the provided Request wrapper # @param request[Contrast::Agent::Request] # @return [Contrast::Api::Dtm::RouteCoverage] 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] # the route is directly implemented within the application if direct_route?(full_route) route = full_route[2] # route w/ highest precedence Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route) else engine_route(full_route, request) end rescue StandardError => _e nil 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 # route is not mounted within an engine def direct_route? full_route full_route[2]&.app&.cs__class == ActionDispatch::Routing::RouteSet::Dispatcher || (full_route[2].cs__class == ActionDispatch::Journey::Route && full_route[2]&.app&.cs__class == ActionDispatch::Routing::Mapper::Constraints) end def engine_route full_route, request engine_route = full_route[2] # supposed route - but actually an Engine mount point return unless engine_route engine_mount_name = engine_route.name return unless engine_mount_name engine_path_segments = request.rack_request.path_info.split(engine_mount_name) return if engine_path_segments.empty? path_within_engine = engine_path_segments[-1] return unless path_within_engine engine_router = engine_route.app&.app&.routes&.router return unless engine_router # Get all routes regardless of http method matching_routes = engine_router.send(:filter_routes, path_within_engine) return unless matching_routes # filter for current http method reportable_routes = engine_router.send(:match_routes, matching_routes, request.rack_request) return if reportable_routes.empty? Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(reportable_routes[0]) end # Rails engine routes need to be detected by inspecting Engine class route set def find_all_routes app, route_list return route_list unless app.cs__respond_to?(:routes) && app.routes.cs__respond_to?(:routes) app.routes.routes.each do |route| if route.cs__respond_to?(:app) && route.app.cs__class == ActionDispatch::Routing::RouteSet::Dispatcher route_list << Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route) elsif route.app.app.cs__respond_to?(:routes) route_list += find_all_routes(route.app.app, []) end end route_list end end end end end end