From 5b09a97c805016028afc55d37f012d6fc1d8d22c Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Wed, 12 Jun 2024 09:34:48 +0200 Subject: [PATCH] Allow registering additional headers for published entries REDMINE-20674 --- .../pageflow/entries_controller.rb | 15 ++-- .../lib/pageflow_scrolled/configuration.rb | 8 +-- lib/pageflow/additional_headers.rb | 27 +++++++ lib/pageflow/configuration.rb | 24 ++++++- spec/pageflow/additional_headers_spec.rb | 32 +++++++++ .../pageflow/entries_show_request_spec.rb | 71 +++++++++++++++++++ 6 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 lib/pageflow/additional_headers.rb create mode 100644 spec/pageflow/additional_headers_spec.rb diff --git a/app/controllers/pageflow/entries_controller.rb b/app/controllers/pageflow/entries_controller.rb index 40ef11345..c018c1975 100644 --- a/app/controllers/pageflow/entries_controller.rb +++ b/app/controllers/pageflow/entries_controller.rb @@ -84,8 +84,11 @@ def delegate_to_entry_type_frontend_app!(entry) EntriesControllerEnvHelper.add_entry_info_to_env(request.env, entry: entry, mode: :published) delegate_to_rack_app!(entry.entry_type.frontend_app) do |_status, headers, _body| + config = Pageflow.config_for(entry) + allow_iframe_for_embed(headers) - apply_cache_control(entry, headers) + apply_additional_headers(entry, config, headers) + apply_cache_control(entry, config, headers) end end @@ -93,13 +96,17 @@ def allow_iframe_for_embed(headers) headers.except!('X-Frame-Options') if params[:embed] end - def apply_cache_control(entry, headers) - config = Pageflow.config_for(entry) - + def apply_cache_control(entry, config, headers) return if config.public_entry_cache_control_header.blank? return if entry.password_protected? headers['Cache-Control'] = config.public_entry_cache_control_header end + + def apply_additional_headers(entry, config, headers) + headers.merge!( + config.additional_public_entry_headers.for(entry, request) + ) + end end end diff --git a/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb b/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb index 3f4e5da6b..15068faf3 100644 --- a/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb +++ b/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb @@ -10,7 +10,7 @@ class Configuration # @example # # config.additional_frontend_packs.register( - # pageflow-scrolled/contentElements/some-pack', + # 'pageflow-scrolled/contentElements/some-pack', # content_element_type_names: ['someType'] # ) # @@ -23,7 +23,7 @@ class Configuration # @example # # config.additional_editor_packs.register( - # pageflow-scrolled/contentElements/some-pack' + # 'pageflow-scrolled/contentElements/some-pack' # ) # # @return [AdditionalPacks] @@ -36,8 +36,8 @@ class Configuration # @example # # config.additional_frontend_seed_data.register( - # pageflow-scrolled/contentElements/some-pack', - # ->(entry, request) { {some: 'data'}} + # 'someData', + # ->(entry:, request:, **) { {some: 'data'}} # ) # # @return [AdditionalSeedData] diff --git a/lib/pageflow/additional_headers.rb b/lib/pageflow/additional_headers.rb new file mode 100644 index 000000000..967692a0c --- /dev/null +++ b/lib/pageflow/additional_headers.rb @@ -0,0 +1,27 @@ +module Pageflow + # Register additional response headers for published entries. + class AdditionalHeaders + # @api private + def initialize + @headers = [] + end + + # Either a hash of name values pair or a callable taking a + # {PublishedEntry} record and an {ActionDispatch::Request} object + # and returns a hash. + def register(headers) + @headers << headers + end + + # @api private + def for(entry, request) + @headers.map { |headers| + if headers.respond_to?(:call) + headers.call(entry, request) + else + headers + end + }.reduce({}, :merge) + end + end +end diff --git a/lib/pageflow/configuration.rb b/lib/pageflow/configuration.rb index 841817fa8..de834d1bf 100644 --- a/lib/pageflow/configuration.rb +++ b/lib/pageflow/configuration.rb @@ -187,8 +187,8 @@ class Configuration attr_accessor :public_entry_request_scope # Either a lambda or an object with a `call` method taking an - # {Entry} record and an {ActionDispatch::Request} object and - # returning `nil` or a path to redirect to. Can be used in + # {PublishedEntry} record and an {ActionDispatch::Request} object + # and returning `nil` or a path to redirect to. Can be used in # conjuction with {PrimaryDomainEntryRedirect} to make sure # entries are accessed via their account's configured cname. # @@ -208,6 +208,24 @@ class Configuration # @return [String] attr_accessor :public_entry_cache_control_header + # Provide additional response headers for published entries. + # + # @example + # + # config.additional_public_entry_headers.register( + # {'Some' => 'value'} + # ) + # + # config.additional_public_entry_headers.register( + # lambda do |_entry, request| + # {'Some' => request.headers['Other']} + # end + # ) + # + # @return [AdditionalHeaders] + # @since edge + attr_reader :additional_public_entry_headers + # Either a lambda or an object with a `call` method taking a # {Site} as paramater and returing a hash of options used to # construct the url of a published entry. @@ -435,6 +453,7 @@ def initialize(target_type_name = nil) @site_request_scope = CnameSiteRequestScope.new @public_entry_request_scope = lambda { |entries, request| entries } @public_entry_redirect = ->(_entry, _request) { nil } + @additional_public_entry_headers = AdditionalHeaders.new @public_entry_url_options = Pageflow::SitesHelper::DEFAULT_PUBLIC_ENTRY_OPTIONS @entry_embed_url_options = {protocol: 'https'} @@ -578,6 +597,7 @@ def enable_all_features delegate :themes, to: :config delegate :widget_types, to: :config delegate :public_entry_cache_control_header=, to: :config + delegate :additional_public_entry_headers, to: :config delegate :for_entry_type, to: :config end diff --git a/spec/pageflow/additional_headers_spec.rb b/spec/pageflow/additional_headers_spec.rb new file mode 100644 index 000000000..14a95f5af --- /dev/null +++ b/spec/pageflow/additional_headers_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +module Pageflow + describe AdditionalHeaders do + it 'returns empty hash by default' do + additional_headers = AdditionalHeaders.new + entry = create(:published_entry) + + result = additional_headers.for(entry, request) + + expect(result).to eq({}) + end + + it 'merges multiple registered hashes of headers' do + additional_headers = AdditionalHeaders.new + entry = create(:published_entry) + + additional_headers.register('Some' => 'value') + additional_headers.register('Other' => 'header') + additional_headers.register(proc { {'Dynamic' => 'header'} }) + result = additional_headers.for(entry, request) + + expect(result).to eq('Some' => 'value', + 'Other' => 'header', + 'Dynamic' => 'header') + end + + def request(uri = 'https://example.com') + ActionDispatch::Request.new(Rack::MockRequest.env_for(uri)) + end + end +end diff --git a/spec/requests/pageflow/entries_show_request_spec.rb b/spec/requests/pageflow/entries_show_request_spec.rb index ebdc12655..64aacff5a 100644 --- a/spec/requests/pageflow/entries_show_request_spec.rb +++ b/spec/requests/pageflow/entries_show_request_spec.rb @@ -395,6 +395,77 @@ module Pageflow expect(response.headers['Cache-Control']).to eq('public, max-age=3600') end end + + describe 'with additional headers' do + it 'adds headers to responds' do + pageflow_configure do |config| + config.additional_public_entry_headers.register('X-Some' => 'value') + end + + entry = create(:entry, :published, + type_name: 'test') + + get(short_entry_url(entry)) + + expect(response.status).to eq(200) + expect(response.headers['X-Some']).to eq('value') + end + + it 'passes entry and request to callable' do + pageflow_configure do |config| + config.additional_public_entry_headers.register( + lambda do |entry, request| + { + 'X-From-Entry' => entry.type_name, + 'X-From-Request' => request.subdomain + } + end + ) + end + + entry = create(:entry, :published, + type_name: 'test') + + get(short_entry_url(entry), headers: {'HTTP_HOST' => 'news.example.com'}) + + expect(response.status).to eq(200) + expect(response.headers['X-From-Entry']).to eq('test') + expect(response.headers['X-From-Request']).to eq('news') + end + + it 'does not use headers registered in feature flag by default' do + pageflow_configure do |config| + config.features.register('some_header') do |feature_config| + feature_config.additional_public_entry_headers.register('X-Some' => 'value') + end + end + + entry = create(:entry, :published, + type_name: 'test') + + get(short_entry_url(entry)) + + expect(response.status).to eq(200) + expect(response.headers).not_to have_key('X-Some') + end + + it 'uses headers registered in enabled feature flag' do + pageflow_configure do |config| + config.features.register('some_header') do |feature_config| + feature_config.additional_public_entry_headers.register('X-Some' => 'value') + end + end + + entry = create(:entry, :published, + type_name: 'test', + with_feature: 'some_header') + + get(short_entry_url(entry)) + + expect(response.status).to eq(200) + expect(response.headers['X-Some']).to eq('value') + end + end end end end