summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--acceptance/tests/ticket_6857_password-disclosure-when-changing-a-users-password.rb23
-rw-r--r--lib/puppet/face/status.rb1
-rw-r--r--lib/puppet/interface/action.rb9
-rw-r--r--lib/puppet/interface/action_manager.rb15
-rw-r--r--lib/puppet/interface/option.rb19
-rw-r--r--lib/puppet/interface/option_builder.rb13
-rwxr-xr-xlib/puppet/type/user.rb8
-rwxr-xr-xspec/shared_behaviours/things_that_declare_options.rb113
-rwxr-xr-xspec/unit/interface/action_spec.rb63
-rwxr-xr-xspec/unit/interface/option_spec.rb44
-rwxr-xr-xspec/unit/type/user_spec.rb8
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