From 4e9f188acab3ca0fa65922552239a7594b84e60d Mon Sep 17 00:00:00 2001 From: "rongjun.xie" Date: Mon, 2 Nov 2026 14:53:10 +1100 Subject: [PATCH] Add follow_location configuration to not follow redirects by default swagger_aem 4.0.x and swagger_aem_osgi 2.0.x always followed redirects, as their API clients used `opts[:follow_location] || true`, while ruby_aem relies on getting 302 responses back. swagger_aem 4.1.0 and swagger_aem_osgi 2.1.0 don't follow redirects by default and honour follow_location, so ruby_aem now passes it on every API call, configurable via the new follow_location configuration value (default: false). Co-Authored-By: Claude Opus 5.5 --- Gemfile | 4 ++-- Gemfile.lock | 12 ++++++------ README.md | 3 ++- lib/ruby_aem.rb | 6 +++++- lib/ruby_aem/client.rb | 7 +++++-- ruby_aem.gemspec | 4 ++-- test/unit/client_spec.rb | 12 ++++++------ test/unit/ruby_aem_spec.rb | 38 +++++++++++++++++++++++++++++++++++++ test/unit/spec_yaml_spec.rb | 11 ++++++----- 9 files changed, 72 insertions(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index ae2307a..2137592 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,8 @@ source 'https://rubygems.org' gem 'openssl', '4.0.2' gem 'retries', '0.0.5' gem 'rexml', '3.4.4' -gem 'swagger_aem', '4.0.1' -gem 'swagger_aem_osgi', '2.0.1' +gem 'swagger_aem', '4.1.0' +gem 'swagger_aem_osgi', '2.1.0' gem 'rspec', '3.13.2', require: false gem 'rubocop', '1.91.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 4851d30..5bbd06f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,9 +61,9 @@ GEM prism (~> 1.7) ruby-progressbar (1.13.0) simplecov (1.3.2) - swagger_aem (4.0.1) + swagger_aem (4.1.0) typhoeus (~> 1.0, >= 1.0.1) - swagger_aem_osgi (2.0.1) + swagger_aem_osgi (2.1.0) typhoeus (~> 1.0, >= 1.0.1) typhoeus (1.6.0) ethon (>= 0.18.0) @@ -92,8 +92,8 @@ DEPENDENCIES rspec (= 3.13.2) rubocop (= 1.91.0) simplecov (= 1.3.2) - swagger_aem (= 4.0.1) - swagger_aem_osgi (= 2.0.1) + swagger_aem (= 4.1.0) + swagger_aem_osgi (= 2.1.0) yard (= 0.9.45) CHECKSUMS @@ -134,8 +134,8 @@ CHECKSUMS rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 simplecov (1.3.2) sha256=2a22ec1759f1b8db60881a2e038758956fc69ef93ed669be0bde0678de6d0e85 - swagger_aem (4.0.1) sha256=45bbb45cb987449e7909bca81b75f9aa842dd60ec6ef0360da8328c90e8ce271 - swagger_aem_osgi (2.0.1) sha256=ee9ea86d3fa03f68ba95a9094b79976bdffdcd8d0ca8105fcbc8f23db63ae1f0 + swagger_aem (4.1.0) sha256=75e6a0b029a976ffd9a01b0b06cf4ceb8d79a7dbece4960ee0566e4d00beb4df + swagger_aem_osgi (2.1.0) sha256=72cefe078d86831585c9ad137a4fed6bfafbf81946cbe33f466c75689820f944 typhoeus (1.6.0) sha256=bacc41c23e379547e29801dc235cd1699b70b955a1ba3d32b2b877aa844c331d unicode-display_width (3.3.0) sha256=4b7aa66a4b11db50f6f7e98411215cd0dd10eaecb665d36eec5bcacb3fa0b613 unicode-emoji (4.3.0) sha256=11c02fa73290378c066bb0562cd4c87d8e1b706fbbe0059ca12746a4244de8ce diff --git a/README.md b/README.md index d0e7216..60af9dc 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ aem = RubyAem::Aem.new({ port: 4502, timeout: 300, verify_ssl: true, - debug: false + debug: false, + follow_location: false }) ``` diff --git a/lib/ruby_aem.rb b/lib/ruby_aem.rb index 9735ad2..8d6d9b9 100644 --- a/lib/ruby_aem.rb +++ b/lib/ruby_aem.rb @@ -50,6 +50,7 @@ class Aem # - port: AEM instance port, default: 4502 # - timeout: connection timeout in seconds, default: 300 seconds # - debug: if true, then additional debug messages will be included, default: false + # - follow_location: if true, then HTTP redirects will be followed, default: false # @return new RubyAem::Aem instance def initialize(conf = {}) sanitise_conf(conf) @@ -94,7 +95,7 @@ def initialize(conf = {}) spec = YAML.load_file(File.expand_path('../../conf/spec.yaml', __FILE__)) - @client = RubyAem::Client.new(apis, spec) + @client = RubyAem::Client.new(apis, spec, follow_location: conf[:follow_location]) end # Set default configuration values and handle numeric/boolean String values @@ -107,12 +108,15 @@ def sanitise_conf(conf) conf[:host] ||= 'localhost' conf[:port] ||= 4502 conf[:timeout] ||= 300 + # don't follow redirects by default, ruby_aem relies on getting 302 responses back + conf[:follow_location] ||= false # handle custom configuration value being passed as a String # e.g. when the values are passed via environment variables conf[:port] = conf[:port].to_i conf[:timeout] = conf[:timeout].to_i conf[:verify_ssl] = conf[:verify_ssl] == 'true' if conf[:verify_ssl].is_a? String conf[:debug] = conf[:debug] == 'true' if conf[:debug].is_a? String + conf[:follow_location] = conf[:follow_location] == 'true' if conf[:follow_location].is_a? String end # Create an AEM instance. diff --git a/lib/ruby_aem/client.rb b/lib/ruby_aem/client.rb index 89ed5eb..1d888de 100644 --- a/lib/ruby_aem/client.rb +++ b/lib/ruby_aem/client.rb @@ -30,10 +30,12 @@ class Client # # @param apis a hash of Swagger AEM client's API instances # @param spec ruby_aem specification + # @param follow_location if true, then HTTP redirects will be followed, default: false # @return new RubyAem::Client instance - def initialize(apis, spec) + def initialize(apis, spec, follow_location: false) @apis = apis @spec = spec + @follow_location = follow_location end # Make an API call using the relevant Swagger AEM API client. @@ -59,7 +61,8 @@ def call(clazz, action, call_params) required_params.each_value { |value| params.push(value % call_params) } - params.push({}) + # Swagger AEM API clients take follow_location as a per-call option, not a configuration value + params.push({ follow_location: @follow_location }) optional_params = action_spec['params']['optional'] || {} optional_params.each { |key, value| add_optional_param(key, value, params, call_params) diff --git a/ruby_aem.gemspec b/ruby_aem.gemspec index fd0692a..5e2de19 100644 --- a/ruby_aem.gemspec +++ b/ruby_aem.gemspec @@ -19,6 +19,6 @@ Gem::Specification.new do |s| s.add_dependency 'retries', '0.0.5' s.add_dependency 'rexml', '3.4.4' - s.add_dependency 'swagger_aem', '4.0.1' - s.add_dependency 'swagger_aem_osgi', '2.0.1' + s.add_dependency 'swagger_aem', '4.1.0' + s.add_dependency 'swagger_aem_osgi', '2.1.0' end diff --git a/test/unit/client_spec.rb b/test/unit/client_spec.rb index 30848a3..9a8986e 100644 --- a/test/unit/client_spec.rb +++ b/test/unit/client_spec.rb @@ -35,7 +35,7 @@ expect(mock_class).to receive(:name).once.and_return('RubyAem::Resources::Bundle') mock_api = double('mock_api') - expect(mock_api).to receive(:post_bundle_with_http_info).once.with('somebundle', 'start', {}).and_return(['some data', 200, {}]) + expect(mock_api).to receive(:post_bundle_with_http_info).once.with('somebundle', 'start', { follow_location: false }).and_return(['some data', 200, {}]) apis = { console: mock_api } client = RubyAem::Client.new(apis, @spec) @@ -49,7 +49,7 @@ expect(mock_class).to receive(:name).once.and_return('RubyAem::Resources::Bundle') mock_api = double('mock_api') - expect(mock_api).to receive(:post_bundle_with_http_info).once.with('somebundle', 'start', {}).and_raise(mock_error) + expect(mock_api).to receive(:post_bundle_with_http_info).once.with('somebundle', 'start', { follow_location: false }).and_raise(mock_error) apis = { console: mock_api } client = RubyAem::Client.new(apis, @spec) @@ -85,7 +85,7 @@ expect(mock_class).to receive(:name).once.and_return('RubyAem::Resources::Bundle') mock_api = double('mock_api') - expect(mock_api).to receive(:post_bundle_with_http_info).once.with({ optional1: 'value1', optional2: 'value2' }).and_return(['some data', 200, {}]) + expect(mock_api).to receive(:post_bundle_with_http_info).once.with({ follow_location: false, optional1: 'value1', optional2: 'value2' }).and_return(['some data', 200, {}]) apis = { console: mock_api } client = RubyAem::Client.new(apis, spec) @@ -121,7 +121,7 @@ expect(mock_class).to receive(:name).once.and_return('RubyAem::Resources::Bundle') mock_api = double('mock_api') - expect(mock_api).to receive(:post_bundle_with_http_info).once.with({ optional1: true, optional2: false }).and_return(['some data', 200, {}]) + expect(mock_api).to receive(:post_bundle_with_http_info).once.with({ follow_location: false, optional1: true, optional2: false }).and_return(['some data', 200, {}]) apis = { console: mock_api } client = RubyAem::Client.new(apis, spec) @@ -157,7 +157,7 @@ expect(mock_class).to receive(:name).once.and_return('RubyAem::Resources::Bundle') mock_api = double('mock_api') - expect(mock_api).to receive(:post_bundle_with_http_info).once.with({ optional1: 'value1', optional2: 'value2' }).and_return(['some data', 200, {}]) + expect(mock_api).to receive(:post_bundle_with_http_info).once.with({ follow_location: false, optional1: 'value1', optional2: 'value2' }).and_return(['some data', 200, {}]) apis = { console: mock_api } client = RubyAem::Client.new(apis, spec) @@ -205,7 +205,7 @@ mock_api = double('mock_api') expect(mock_api).to receive(:post_bundle_with_http_info) - .once.with({ optional1: mock_file_package, optional2: mock_file_plain, optional3: mock_file_certificate, optional4: mock_file_private_key }) + .once.with({ follow_location: false, optional1: mock_file_package, optional2: mock_file_plain, optional3: mock_file_certificate, optional4: mock_file_private_key }) .and_return(['some data', 200, {}]) apis = { console: mock_api } diff --git a/test/unit/ruby_aem_spec.rb b/test/unit/ruby_aem_spec.rb index 4174b0b..9adac21 100644 --- a/test/unit/ruby_aem_spec.rb +++ b/test/unit/ruby_aem_spec.rb @@ -168,4 +168,42 @@ expect(user).to_not be(nil) end end + + # Make real Swagger AEM and Swagger AEM OSGI API calls, with stubbed HTTP responses, + # to confirm that they pass follow_location through to the HTTP request + describe 'test follow_location' do + before do + Typhoeus.stub(/.*/).and_return(Typhoeus::Response.new(code: 200, body: '', headers: {})) + @followlocations = [] + allow(Typhoeus::Request).to(receive(:new).and_wrap_original { |original, url, opts| + @followlocations.push(opts[:followlocation]) + original.call(url, opts) + }) + end + + after do + Typhoeus::Expectation.clear + end + + it 'should not follow redirects by default' do + aem = RubyAem::Aem.new + aem.bundle('somebundle').start + aem.saml.get + expect(@followlocations).to eq([false, false]) + end + + it 'should follow redirects when follow_location is true' do + aem = RubyAem::Aem.new(follow_location: true) + aem.bundle('somebundle').start + aem.saml.get + expect(@followlocations).to eq([true, true]) + end + + it 'should follow redirects when follow_location is a true String' do + aem = RubyAem::Aem.new(follow_location: 'true') + aem.bundle('somebundle').start + aem.saml.get + expect(@followlocations).to eq([true, true]) + end + end end diff --git a/test/unit/spec_yaml_spec.rb b/test/unit/spec_yaml_spec.rb index 5cdc18c..1cf3390 100644 --- a/test/unit/spec_yaml_spec.rb +++ b/test/unit/spec_yaml_spec.rb @@ -58,6 +58,7 @@ def swagger_opts_keys(method) 'author', 'some-flush-agent', { + follow_location: false, jcr_primary_type: 'cq:Page', jcr_content_cq_name: 'some-flush-agent', jcr_content_jcr_title: 'Some Flush Agent Title', @@ -86,28 +87,28 @@ def swagger_opts_keys(method) end it 'should pass _operation param when deleting replication agent' do - expect(@mock_api).to receive(:post_agent_with_http_info).once.with('author', 'some-replication-agent', { _operation: 'delete' }).and_return([nil, 204, {}]) + expect(@mock_api).to receive(:post_agent_with_http_info).once.with('author', 'some-replication-agent', { follow_location: false, _operation: 'delete' }).and_return([nil, 204, {}]) replication_agent = RubyAem::Resources::ReplicationAgent.new(@client, 'author', 'some-replication-agent') result = replication_agent.delete expect(result.message).to eq('Replication agent some-replication-agent deleted on author') end it 'should pass _operation param when creating truststore' do - expect(@mock_api).to receive(:post_truststore_with_http_info).once.with({ new_password: 'somepassword', re_password: 'somepassword', _operation: 'createStore' }).and_return([nil, 200, {}]) + expect(@mock_api).to receive(:post_truststore_with_http_info).once.with({ follow_location: false, new_password: 'somepassword', re_password: 'somepassword', _operation: 'createStore' }).and_return([nil, 200, {}]) truststore = RubyAem::Resources::Truststore.new(@client) result = truststore.create('somepassword') expect(result.message).to eq('Truststore created') end it 'should pass rep_password param when creating user' do - expect(@mock_api).to receive(:post_authorizables_with_http_info).once.with('someuser', '/home/users/s/', { create_user: 'true', rep_password: 'somepassword' }).and_return([nil, 201, {}]) + expect(@mock_api).to receive(:post_authorizables_with_http_info).once.with('someuser', '/home/users/s/', { follow_location: false, create_user: 'true', rep_password: 'somepassword' }).and_return([nil, 201, {}]) expect(RubyAem::Handlers).to receive(:html_authorizable_id).once user = RubyAem::Resources::User.new(@client, '/home/users/s/', 'someuser') user.create('somepassword') end it 'should pass combine_tags_or param when retrieving AEM health check' do - expect(@mock_api).to receive(:get_aem_health_check_with_http_info).once.with({ tags: 'shallow', combine_tags_or: 'false' }).and_return(['{}', 200, {}]) + expect(@mock_api).to receive(:get_aem_health_check_with_http_info).once.with({ follow_location: false, tags: 'shallow', combine_tags_or: 'false' }).and_return(['{}', 200, {}]) expect(RubyAem::Handlers).to receive(:json_aem_health_check).once aem = RubyAem::Resources::Aem.new(@client) aem.get_aem_health_check(tags: 'shallow', combine_tags_or: false) @@ -117,7 +118,7 @@ def swagger_opts_keys(method) expect(@mock_api).to receive(:post_package_service_json_with_http_info).once.with( 'etc/packages/somepackagegroup', 'create', - { group_name: 'somepackagegroup', package_name: 'somepackage', package_version: '1.2.3', _charset_: 'utf-8' } + { follow_location: false, group_name: 'somepackagegroup', package_name: 'somepackage', package_version: '1.2.3', _charset_: 'utf-8' } ).and_return(['{}', 200, {}]) expect(RubyAem::Handlers).to receive(:json_package_service).once package = RubyAem::Resources::Package.new(@client, 'somepackagegroup', 'somepackage', '1.2.3')