diff options
-rw-r--r-- | acceptance/tests/ticket_6857_password-disclosure-when-changing-a-users-password.rb | 23 | ||||
-rw-r--r-- | lib/puppet/face/status.rb | 1 | ||||
-rw-r--r-- | lib/puppet/interface/action.rb | 9 | ||||
-rw-r--r-- | lib/puppet/interface/action_manager.rb | 15 | ||||
-rw-r--r-- | lib/puppet/interface/option.rb | 19 | ||||
-rw-r--r-- | lib/puppet/interface/option_builder.rb | 13 | ||||
-rwxr-xr-x | lib/puppet/type/user.rb | 8 | ||||
-rwxr-xr-x | spec/shared_behaviours/things_that_declare_options.rb | 113 | ||||
-rwxr-xr-x | spec/unit/interface/action_spec.rb | 63 | ||||
-rwxr-xr-x | spec/unit/interface/option_spec.rb | 44 | ||||
-rwxr-xr-x | spec/unit/type/user_spec.rb | 8 |
11 files changed, 311 insertions, 5 deletions
diff --git a/acceptance/tests/ticket_6857_password-disclosure-when-changing-a-users-password.rb b/acceptance/tests/ticket_6857_password-disclosure-when-changing-a-users-password.rb new file mode 100644 index 000000000..f1e100c2e --- /dev/null +++ b/acceptance/tests/ticket_6857_password-disclosure-when-changing-a-users-password.rb @@ -0,0 +1,23 @@ +test_name "#6857: redact password hashes when applying in noop mode" + +adduser_manifest = <<MANIFEST +user { 'passwordtestuser': + ensure => 'present', + password => 'apassword', +} +MANIFEST + +changepass_manifest = <<MANIFEST +user { 'passwordtestuser': + ensure => 'present', + password => 'newpassword', + noop => true, +} +MANIFEST + +apply_manifest_on(agents, adduser_manifest ) +results = apply_manifest_on(agents, changepass_manifest ) + +results.each do |result| + assert_match( /current_value \[old password hash redacted\], should be \[new password hash redacted\]/ , "#{result.stdout}" ) +end diff --git a/lib/puppet/face/status.rb b/lib/puppet/face/status.rb index bdb0c4d26..e8c87e98d 100644 --- a/lib/puppet/face/status.rb +++ b/lib/puppet/face/status.rb @@ -12,6 +12,7 @@ Puppet::Indirector::Face.define(:status, '0.0.1') do get_action(:search).summary "Invalid for this subcommand." find = get_action(:find) + find.default = true find.summary "Check status of puppet master server." find.arguments "<dummy_text>" find.returns <<-'EOT' diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb index fe77a9658..fc1121eb6 100644 --- a/lib/puppet/interface/action.rb +++ b/lib/puppet/interface/action.rb @@ -199,6 +199,7 @@ def #{@name}(#{decl.join(", ")}) options = args.last action = get_action(#{name.inspect}) + action.add_default_args(args) action.validate_args(args) __invoke_decorations(:before, action, args, options) rval = self.__send__(#{internal_name.inspect}, *args) @@ -252,6 +253,14 @@ WRAPPER option end + def add_default_args(args) + options.map {|x| get_option(x) }.each do |option| + if option.has_default? and not option.aliases.any? {|x| args.last.has_key? x} + args.last[option.name] = option.default + end + end + end + def validate_args(args) # Check for multiple aliases for the same option... args.last.keys.each do |name| diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb index fbf588d7d..5c9af4f96 100644 --- a/lib/puppet/interface/action_manager.rb +++ b/lib/puppet/interface/action_manager.rb @@ -7,13 +7,14 @@ module Puppet::Interface::ActionManager require 'puppet/interface/action_builder' @actions ||= {} - @default_action ||= nil raise "Action #{name} already defined for #{self}" if action?(name) + action = Puppet::Interface::ActionBuilder.build(self, name, &block) - if action.default - raise "Actions #{@default_action.name} and #{name} cannot both be default" if @default_action - @default_action = action + + if action.default and current = get_default_action + raise "Actions #{current.name} and #{name} cannot both be default" end + @actions[action.name] = action end @@ -61,7 +62,11 @@ module Puppet::Interface::ActionManager end def get_default_action - @default_action + default = actions.map {|x| get_action(x) }.select {|x| x.default } + if default.length > 1 + raise "The actions #{default.map(&:name).join(", ")} cannot all be default" + end + default.first end def action?(name) diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb index 3cd930acf..01f6f2307 100644 --- a/lib/puppet/interface/option.rb +++ b/lib/puppet/interface/option.rb @@ -6,6 +6,7 @@ class Puppet::Interface::Option def initialize(parent, *declaration, &block) @parent = parent @optparse = [] + @default = nil # Collect and sort the arguments in the declaration. dups = {} @@ -81,8 +82,26 @@ class Puppet::Interface::Option !!@required end + def has_default? + !!@default + end + + def default=(proc) + required and raise ArgumentError, "#{self} can't be optional and have a default value" + proc.is_a? Proc or raise ArgumentError, "default value for #{self} is a #{proc.class.name.inspect}, not a proc" + @default = proc + end + + def default + @default and @default.call + end + attr_reader :parent, :name, :aliases, :optparse attr_accessor :required + def required=(value) + has_default? and raise ArgumentError, "#{self} can't be optional and have a default value" + @required = value + end attr_accessor :before_action def before_action=(proc) diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb index 5676ec977..c87adc2c0 100644 --- a/lib/puppet/interface/option_builder.rb +++ b/lib/puppet/interface/option_builder.rb @@ -51,4 +51,17 @@ class Puppet::Interface::OptionBuilder def required(value = true) @option.required = value end + + def default_to(&block) + block or raise ArgumentError, "#{@option} default_to requires a block" + if @option.has_default? + raise ArgumentError, "#{@option} already has a default value" + end + # Ruby 1.8 treats a block without arguments as accepting any number; 1.9 + # gets this right, so we work around it for now... --daniel 2011-07-20 + unless block.arity == 0 or (RUBY_VERSION =~ /^1\.8/ and block.arity == -1) + raise ArgumentError, "#{@option} default_to block should not take any arguments" + end + @option.default = block + end end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 572d5796d..c64bf69e8 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -168,6 +168,14 @@ module Puppet return "changed password" end end + + def is_to_s( currentvalue ) + return '[old password hash redacted]' + end + def should_to_s( newvalue ) + return '[new password hash redacted]' + end + end newproperty(:password_min_age, :required_features => :manages_password_age) do diff --git a/spec/shared_behaviours/things_that_declare_options.rb b/spec/shared_behaviours/things_that_declare_options.rb index 19bba66c1..ecdbfcaea 100755 --- a/spec/shared_behaviours/things_that_declare_options.rb +++ b/spec/shared_behaviours/things_that_declare_options.rb @@ -146,4 +146,117 @@ shared_examples_for "things that declare options" do end end end + + describe "#default_to" do + it "should not have a default value by default" do + option = add_options_to do option "--foo" end.get_option(:foo) + option.should_not be_has_default + end + + it "should accept a block for the default value" do + option = add_options_to do + option "--foo" do + default_to do + 12 + end + end + end.get_option(:foo) + + option.should be_has_default + end + + it "should invoke the block when asked for the default value" do + invoked = false + option = add_options_to do + option "--foo" do + default_to do + invoked = true + end + end + end.get_option(:foo) + + option.should be_has_default + option.default.should be_true + invoked.should be_true + end + + it "should return the value of the block when asked for the default" do + option = add_options_to do + option "--foo" do + default_to do + 12 + end + end + end.get_option(:foo) + + option.should be_has_default + option.default.should == 12 + end + + it "should invoke the block every time the default is requested" do + option = add_options_to do + option "--foo" do + default_to do + {} + end + end + end.get_option(:foo) + + first = option.default.object_id + second = option.default.object_id + third = option.default.object_id + + first.should_not == second + first.should_not == third + second.should_not == third + end + + it "should fail if the option has a default and is required" do + expect { + add_options_to do + option "--foo" do + required + default_to do 12 end + end + end + }.to raise_error ArgumentError, /can't be optional and have a default value/ + + expect { + add_options_to do + option "--foo" do + default_to do 12 end + required + end + end + }.to raise_error ArgumentError, /can't be optional and have a default value/ + end + + it "should fail if default_to has no block" do + expect { add_options_to do option "--foo" do default_to end end }. + to raise_error ArgumentError, /default_to requires a block/ + end + + it "should fail if default_to is invoked twice" do + expect { + add_options_to do + option "--foo" do + default_to do 12 end + default_to do "fun" end + end + end + }.to raise_error ArgumentError, /already has a default value/ + end + + [ "one", "one, two", "one, *two" ].each do |input| + it "should fail if the block has the wrong arity (#{input})" do + expect { + add_options_to do + option "--foo" do + eval "default_to do |#{input}| 12 end" + end + end + }.to raise_error ArgumentError, /should not take any arguments/ + end + end + end end diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb index 23216e7b4..d71a7d000 100755 --- a/spec/unit/interface/action_spec.rb +++ b/spec/unit/interface/action_spec.rb @@ -552,4 +552,67 @@ describe Puppet::Interface::Action do to raise_error ArgumentError, /Multiple aliases for the same option/ end end + + context "default option values" do + subject do + Puppet::Interface.new(:default_option_values, '1.0.0') do + action :foo do + option "--foo" do end + option "--bar" do end + when_invoked do |options| options end + end + end + end + + let :action do subject.get_action :foo end + let :option do action.get_option :foo end + + it "should not add options without defaults" do + subject.foo.should == {} + end + + it "should not add options without defaults, if options are given" do + subject.foo(:bar => 1).should == { :bar => 1 } + end + + it "should add the option default value when set" do + option.default = proc { 12 } + subject.foo.should == { :foo => 12 } + end + + it "should add the option default value when set, if other options are given" do + option.default = proc { 12 } + subject.foo(:bar => 1).should == { :foo => 12, :bar => 1 } + end + + it "should invoke the same default proc every time called" do + option.default = proc { @foo ||= {} } + subject.foo[:foo].object_id.should == subject.foo[:foo].object_id + end + + [nil, 0, 1, true, false, {}, []].each do |input| + it "should not override a passed option (#{input.inspect})" do + option.default = proc { :fail } + subject.foo(:foo => input).should == { :foo => input } + end + end + end + + context "runtime manipulations" do + subject do + Puppet::Interface.new(:runtime_manipulations, '1.0.0') do + action :foo do + when_invoked do |options| options end + end + end + end + + let :action do subject.get_action :foo end + + it "should be the face default action if default is set true" do + subject.get_default_action.should be_nil + action.default = true + subject.get_default_action.should == action + end + end end diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb index e77b46e79..e73561fba 100755 --- a/spec/unit/interface/option_spec.rb +++ b/spec/unit/interface/option_spec.rb @@ -97,4 +97,48 @@ describe Puppet::Interface::Option do end end end + + context "defaults" do + subject { Puppet::Interface::Option.new(face, "--foo") } + + it "should work sanely if member variables are used for state" do + subject.default = proc { @foo ||= 0; @foo += 1 } + subject.default.should == 1 + subject.default.should == 2 + subject.default.should == 3 + end + + context "with no default" do + it { should_not be_has_default } + its :default do should be_nil end + + it "should set a proc as default" do + expect { subject.default = proc { 12 } }.should_not raise_error + end + + [1, {}, [], Object.new, "foo"].each do |input| + it "should reject anything but a proc (#{input.class})" do + expect { subject.default = input }.to raise_error ArgumentError, /not a proc/ + end + end + end + + context "with a default" do + before :each do subject.default = proc { [:foo] } end + + it { should be_has_default } + its :default do should == [:foo] end + + it "should invoke the block every time" do + subject.default.object_id.should_not == subject.default.object_id + subject.default.should == subject.default + end + + it "should allow replacing the default proc" do + subject.default.should == [:foo] + subject.default = proc { :bar } + subject.default.should == :bar + end + end + end end diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index 2da755ef2..f24fc8eef 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -289,6 +289,14 @@ describe user, :fails_on_windows => true do @password.change_to_s("other", "mypass").should_not be_include("mypass") end + it "should redact the password when displaying the old value" do + @password.is_to_s("currentpassword").should =~ /^\[old password hash redacted\]$/ + end + + it "should redact the password when displaying the new value" do + @password.should_to_s("newpassword").should =~ /^\[new password hash redacted\]$/ + end + it "should fail if a ':' is included in the password" do lambda { @password.should = "some:thing" }.should raise_error(Puppet::Error) end |