diff options
-rw-r--r-- | cspell.json | 1 | ||||
-rw-r--r-- | lib/chef/provider/user.rb | 36 | ||||
-rw-r--r-- | lib/chef/provider/user/linux.rb | 24 | ||||
-rw-r--r-- | lib/chef/resource/user.rb | 10 | ||||
-rw-r--r-- | spec/unit/provider/user/linux_spec.rb | 50 | ||||
-rw-r--r-- | spec/unit/resource/user/linux_user_spec.rb | 42 |
6 files changed, 154 insertions, 9 deletions
diff --git a/cspell.json b/cspell.json index 2d09fea171..7a40dd82ba 100644 --- a/cspell.json +++ b/cspell.json @@ -528,6 +528,7 @@ "imageinfo", "Immutablize", "immutablize", + "inact", "includedir", "includepkgs", "includer", diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb index 61de2127bb..2abd7f5f3c 100644 --- a/lib/chef/provider/user.rb +++ b/lib/chef/provider/user.rb @@ -66,15 +66,13 @@ class Chef end current_resource.comment(user_info.gecos) - if new_resource.password && current_resource.password == "x" - begin - require "shadow" - rescue LoadError - @shadow_lib_ok = false - else - shadow_info = Shadow::Passwd.getspnam(new_resource.username) - current_resource.password(shadow_info.sp_pwdp) - end + begin + require "shadow" + rescue LoadError + @shadow_lib_ok = false + else + @shadow_info = Shadow::Passwd.getspnam(new_resource.username) + current_resource.password(@shadow_info.sp_pwdp) if new_resource.password && current_resource.password == "x" end convert_group_name if new_resource.gid @@ -83,6 +81,20 @@ class Chef current_resource end + def load_shadow_options + unless @shadow_info.nil? + current_resource.inactive(@shadow_info.sp_inact&.to_i) + # sp_expire gives time since epoch in days till expiration. Need to convert that + # to time in seconds since epoch and output date format for comparison + expire_date = if @shadow_info.sp_expire.nil? + @shadow_info.sp_expire + else + Time.at(@shadow_info.sp_expire * 60 * 60 * 24).strftime("%Y-%m-%d") + end + current_resource.expire_date(expire_date) + end + end + def define_resource_requirements requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a| a.assertion { @group_name_resolved } @@ -95,6 +107,12 @@ class Chef a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." \ "Note that user update converge may report false-positive on the basis of mismatched password. " end + requirements.assert(:all_actions) do |a| + # either neither linux-only value is set, or we need to be on Linux. + a.assertion { (!new_resource.expire_date && !new_resource.inactive) || linux? } + a.failure_message Chef::Exceptions::User, "Properties expire_date and inactive are not supported by this OS or have not been implemented for this OS yet." + a.whyrun "Properties expire_date and inactive are ignored as they are not supported by this OS or have not been implemented yet for this OS" + end requirements.assert(:modify, :lock, :unlock) do |a| a.assertion { @user_exists } a.failure_message(Chef::Exceptions::User, "Cannot modify user #{new_resource.username} - does not exist!") diff --git a/lib/chef/provider/user/linux.rb b/lib/chef/provider/user/linux.rb index 40b5985cb1..ab411d769a 100644 --- a/lib/chef/provider/user/linux.rb +++ b/lib/chef/provider/user/linux.rb @@ -23,6 +23,22 @@ class Chef provides :linux_user provides :user, os: "linux" + def load_current_resource + super + load_shadow_options + end + + def compare_user + super + %i{expire_date inactive}.each do |user_attrib| + new_val = new_resource.send(user_attrib) + cur_val = current_resource.send(user_attrib) + if !new_val.nil? && new_val.to_s != cur_val.to_s + @change_desc << "change #{user_attrib} from #{cur_val} to #{new_val}" + end + end + end + def create_user shell_out!("useradd", universal_options, useradd_options, new_resource.username) end @@ -52,7 +68,9 @@ class Chef def universal_options opts = [] opts << "-c" << new_resource.comment if should_set?(:comment) + opts << "-e" << new_resource.expire_date if prop_is_set?(:expire_date) opts << "-g" << new_resource.gid if should_set?(:gid) + opts << "-f" << new_resource.inactive if prop_is_set?(:inactive) opts << "-p" << new_resource.password if should_set?(:password) opts << "-s" << new_resource.shell if should_set?(:shell) opts << "-u" << new_resource.uid if should_set?(:uid) @@ -116,6 +134,12 @@ class Chef # FIXME: should probably go on the current_resource @locked end + + def prop_is_set?(prop) + v = new_resource.send(prop.to_sym) + + !v.nil? && v != "" + end end end end diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb index 992935a6b6..0e84a6b645 100644 --- a/lib/chef/resource/user.rb +++ b/lib/chef/resource/user.rb @@ -72,6 +72,16 @@ class Chef description: "The numeric group identifier." alias_method :group, :gid + + property :expire_date, [ String, NilClass ], + description: "(Linux) The date on which the user account will be disabled. The date is specified in the format YYYY-MM-DD.", + introduced: "18.0", + desired_state: false + + property :inactive, [ String, Integer, NilClass ], + description: "(Linux) The number of days after a password expires until the account is permanently disabled. A value of 0 disables the account as soon as the password has expired, and a value of -1 disables the feature.", + introduced: "18.0", + desired_state: false end end end diff --git a/spec/unit/provider/user/linux_spec.rb b/spec/unit/provider/user/linux_spec.rb index 1f22d963da..1fc8e3c6a1 100644 --- a/spec/unit/provider/user/linux_spec.rb +++ b/spec/unit/provider/user/linux_spec.rb @@ -70,4 +70,54 @@ describe Chef::Provider::User::Linux do expect( provider.useradd_options ).to eql(["-m"]) end end + + describe "expire_date behavior" do + before(:each) do + @new_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) + @current_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) + end + + it "defaults expire_date to nil" do + expect( @new_resource.expire_date ).to be nil + end + + it "by default expire_date is nil and we use ''" do + expect( provider.universal_options ).to eql([]) + end + + it "setting expire_date to nil includes ''" do + @new_resource.expire_date nil + expect( provider.universal_options ).to eql([]) + end + + it "setting expire_date to 1982-04-16 includes -e" do + @new_resource.expire_date "1982-04-16" + expect( provider.universal_options ).to eql(["-e", "1982-04-16"]) + end + end + + describe "inactive behavior" do + before(:each) do + @new_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) + @current_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) + end + + it "defaults inactive to nil" do + expect( @new_resource.inactive ).to be nil + end + + it "by default inactive is nil and we use ''" do + expect( provider.universal_options ).to eql([]) + end + + it "setting inactive to nil includes ''" do + @new_resource.inactive nil + expect( provider.universal_options ).to eql([]) + end + + it "setting inactive to 90 includes -f" do + @new_resource.inactive 90 + expect( provider.universal_options ).to eql(["-f", 90]) + end + end end diff --git a/spec/unit/resource/user/linux_user_spec.rb b/spec/unit/resource/user/linux_user_spec.rb new file mode 100644 index 0000000000..c9bd71b11d --- /dev/null +++ b/spec/unit/resource/user/linux_user_spec.rb @@ -0,0 +1,42 @@ +require "spec_helper" + +describe Chef::Resource::User, "initialize" do + let(:resource) { Chef::Resource::User::LinuxUser.new("notarealuser") } + + describe "inactive attribute" do + it "allows a string" do + resource.inactive "100" + expect(resource.inactive).to eql("100") + end + + it "allows an integer" do + resource.inactive 100 + expect(resource.inactive).to eql(100) + end + + it "does not allow a hash" do + expect { resource.inactive({ woot: "i found it" }) }.to raise_error(ArgumentError) + end + end + + describe "expire_date attribute" do + it "allows a string" do + resource.expire_date "100" + expect(resource.expire_date).to eql("100") + end + + it "does not allow an integer" do + expect { resource.expire_date(90) }.to raise_error(ArgumentError) + end + + it "does not allow a hash" do + expect { resource.expire_date({ woot: "i found it" }) }.to raise_error(ArgumentError) + end + end + + %w{inactive expire_date}.each do |prop| + it "sets #{prop} to nil" do + expect(resource.send(prop)).to eql(nil) + end + end +end |