From 788aad69c0a735464c546914dc0f54b355532de8 Mon Sep 17 00:00:00 2001 From: Alexander Fisher Date: Tue, 14 Apr 2020 14:26:47 +0100 Subject: [PATCH] Finish conversion of `postgresql_escape` function In https://github.com/puppetlabs/puppetlabs-postgresql/pull/1129 the 3 functions from this module were ported to the modern function API and namespaced. None of the boilerplate was fixed up, tests weren't updated, documentation wasn't updated etc. This commit finishes the work for the `postgresql_escape` function. * Remove old API puppet/parser/functions version. * Create a replacement non-namespaced, (but deprecated), new API shim that calls the namespaced version. This function is *probably* not being used outside of this module, but it doesn't hurt to get the old version around for now. * Replace argument validation in the function code with data-type based validation for the individual function parameters. * Refactor function for increased readability. * Test both shim and namespaced version of the function. * Update `postgresql::server` to use namespaced version. * Update REFERENCE.md --- REFERENCE.md | 41 +++++++------ .../functions/postgresql/postgresql_escape.rb | 58 ++++++------------- lib/puppet/functions/postgresql_escape.rb | 10 ++++ .../parser/functions/postgresql_escape.rb | 29 ---------- manifests/server/passwd.pp | 4 +- spec/functions/postgresql_escape_spec.rb | 5 ++ .../postgresql_postgresql_escape_spec.rb | 37 +----------- spec/spec_helper_local.rb | 20 +++++++ spec/unit/functions/postgresql_escape_spec.rb | 27 --------- 9 files changed, 76 insertions(+), 155 deletions(-) create mode 100644 lib/puppet/functions/postgresql_escape.rb delete mode 100644 lib/puppet/parser/functions/postgresql_escape.rb create mode 100644 spec/functions/postgresql_escape_spec.rb delete mode 100644 spec/unit/functions/postgresql_escape_spec.rb diff --git a/REFERENCE.md b/REFERENCE.md index 428f5fa9a3..b03fc8aca3 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -64,9 +64,9 @@ _Private Classes_ _Public Functions_ * [`postgresql::default`](#postgresqldefault): This function pull default values from the `params` class or `globals` class if the value is not present in `params`. -* [`postgresql::postgresql_escape`](#postgresqlpostgresql_escape): This function safely escapes a string using a consistent random tag +* [`postgresql::postgresql_escape`](#postgresqlpostgresql_escape): This function escapes a string using [Dollar Quoting](https://www.postgresql.org/docs/12/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING) using a randomly generated tag if required. * [`postgresql::postgresql_password`](#postgresqlpostgresql_password): This function returns the postgresql password hash from the clear text username / password -* [`postgresql_escape`](#postgresql_escape): This function safely escapes a string using a consistent random tag +* [`postgresql_escape`](#postgresql_escape): DEPRECATED. Use the namespaced function [`postgresql::postgresql_escape`](#postgresqlpostgresql_escape) instead. * [`postgresql_password`](#postgresql_password): DEPRECATED. Use the namespaced function [`postgresql::postgresql_password`](#postgresqlpostgresql_password) instead. _Private Functions_ @@ -2787,26 +2787,19 @@ Data type: `String` Type: Ruby 4.x API -postgresql_escape.rb ----- original file header ---- +This function escapes a string using [Dollar Quoting](https://www.postgresql.org/docs/12/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING) using a randomly generated tag if required. - @return Safely escapes a string using $$ using a random tag which should be consistent +#### `postgresql::postgresql_escape(String[1] $input_string)` -#### `postgresql::postgresql_escape(Any *$args)` +The postgresql::postgresql_escape function. -postgresql_escape.rb ----- original file header ---- +Returns: `String` A `Dollar Quoted` string - @return Safely escapes a string using $$ using a random tag which should be consistent +##### `input_string` -Returns: `Data type` Describe what the function returns here - -##### `*args` - -Data type: `Any` +Data type: `String[1]` -The original array of arguments. Port this to individually managed params -to get the full benefit of the modern function API. +The unescaped string you want to escape using `dollar quoting` ### postgresql::postgresql_password @@ -2834,15 +2827,21 @@ The clear text `password` ### postgresql_escape -Type: Ruby 3.x API +Type: Ruby 4.x API + +DEPRECATED. Use the namespaced function [`postgresql::postgresql_escape`](#postgresqlpostgresql_escape) instead. -This function safely escapes a string using a consistent random tag +#### `postgresql_escape(Any *$args)` -#### `postgresql_escape()` +The postgresql_escape function. + +Returns: `Any` + +##### `*args` + +Data type: `Any` -This function safely escapes a string using a consistent random tag -Returns: `Any` Safely escapes a string using $$ using a random tag which should be consistent ### postgresql_password diff --git a/lib/puppet/functions/postgresql/postgresql_escape.rb b/lib/puppet/functions/postgresql/postgresql_escape.rb index 1555af0783..223ad7018f 100644 --- a/lib/puppet/functions/postgresql/postgresql_escape.rb +++ b/lib/puppet/functions/postgresql/postgresql_escape.rb @@ -1,52 +1,30 @@ -# This is an autogenerated function, ported from the original legacy version. -# It /should work/ as is, but will not have all the benefits of the modern -# function API. You should see the function docs to learn how to add function -# signatures for type safety and to document this function using puppet-strings. -# -# https://puppet.com/docs/puppet/latest/custom_functions_ruby.html -# -# ---- original file header ---- require 'digest/md5' -# postgresql_escape.rb -# ---- original file header ---- -# -# @summary -# This function safely escapes a string using a consistent random tag -# @return Safely escapes a string using $$ using a random tag which should be consistent -# -# +# @summary This function escapes a string using [Dollar Quoting](https://www.postgresql.org/docs/12/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING) using a randomly generated tag if required. Puppet::Functions.create_function(:'postgresql::postgresql_escape') do - # @param args - # The original array of arguments. Port this to individually managed params - # to get the full benefit of the modern function API. - # - # @return [Data type] - # Describe what the function returns here + # @param input_string + # The unescaped string you want to escape using `dollar quoting` # + # @return [String] + # A `Dollar Quoted` string dispatch :default_impl do - # Call the method named 'default_impl' when this is matched - # Port this to match individual params for better type safety - repeated_param 'Any', :args + param 'String[1]', :input_string end - def default_impl(*args) - if args.size != 1 - raise(Puppet::ParseError, 'postgresql_escape(): Wrong number of arguments ' \ - "given (#{args.size} for 1)") + def default_impl(input_string) + # Where allowed, just return the original string wrapped in `$$` + return "$$#{input_string}$$" unless tag_needed?(input_string) + + # Keep generating possible values for tag until we find one that doesn't appear in the input string + tag = Digest::MD5.hexdigest(input_string)[0..5].gsub(%r{\d}, '') + until input_string !~ %r{#{tag}} + tag = Digest::MD5.hexdigest(tag)[0..5].gsub(%r{\d}, '') end - password = args[0] + "$#{tag}$#{input_string}$#{tag}$" + end - if password !~ %r{\$\$} && password[-1] != '$' - retval = "$$#{password}$$" - else - escape = Digest::MD5.hexdigest(password)[0..5].gsub(%r{\d}, '') - until password !~ %r{#{escape}} - escape = Digest::MD5.hexdigest(escape)[0..5].gsub(%r{\d}, '') - end - retval = "$#{escape}$#{password}$#{escape}$" - end - retval + def tag_needed?(input_string) + input_string =~ %r{\$\$} || input_string.end_with?('$') end end diff --git a/lib/puppet/functions/postgresql_escape.rb b/lib/puppet/functions/postgresql_escape.rb new file mode 100644 index 0000000000..1e366d2d06 --- /dev/null +++ b/lib/puppet/functions/postgresql_escape.rb @@ -0,0 +1,10 @@ +# @summary DEPRECATED. Use the namespaced function [`postgresql::postgresql_escape`](#postgresqlpostgresql_escape) instead. +Puppet::Functions.create_function(:postgresql_escape) do + dispatch :deprecation_gen do + repeated_param 'Any', :args + end + def deprecation_gen(*args) + call_function('deprecation', 'postgresql_escape', 'This method is deprecated, please use postgresql::postgresql_escape instead.') + call_function('postgresql::postgresql_escape', *args) + end +end diff --git a/lib/puppet/parser/functions/postgresql_escape.rb b/lib/puppet/parser/functions/postgresql_escape.rb deleted file mode 100644 index f7e6f33add..0000000000 --- a/lib/puppet/parser/functions/postgresql_escape.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'digest/md5' - -# postgresql_escape.rb -module Puppet::Parser::Functions - newfunction(:postgresql_escape, type: :rvalue, doc: <<-EOS - This function safely escapes a string using a consistent random tag - @return Safely escapes a string using $$ using a random tag which should be consistent - EOS - ) do |args| - - if args.size != 1 - raise(Puppet::ParseError, 'postgresql_escape(): Wrong number of arguments ' \ - "given (#{args.size} for 1)") - end - - password = args[0] - - if password !~ %r{\$\$} && password[-1] != '$' - retval = "$$#{password}$$" - else - escape = Digest::MD5.hexdigest(password)[0..5].gsub(%r{\d}, '') - until password !~ %r{#{escape}} - escape = Digest::MD5.hexdigest(escape)[0..5].gsub(%r{\d}, '') - end - retval = "$#{escape}$#{password}$#{escape}$" - end - retval - end -end diff --git a/manifests/server/passwd.pp b/manifests/server/passwd.pp index a9477fd0f3..a2f52ba18b 100644 --- a/manifests/server/passwd.pp +++ b/manifests/server/passwd.pp @@ -15,12 +15,12 @@ default => '' } - if ($postgres_password != undef) { + if $postgres_password { # NOTE: this password-setting logic relies on the pg_hba.conf being # configured to allow the postgres system user to connect via psql # without specifying a password ('ident' or 'trust' security). This is # the default for pg_hba.conf. - $escaped = postgresql_escape($postgres_password) + $escaped = postgresql::postgresql_escape($postgres_password) exec { 'set_postgres_postgrespw': # This command works w/no password because we run it as postgres system # user diff --git a/spec/functions/postgresql_escape_spec.rb b/spec/functions/postgresql_escape_spec.rb new file mode 100644 index 0000000000..a00f05eecd --- /dev/null +++ b/spec/functions/postgresql_escape_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe 'postgresql_escape' do + it_behaves_like 'postgresql_escape function' +end diff --git a/spec/functions/postgresql_postgresql_escape_spec.rb b/spec/functions/postgresql_postgresql_escape_spec.rb index 947ee72423..329ae8521a 100644 --- a/spec/functions/postgresql_postgresql_escape_spec.rb +++ b/spec/functions/postgresql_postgresql_escape_spec.rb @@ -1,40 +1,5 @@ require 'spec_helper' describe 'postgresql::postgresql_escape' do - # without knowing details about the implementation, this is the only test - # case that we can autogenerate. You should add more examples below! - it { is_expected.not_to eq(nil) } - - ################################# - # Below are some example test cases. You may uncomment and modify them to match - # your needs. Notice that they all expect the base error class of `StandardError`. - # This is because the autogenerated function uses an untyped array for parameters - # and relies on your implementation to do the validation. As you convert your - # function to proper dispatches and typed signatures, you should change the - # expected error of the argument validation examples to `ArgumentError`. - # - # Other error types you might encounter include - # - # * StandardError - # * ArgumentError - # * Puppet::ParseError - # - # Read more about writing function unit tests at https://rspec-puppet.com/documentation/functions/ - # - # it 'raises an error if called with no argument' do - # is_expected.to run.with_params.and_raise_error(StandardError) - # end - # - # it 'raises an error if there is more than 1 arguments' do - # is_expected.to run.with_params({ 'foo' => 1 }, 'bar' => 2).and_raise_error(StandardError) - # end - # - # it 'raises an error if argument is not the proper type' do - # is_expected.to run.with_params('foo').and_raise_error(StandardError) - # end - # - # it 'returns the proper output' do - # is_expected.to run.with_params(123).and_return('the expected output') - # end - ################################# + it_behaves_like 'postgresql_escape function' end diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb index ba834774b1..a5ed86af90 100644 --- a/spec/spec_helper_local.rb +++ b/spec/spec_helper_local.rb @@ -55,3 +55,23 @@ def param(type, title, param) is_expected.to run.with_params('foo').and_raise_error(StandardError) end end + +shared_examples 'postgresql_escape function' do + it { is_expected.not_to eq(nil) } + it { + is_expected.to run.with_params('foo') + .and_return('$$foo$$') + } + it { + is_expected.to run.with_params('fo$$o') + .and_return('$ed$fo$$o$ed$') + } + it { + is_expected.to run.with_params('foo$') + .and_return('$a$foo$$a$') + } + it 'raises an error if there is more than 1 argument' do + is_expected.to run.with_params(['foo'], ['foo']) + .and_raise_error(StandardError) + end +end diff --git a/spec/unit/functions/postgresql_escape_spec.rb b/spec/unit/functions/postgresql_escape_spec.rb deleted file mode 100644 index b512c0cda9..0000000000 --- a/spec/unit/functions/postgresql_escape_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' -describe 'postgresql_escape' do - describe 'postgresql_escape', type: :puppet_function do - it { - is_expected.to run.with_params('foo') - .and_return('$$foo$$') - } - end - describe 'postgresql_escape', type: :puppet_function do - it { - is_expected.to run.with_params('fo$$o') - .and_return('$ed$fo$$o$ed$') - } - end - describe 'postgresql_escape', type: :puppet_function do - it { - is_expected.to run.with_params('foo$') - .and_return('$a$foo$$a$') - } - end - describe 'postgresql_escape', type: :puppet_function do - it { - is_expected.to run.with_params(['foo'], ['foo']) - .and_raise_error(%r{Wrong number of arguments}) - } - end -end