diff --git a/app/web/api/v1/strategies.rb b/app/web/api/v1/strategies.rb index 12582b08..992211b7 100644 --- a/app/web/api/v1/strategies.rb +++ b/app/web/api/v1/strategies.rb @@ -33,8 +33,8 @@ def index(_request) def display_name_for(name) case name.to_s - when 'ssrf_filter' then 'Standard (recommended)' - when 'browserless' then 'JavaScript pages' + when 'faraday' then 'Standard rendering' + when 'browserless' then 'JavaScript pages (recommended)' else name.to_s.split('_').map(&:capitalize).join(' ') end end diff --git a/app/web/boot/setup.rb b/app/web/boot/setup.rb index 2c22f364..fd69faa8 100644 --- a/app/web/boot/setup.rb +++ b/app/web/boot/setup.rb @@ -26,9 +26,7 @@ def validate_environment! # @return [void] def configure_request_service! - Html2rss::RequestService.register_strategy(:ssrf_filter, SsrfFilterStrategy) - Html2rss::RequestService.default_strategy_name = :ssrf_filter - Html2rss::RequestService.unregister_strategy(:faraday) + nil end end end diff --git a/app/web/domain/auto_source.rb b/app/web/domain/auto_source.rb index 4b1d09c7..becd389e 100644 --- a/app/web/domain/auto_source.rb +++ b/app/web/domain/auto_source.rb @@ -21,7 +21,7 @@ def enabled? # @param token_data [Hash{Symbol=>Object}] authenticated account data. # @param strategy [String] # @return [Html2rss::Web::Api::V1::FeedMetadata::Metadata, nil] - def create_stable_feed(name, url, token_data, strategy = 'ssrf_filter') + def create_stable_feed(name, url, token_data, strategy = 'faraday') return nil unless token_data && FeedAccess.url_allowed_for_username?(token_data[:username], url) feed_token = Auth.generate_feed_token(token_data[:username], url, strategy: strategy) diff --git a/app/web/feeds/source_resolver.rb b/app/web/feeds/source_resolver.rb index 0bb33a6e..75e1abf4 100644 --- a/app/web/feeds/source_resolver.rb +++ b/app/web/feeds/source_resolver.rb @@ -69,7 +69,7 @@ def static_cache_identity(feed_name, params) def static_generator_input(config, params) generator_input = config.dup generator_input[:params] = merged_static_params(config, params) - generator_input[:strategy] ||= Html2rss::RequestService.default_strategy_name + generator_input[:strategy] ||= :faraday generator_input end diff --git a/app/web/security/ssrf_filter_strategy.rb b/app/web/security/ssrf_filter_strategy.rb deleted file mode 100644 index 2ad3f76f..00000000 --- a/app/web/security/ssrf_filter_strategy.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'ssrf_filter' -require 'html2rss' -module Html2rss - module Web - ## - # Strategy to fetch a URL using the SSRF filter. - class SsrfFilterStrategy < Html2rss::RequestService::Strategy - # Executes a URL fetch through `ssrf_filter` and adapts response shape. - # - # @return [Html2rss::RequestService::Response] - def execute - headers = LocalConfig.global.fetch(:headers, {}).merge( - ctx.headers.transform_keys(&:to_sym) - ) - response = SsrfFilter.get(ctx.url, headers:) - - Html2rss::RequestService::Response.new(body: response.body, - url: ctx.url, - headers: response.to_hash.transform_values(&:first)) - end - end - end -end diff --git a/spec/html2rss/web/api/v1/feed_metadata_spec.rb b/spec/html2rss/web/api/v1/feed_metadata_spec.rb index dc8780b4..fb9e55fa 100644 --- a/spec/html2rss/web/api/v1/feed_metadata_spec.rb +++ b/spec/html2rss/web/api/v1/feed_metadata_spec.rb @@ -11,7 +11,7 @@ name: 'Example Feed', url: 'https://example.com/articles', username: 'alice', - strategy: 'ssrf_filter', + strategy: 'faraday', feed_token: 'generated-token', identity_token: 'account-token' } @@ -23,7 +23,7 @@ name: 'Example Feed', url: 'https://example.com/articles', username: 'alice', - strategy: 'ssrf_filter', + strategy: 'faraday', feed_token: 'generated-token', public_url: '/api/v1/feeds/generated-token', json_public_url: '/api/v1/feeds/generated-token.json' diff --git a/spec/html2rss/web/api/v1_spec.rb b/spec/html2rss/web/api/v1_spec.rb index f7137b9f..17d52278 100644 --- a/spec/html2rss/web/api/v1_spec.rb +++ b/spec/html2rss/web/api/v1_spec.rb @@ -49,14 +49,14 @@ def ghost_feed_token .create_with_validation( username: 'ghost', url: feed_url, - strategy: 'ssrf_filter', + strategy: 'faraday', secret_key: ENV.fetch('HTML2RSS_SECRET_KEY') ) .encode end def valid_feed_token - Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'ssrf_filter') + Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'faraday') end def json_feed_response_for(token) @@ -285,7 +285,7 @@ def json_feed_headers_tuple end it 'renders feed for a valid token', :aggregate_failures do - token = Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'faraday') allow(Html2rss::Web::Feeds::Service).to receive(:call).and_return(feed_result) allow(Html2rss::Web::Feeds::RssRenderer).to receive(:call).and_return('') @@ -305,7 +305,7 @@ def json_feed_headers_tuple end it 'prefers xml when Accept quality outranks json', :aggregate_failures do - token = Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'faraday') allow(Html2rss::Web::Feeds::Service).to receive(:call).and_return(feed_result) allow(Html2rss::Web::Feeds::RssRenderer).to receive(:call).and_return('') @@ -317,7 +317,7 @@ def json_feed_headers_tuple end it 'ignores query param strategy overrides', :aggregate_failures, openapi: false do - token = Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', feed_url, strategy: 'faraday') allow(Html2rss::Web::Feeds::Service).to receive(:call).and_return(feed_result) allow(Html2rss::Web::Feeds::RssRenderer).to receive(:call).and_return('') @@ -346,7 +346,7 @@ def json_feed_headers_tuple it 'returns forbidden when auto source is disabled', :aggregate_failures do unique_url = "#{feed_url}/disabled" - token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'faraday') ClimateControl.modify(AUTO_SOURCE_ENABLED: 'false') do get "/api/v1/feeds/#{token}", {}, { 'HTTP_ACCEPT' => 'application/xml' } @@ -359,7 +359,7 @@ def json_feed_headers_tuple it 'returns JSON Feed-shaped forbidden errors when requested through Accept', :aggregate_failures do unique_url = "#{feed_url}/disabled-json" - token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'faraday') ClimateControl.modify(AUTO_SOURCE_ENABLED: 'false') do get "/api/v1/feeds/#{token}", {}, { 'HTTP_ACCEPT' => 'application/feed+json' } @@ -372,7 +372,7 @@ def json_feed_headers_tuple it 'returns non-cacheable xml feed errors when service generation fails', :aggregate_failures do unique_url = "#{feed_url}/service-error-xml" - token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'faraday') allow(Html2rss::Web::Feeds::Service).to receive(:call).and_return(service_error_result) @@ -386,7 +386,7 @@ def json_feed_headers_tuple it 'returns non-cacheable json feed errors when service generation fails', :aggregate_failures do unique_url = "#{feed_url}/service-error-json" - token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'ssrf_filter') + token = Html2rss::Web::Auth.generate_feed_token('admin', unique_url, strategy: 'faraday') status, content_type, cache_control, title = json_feed_service_error_tuple(token) @@ -404,7 +404,7 @@ def json_feed_headers_tuple let(:request_params) do { url: feed_url, - strategy: 'ssrf_filter' + strategy: 'faraday' } end diff --git a/spec/html2rss/web/app_integration_spec.rb b/spec/html2rss/web/app_integration_spec.rb index e84b8b20..8cb2a230 100644 --- a/spec/html2rss/web/app_integration_spec.rb +++ b/spec/html2rss/web/app_integration_spec.rb @@ -59,7 +59,7 @@ Html2rss::Web::FeedToken, url: feed_url, username: account[:username], - strategy: 'ssrf_filter' + strategy: 'faraday' ) allow(Html2rss::Web::FeedToken).to receive_messages( decode: token_payload, @@ -187,7 +187,7 @@ def stub_escaped_feed_token(raw_token:, encoded_token:) Html2rss::Web::FeedToken, url: feed_url, username: account[:username], - strategy: 'ssrf_filter' + strategy: 'faraday' ) allow(Html2rss::Web::FeedToken).to receive(:decode).with(raw_token).and_return(escaped_token_payload) @@ -203,7 +203,7 @@ def stub_escaped_feed_token(raw_token:, encoded_token:) let(:request_payload) do { url: feed_url, - strategy: 'ssrf_filter' + strategy: 'faraday' } end @@ -212,7 +212,7 @@ def stub_escaped_feed_token(raw_token:, encoded_token:) id: 'feed-123', name: 'Example Feed', url: feed_url, - strategy: 'ssrf_filter', + strategy: 'faraday', feed_token: feed_token, public_url: "/api/v1/feeds/#{feed_token}", json_public_url: "/api/v1/feeds/#{feed_token}.json", diff --git a/spec/html2rss/web/boot/setup_spec.rb b/spec/html2rss/web/boot/setup_spec.rb index 32fbde0f..8682dbce 100644 --- a/spec/html2rss/web/boot/setup_spec.rb +++ b/spec/html2rss/web/boot/setup_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -require_relative '../../../../app/web/boot/setup' +require_relative '../../../../app' RSpec.describe Html2rss::Web::Boot::Setup do describe '.call!' do @@ -10,21 +10,14 @@ allow(Html2rss::Web::EnvironmentValidator).to receive(:validate_environment!) allow(Html2rss::Web::EnvironmentValidator).to receive(:validate_production_security!) allow(Html2rss::Web::Flags).to receive(:validate!) - allow(Html2rss::RequestService).to receive(:register_strategy) - allow(Html2rss::RequestService).to receive(:default_strategy_name=) - allow(Html2rss::RequestService).to receive(:unregister_strategy) end - it 'validates environment state and configures the request service', :aggregate_failures do + it 'validates environment state', :aggregate_failures do described_class.call! expect(Html2rss::Web::EnvironmentValidator).to have_received(:validate_environment!).once expect(Html2rss::Web::EnvironmentValidator).to have_received(:validate_production_security!).once expect(Html2rss::Web::Flags).to have_received(:validate!).once - expect(Html2rss::RequestService).to have_received(:register_strategy) - .with(:ssrf_filter, Html2rss::Web::SsrfFilterStrategy).once - expect(Html2rss::RequestService).to have_received(:default_strategy_name=).with(:ssrf_filter).once - expect(Html2rss::RequestService).to have_received(:unregister_strategy).with(:faraday).once end end end diff --git a/spec/html2rss/web/feeds/cache_spec.rb b/spec/html2rss/web/feeds/cache_spec.rb index eb2bacf5..0bcb2b36 100644 --- a/spec/html2rss/web/feeds/cache_spec.rb +++ b/spec/html2rss/web/feeds/cache_spec.rb @@ -13,7 +13,7 @@ feed: Object.new, site_title: 'Example', url: 'https://example.com', - strategy: 'ssrf_filter' + strategy: 'faraday' ), message: nil, ttl_seconds: 60, diff --git a/spec/html2rss/web/feeds/json_renderer_spec.rb b/spec/html2rss/web/feeds/json_renderer_spec.rb index a3a59e3a..d14fef9c 100644 --- a/spec/html2rss/web/feeds/json_renderer_spec.rb +++ b/spec/html2rss/web/feeds/json_renderer_spec.rb @@ -11,7 +11,7 @@ feed: Object.new, site_title: 'https://example.com/articles', url: 'https://example.com/articles', - strategy: 'ssrf_filter' + strategy: 'faraday' ) end let(:empty_result) do @@ -37,7 +37,7 @@ def expected_builder_args { url: 'https://example.com/articles', - strategy: 'ssrf_filter', + strategy: 'faraday', site_title: 'https://example.com/articles' } end diff --git a/spec/html2rss/web/feeds/responder_spec.rb b/spec/html2rss/web/feeds/responder_spec.rb index 2dd1d2e9..d2e51307 100644 --- a/spec/html2rss/web/feeds/responder_spec.rb +++ b/spec/html2rss/web/feeds/responder_spec.rb @@ -21,7 +21,7 @@ def resolved_source Html2rss::Web::Feeds::Contracts::ResolvedSource.new( source_kind: :token, cache_identity: 'token:abc', - generator_input: { strategy: :ssrf_filter, channel: { url: 'https://example.com' } }, + generator_input: { strategy: :faraday, channel: { url: 'https://example.com' } }, ttl_seconds: 600 ) end @@ -74,7 +74,7 @@ def resolved_source expect(Html2rss::Web::Observability).to have_received(:emit).with( event_name: 'feed.render', outcome: 'success', - details: include(strategy: :ssrf_filter, url: 'https://example.com'), + details: include(strategy: :faraday, url: 'https://example.com'), level: :info ) end diff --git a/spec/html2rss/web/feeds/rss_renderer_spec.rb b/spec/html2rss/web/feeds/rss_renderer_spec.rb index a4e7f2f9..ca07c26a 100644 --- a/spec/html2rss/web/feeds/rss_renderer_spec.rb +++ b/spec/html2rss/web/feeds/rss_renderer_spec.rb @@ -11,7 +11,7 @@ feed: Object.new, site_title: 'https://example.com/articles', url: 'https://example.com/articles', - strategy: 'ssrf_filter' + strategy: 'faraday' ) end let(:empty_result) do @@ -37,7 +37,7 @@ def expected_builder_args { url: 'https://example.com/articles', - strategy: 'ssrf_filter', + strategy: 'faraday', site_title: 'https://example.com/articles' } end diff --git a/spec/html2rss/web/feeds/service_spec.rb b/spec/html2rss/web/feeds/service_spec.rb index 834f3cc6..4f085ca4 100644 --- a/spec/html2rss/web/feeds/service_spec.rb +++ b/spec/html2rss/web/feeds/service_spec.rb @@ -11,7 +11,7 @@ source_kind: :static, cache_identity: 'example-feed:abc123', generator_input: { - strategy: :ssrf_filter, + strategy: :faraday, channel: { url: 'https://example.com/articles' }, auto_source: {} }, @@ -109,7 +109,7 @@ def expected_payload feed: feed, site_title: 'Example Feed', url: 'https://example.com/articles', - strategy: 'ssrf_filter' + strategy: 'faraday' ) end end diff --git a/spec/html2rss/web/feeds/source_resolver_spec.rb b/spec/html2rss/web/feeds/source_resolver_spec.rb index 42e431a2..838ad5b0 100644 --- a/spec/html2rss/web/feeds/source_resolver_spec.rb +++ b/spec/html2rss/web/feeds/source_resolver_spec.rb @@ -29,7 +29,6 @@ def resolved_tuple(resolved) before do allow(Html2rss::Web::LocalConfig).to receive(:find).with('legacy').and_return(config) - allow(Html2rss::RequestService).to receive(:default_strategy_name).and_return(:ssrf_filter) end it 'normalizes the static source into shared generator input', :aggregate_failures do @@ -40,7 +39,7 @@ def resolved_tuple(resolved) :static, start_with('static:legacy:'), 900, - include(params: { 'existing' => '1', 'page' => '3' }, strategy: :ssrf_filter) + include(params: { 'existing' => '1', 'page' => '3' }, strategy: :faraday) ] ) end @@ -53,6 +52,14 @@ def resolved_tuple(resolved) params: { 'existing' => '1' } ) end + + it 'preserves an explicit static strategy when configured' do + config[:strategy] = :browserless + + resolved = described_class.call(feed_request) + + expect(resolved.generator_input[:strategy]).to eq(:browserless) + end end context 'with a token request' do @@ -70,7 +77,7 @@ def resolved_tuple(resolved) Html2rss::Web::FeedToken, username: 'admin', url: 'https://example.com/private', - strategy: 'ssrf_filter' + strategy: 'faraday' ) end @@ -82,7 +89,7 @@ def resolved_tuple(resolved) allow(Html2rss::Web::UrlValidator).to receive(:url_allowed?) .with({ username: 'admin' }, 'https://example.com/private').and_return(true) allow(Html2rss::Web::AutoSource).to receive(:enabled?).and_return(true) - allow(Html2rss::RequestService).to receive(:strategy_names).and_return([:ssrf_filter]) + allow(Html2rss::RequestService).to receive(:strategy_names).and_return([:faraday]) allow(Html2rss::Web::LocalConfig).to receive(:global) .and_return({ headers: { 'User-Agent' => 'html2rss-web' } }) end @@ -92,7 +99,7 @@ def resolved_tuple(resolved) expect(resolved_tuple(resolved)).to match( [:token, start_with('token:'), 300, - include(strategy: :ssrf_filter, channel: { url: 'https://example.com/private' }, auto_source: {})] + include(strategy: :faraday, channel: { url: 'https://example.com/private' }, auto_source: {})] ) end end diff --git a/spec/smoke/docker_spec.rb b/spec/smoke/docker_spec.rb index a4ba31cd..115d2b76 100644 --- a/spec/smoke/docker_spec.rb +++ b/spec/smoke/docker_spec.rb @@ -71,7 +71,7 @@ def expect_json_feed_response(path) it 'creates a feed when provided with valid credentials', :aggregate_failures do payload = { url: feed_url, - strategy: 'ssrf_filter' + strategy: 'faraday' } response, body = post_json('/api/v1/feeds', body: payload) @@ -84,7 +84,7 @@ def expect_json_feed_response(path) payload = { url: feed_url, - strategy: 'ssrf_filter' + strategy: 'faraday' } response, body = post_json('/api/v1/feeds', @@ -101,7 +101,7 @@ def expect_json_feed_response(path) payload = { url: feed_url, - strategy: 'ssrf_filter' + strategy: 'faraday' } response, body = post_json('/api/v1/feeds',