diff --git a/lib/puppet/provider/file/windows.rb b/lib/puppet/provider/file/windows.rb index 5c8038db1..833409f7d 100644 --- a/lib/puppet/provider/file/windows.rb +++ b/lib/puppet/provider/file/windows.rb @@ -1,105 +1,104 @@ Puppet::Type.type(:file).provide :windows do desc "Uses Microsoft Windows functionality to manage file ownership and permissions." confine :operatingsystem => :windows has_feature :manages_symlinks if Puppet.features.manages_symlinks? include Puppet::Util::Warnings if Puppet.features.microsoft_windows? require 'puppet/util/windows' - require 'puppet/util/adsi' include Puppet::Util::Windows::Security end # Determine if the account is valid, and if so, return the UID def name2id(value) Puppet::Util::Windows::Security.name_to_sid(value) end # If it's a valid SID, get the name. Otherwise, it's already a name, # so just return it. def id2name(id) if Puppet::Util::Windows::Security.valid_sid?(id) Puppet::Util::Windows::Security.sid_to_name(id) else id end end # We use users and groups interchangeably, so use the same methods for both # (the type expects different methods, so we have to oblige). alias :uid2name :id2name alias :gid2name :id2name alias :name2gid :name2id alias :name2uid :name2id def owner return :absent unless resource.stat get_owner(resource[:path]) end def owner=(should) begin set_owner(should, resolved_path) rescue => detail raise Puppet::Error, "Failed to set owner to '#{should}': #{detail}", detail.backtrace end end def group return :absent unless resource.stat get_group(resource[:path]) end def group=(should) begin set_group(should, resolved_path) rescue => detail raise Puppet::Error, "Failed to set group to '#{should}': #{detail}", detail.backtrace end end def mode if resource.stat mode = get_mode(resource[:path]) mode ? mode.to_s(8) : :absent else :absent end end def mode=(value) begin set_mode(value.to_i(8), resource[:path]) rescue => detail error = Puppet::Error.new("failed to set mode #{mode} on #{resource[:path]}: #{detail.message}") error.set_backtrace detail.backtrace raise error end :file_changed end def validate if [:owner, :group, :mode].any?{|p| resource[p]} and !supports_acl?(resource[:path]) resource.fail("Can only manage owner, group, and mode on filesystems that support Windows ACLs, such as NTFS") end end attr_reader :file private def file @file ||= Puppet::FileSystem.pathname(resource[:path]) end def resolved_path path = file() # under POSIX, :manage means use lchown - i.e. operate on the link return path.to_s if resource[:links] == :manage # otherwise, use chown -- that will resolve the link IFF it is a link # otherwise it will operate on the path Puppet::FileSystem.symlink?(path) ? Puppet::FileSystem.readlink(path) : path.to_s end end diff --git a/lib/puppet/provider/group/windows_adsi.rb b/lib/puppet/provider/group/windows_adsi.rb index 56c0c175b..8ed2443f0 100644 --- a/lib/puppet/provider/group/windows_adsi.rb +++ b/lib/puppet/provider/group/windows_adsi.rb @@ -1,86 +1,86 @@ -require 'puppet/util/adsi' +require 'puppet/util/windows' Puppet::Type.type(:group).provide :windows_adsi do desc "Local group management for Windows. Group members can be both users and groups. Additionally, local groups can contain domain users." defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_features :manages_members def members_insync?(current, should) return false unless current # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of account names. # Cannot use munge of the group property to canonicalize @should # since the default array_matching comparison is not commutative should_empty = should.nil? or should.empty? return false if current.empty? != should_empty # dupes automatically weeded out when hashes built - Puppet::Util::ADSI::Group.name_sid_hash(current) == Puppet::Util::ADSI::Group.name_sid_hash(should) + Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) == Puppet::Util::Windows::ADSI::Group.name_sid_hash(should) end def members_to_s(users) return '' if users.nil? or !users.kind_of?(Array) users = users.map do |user_name| sid = Puppet::Util::Windows::Security.name_to_sid_object(user_name) if sid.account =~ /\\/ - account, _ = Puppet::Util::ADSI::User.parse_name(sid.account) + account, _ = Puppet::Util::Windows::ADSI::User.parse_name(sid.account) else account = sid.account end resource.debug("#{sid.domain}\\#{account} (#{sid.to_s})") "#{sid.domain}\\#{account}" end return users.join(',') end def group - @group ||= Puppet::Util::ADSI::Group.new(@resource[:name]) + @group ||= Puppet::Util::Windows::ADSI::Group.new(@resource[:name]) end def members group.members end def members=(members) group.set_members(members) end def create - @group = Puppet::Util::ADSI::Group.create(@resource[:name]) + @group = Puppet::Util::Windows::ADSI::Group.create(@resource[:name]) @group.commit self.members = @resource[:members] end def exists? - Puppet::Util::ADSI::Group.exists?(@resource[:name]) + Puppet::Util::Windows::ADSI::Group.exists?(@resource[:name]) end def delete - Puppet::Util::ADSI::Group.delete(@resource[:name]) + Puppet::Util::Windows::ADSI::Group.delete(@resource[:name]) end # Only flush if we created or modified a group, not deleted def flush @group.commit if @group end def gid Puppet::Util::Windows::Security.name_to_sid(@resource[:name]) end def gid=(value) fail "gid is read-only" end def self.instances - Puppet::Util::ADSI::Group.map { |g| new(:ensure => :present, :name => g.name) } + Puppet::Util::Windows::ADSI::Group.map { |g| new(:ensure => :present, :name => g.name) } end end diff --git a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb index 5a8741122..45b073da0 100644 --- a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb +++ b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb @@ -1,565 +1,564 @@ require 'puppet/parameter' if Puppet.features.microsoft_windows? require 'win32/taskscheduler' - require 'puppet/util/adsi' end Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do desc %q{This provider uses the win32-taskscheduler gem to manage scheduled tasks on Windows. Puppet requires version 0.2.1 or later of the win32-taskscheduler gem; previous versions can cause "Could not evaluate: The operation completed successfully" errors.} defaultfor :operatingsystem => :windows confine :operatingsystem => :windows def self.instances Win32::TaskScheduler.new.tasks.collect do |job_file| job_title = File.basename(job_file, '.job') new( :provider => :win32_taskscheduler, :name => job_title ) end end def exists? Win32::TaskScheduler.new.exists? resource[:name] end def task return @task if @task @task ||= Win32::TaskScheduler.new @task.activate(resource[:name] + '.job') if exists? @task end def clear_task @task = nil @triggers = nil end def enabled task.flags & Win32::TaskScheduler::DISABLED == 0 ? :true : :false end def command task.application_name end def arguments task.parameters end def working_dir task.working_directory end def user account = task.account_information return 'system' if account == '' account end def trigger return @triggers if @triggers @triggers = [] task.trigger_count.times do |i| trigger = begin task.trigger(i) rescue Win32::TaskScheduler::Error # Win32::TaskScheduler can't handle all of the # trigger types Windows uses, so we need to skip the # unhandled types to prevent "puppet resource" from # blowing up. nil end next unless trigger and scheduler_trigger_types.include?(trigger['trigger_type']) puppet_trigger = {} case trigger['trigger_type'] when Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY puppet_trigger['schedule'] = 'daily' puppet_trigger['every'] = trigger['type']['days_interval'].to_s when Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY puppet_trigger['schedule'] = 'weekly' puppet_trigger['every'] = trigger['type']['weeks_interval'].to_s puppet_trigger['on'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['on'] = days_from_bitfield(trigger['type']['days']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['which_occurrence'] = occurrence_constant_to_name(trigger['type']['weeks']) puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE puppet_trigger['schedule'] = 'once' end puppet_trigger['start_date'] = self.class.normalized_date("#{trigger['start_year']}-#{trigger['start_month']}-#{trigger['start_day']}") puppet_trigger['start_time'] = self.class.normalized_time("#{trigger['start_hour']}:#{trigger['start_minute']}") puppet_trigger['enabled'] = trigger['flags'] & Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED == 0 puppet_trigger['index'] = i @triggers << puppet_trigger end @triggers = @triggers[0] if @triggers.length == 1 @triggers end def user_insync?(current, should) return false unless current # Win32::TaskScheduler can return the 'SYSTEM' account as the # empty string. current = 'system' if current == '' # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of the account name. Puppet::Util::Windows::Security.name_to_sid(current) == Puppet::Util::Windows::Security.name_to_sid(should[0]) end def trigger_insync?(current, should) should = [should] unless should.is_a?(Array) current = [current] unless current.is_a?(Array) return false unless current.length == should.length current_in_sync = current.all? do |c| should.any? {|s| triggers_same?(c, s)} end should_in_sync = should.all? do |s| current.any? {|c| triggers_same?(c,s)} end current_in_sync && should_in_sync end def command=(value) task.application_name = value end def arguments=(value) task.parameters = value end def working_dir=(value) task.working_directory = value end def enabled=(value) if value == :true task.flags = task.flags & ~Win32::TaskScheduler::DISABLED else task.flags = task.flags | Win32::TaskScheduler::DISABLED end end def trigger=(value) desired_triggers = value.is_a?(Array) ? value : [value] current_triggers = trigger.is_a?(Array) ? trigger : [trigger] extra_triggers = [] desired_to_search = desired_triggers.dup current_triggers.each do |current| if found = desired_to_search.find {|desired| triggers_same?(current, desired)} desired_to_search.delete(found) else extra_triggers << current['index'] end end needed_triggers = [] current_to_search = current_triggers.dup desired_triggers.each do |desired| if found = current_to_search.find {|current| triggers_same?(current, desired)} current_to_search.delete(found) else needed_triggers << desired end end extra_triggers.reverse_each do |index| task.delete_trigger(index) end needed_triggers.each do |trigger_hash| # Even though this is an assignment, the API for # Win32::TaskScheduler ends up appending this trigger to the # list of triggers for the task, while #add_trigger is only able # to replace existing triggers. *shrug* task.trigger = translate_hash_to_trigger(trigger_hash) end end def user=(value) self.fail("Invalid user: #{value}") unless Puppet::Util::Windows::Security.name_to_sid(value) if value.to_s.downcase != 'system' task.set_account_information(value, resource[:password]) else # Win32::TaskScheduler treats a nil/empty username & password as # requesting the SYSTEM account. task.set_account_information(nil, nil) end end def create clear_task @task = Win32::TaskScheduler.new(resource[:name], dummy_time_trigger) self.command = resource[:command] [:arguments, :working_dir, :enabled, :trigger, :user].each do |prop| send("#{prop}=", resource[prop]) if resource[prop] end end def destroy Win32::TaskScheduler.new.delete(resource[:name] + '.job') end def flush unless resource[:ensure] == :absent self.fail('Parameter command is required.') unless resource[:command] task.save @task = nil end end def triggers_same?(current_trigger, desired_trigger) return false unless current_trigger['schedule'] == desired_trigger['schedule'] return false if current_trigger.has_key?('enabled') && !current_trigger['enabled'] desired = desired_trigger.dup desired['every'] ||= current_trigger['every'] if current_trigger.has_key?('every') desired['months'] ||= current_trigger['months'] if current_trigger.has_key?('months') desired['on'] ||= current_trigger['on'] if current_trigger.has_key?('on') desired['day_of_week'] ||= current_trigger['day_of_week'] if current_trigger.has_key?('day_of_week') translate_hash_to_trigger(current_trigger) == translate_hash_to_trigger(desired) end def self.normalized_date(date_string) date = Date.parse("#{date_string}") "#{date.year}-#{date.month}-#{date.day}" end def self.normalized_time(time_string) Time.parse("#{time_string}").strftime('%H:%M') end def dummy_time_trigger now = Time.now { 'flags' => 0, 'random_minutes_interval' => 0, 'end_day' => 0, "end_year" => 0, "trigger_type" => 0, "minutes_interval" => 0, "end_month" => 0, "minutes_duration" => 0, 'start_year' => now.year, 'start_month' => now.month, 'start_day' => now.day, 'start_hour' => now.hour, 'start_minute' => now.min, 'trigger_type' => Win32::TaskScheduler::ONCE, } end def translate_hash_to_trigger(puppet_trigger, user_provided_input=false) trigger = dummy_time_trigger if user_provided_input self.fail "'enabled' is read-only on triggers" if puppet_trigger.has_key?('enabled') self.fail "'index' is read-only on triggers" if puppet_trigger.has_key?('index') end puppet_trigger.delete('index') if puppet_trigger.delete('enabled') == false trigger['flags'] |= Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED else trigger['flags'] &= ~Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED end extra_keys = puppet_trigger.keys.sort - ['schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week'] self.fail "Unknown trigger option(s): #{Puppet::Parameter.format_value_for_display(extra_keys)}" unless extra_keys.empty? self.fail "Must specify 'start_time' when defining a trigger" unless puppet_trigger['start_time'] case puppet_trigger['schedule'] when 'daily' trigger['trigger_type'] = Win32::TaskScheduler::DAILY trigger['type'] = { 'days_interval' => Integer(puppet_trigger['every'] || 1) } when 'weekly' trigger['trigger_type'] = Win32::TaskScheduler::WEEKLY trigger['type'] = { 'weeks_interval' => Integer(puppet_trigger['every'] || 1) } trigger['type']['days_of_week'] = if puppet_trigger['day_of_week'] bitfield_from_days_of_week(puppet_trigger['day_of_week']) else scheduler_days_of_week.inject(0) {|day_flags,day| day_flags |= day} end when 'monthly' trigger['type'] = { 'months' => bitfield_from_months(puppet_trigger['months'] || (1..12).to_a), } if puppet_trigger.keys.include?('on') if puppet_trigger.has_key?('day_of_week') or puppet_trigger.has_key?('which_occurrence') self.fail "Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger" end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDATE trigger['type']['days'] = bitfield_from_days(puppet_trigger['on']) elsif puppet_trigger.keys.include?('which_occurrence') or puppet_trigger.keys.include?('day_of_week') self.fail 'which_occurrence cannot be specified as an array' if puppet_trigger['which_occurrence'].is_a?(Array) %w{day_of_week which_occurrence}.each do |field| self.fail "#{field} must be specified when creating a monthly day-of-week based trigger" unless puppet_trigger.has_key?(field) end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDOW trigger['type']['weeks'] = occurrence_name_to_constant(puppet_trigger['which_occurrence']) trigger['type']['days_of_week'] = bitfield_from_days_of_week(puppet_trigger['day_of_week']) else self.fail "Don't know how to create a 'monthly' schedule with the options: #{puppet_trigger.keys.sort.join(', ')}" end when 'once' self.fail "Must specify 'start_date' when defining a one-time trigger" unless puppet_trigger['start_date'] trigger['trigger_type'] = Win32::TaskScheduler::ONCE else self.fail "Unknown schedule type: #{puppet_trigger["schedule"].inspect}" end if start_date = puppet_trigger['start_date'] start_date = Date.parse(start_date) self.fail "start_date must be on or after 1753-01-01" unless start_date >= Date.new(1753, 1, 1) trigger['start_year'] = start_date.year trigger['start_month'] = start_date.month trigger['start_day'] = start_date.day end start_time = Time.parse(puppet_trigger['start_time']) trigger['start_hour'] = start_time.hour trigger['start_minute'] = start_time.min trigger end def validate_trigger(value) value = [value] unless value.is_a?(Array) # translate_hash_to_trigger handles the same validation that we # would be doing here at the individual trigger level. value.each {|t| translate_hash_to_trigger(t, true)} true end private def bitfield_from_months(months) bitfield = 0 months = [months] unless months.is_a?(Array) months.each do |month| integer_month = Integer(month) rescue nil self.fail 'Month must be specified as an integer in the range 1-12' unless integer_month == month.to_f and integer_month.between?(1,12) bitfield |= scheduler_months[integer_month - 1] end bitfield end def bitfield_from_days(days) bitfield = 0 days = [days] unless days.is_a?(Array) days.each do |day| # The special "day" of 'last' is represented by day "number" # 32. 'last' has the special meaning of "the last day of the # month", no matter how many days there are in the month. day = 32 if day == 'last' integer_day = Integer(day) self.fail "Day must be specified as an integer in the range 1-31, or as 'last'" unless integer_day = day.to_f and integer_day.between?(1,32) bitfield |= 1 << integer_day - 1 end bitfield end def bitfield_from_days_of_week(days_of_week) bitfield = 0 days_of_week = [days_of_week] unless days_of_week.is_a?(Array) days_of_week.each do |day_of_week| bitfield |= day_of_week_name_to_constant(day_of_week) end bitfield end def months_from_bitfield(bitfield) months = [] scheduler_months.each do |month| if bitfield & month != 0 months << month_constant_to_number(month) end end months end def days_from_bitfield(bitfield) days = [] i = 0 while bitfield > 0 if bitfield & 1 > 0 # Day 32 has the special meaning of "the last day of the # month", no matter how many days there are in the month. days << (i == 31 ? 'last' : i + 1) end bitfield = bitfield >> 1 i += 1 end days end def days_of_week_from_bitfield(bitfield) days_of_week = [] scheduler_days_of_week.each do |day_of_week| if bitfield & day_of_week != 0 days_of_week << day_of_week_constant_to_name(day_of_week) end end days_of_week end def scheduler_trigger_types [ Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE ] end def scheduler_days_of_week [ Win32::TaskScheduler::SUNDAY, Win32::TaskScheduler::MONDAY, Win32::TaskScheduler::TUESDAY, Win32::TaskScheduler::WEDNESDAY, Win32::TaskScheduler::THURSDAY, Win32::TaskScheduler::FRIDAY, Win32::TaskScheduler::SATURDAY ] end def scheduler_months [ Win32::TaskScheduler::JANUARY, Win32::TaskScheduler::FEBRUARY, Win32::TaskScheduler::MARCH, Win32::TaskScheduler::APRIL, Win32::TaskScheduler::MAY, Win32::TaskScheduler::JUNE, Win32::TaskScheduler::JULY, Win32::TaskScheduler::AUGUST, Win32::TaskScheduler::SEPTEMBER, Win32::TaskScheduler::OCTOBER, Win32::TaskScheduler::NOVEMBER, Win32::TaskScheduler::DECEMBER ] end def scheduler_occurrences [ Win32::TaskScheduler::FIRST_WEEK, Win32::TaskScheduler::SECOND_WEEK, Win32::TaskScheduler::THIRD_WEEK, Win32::TaskScheduler::FOURTH_WEEK, Win32::TaskScheduler::LAST_WEEK ] end def day_of_week_constant_to_name(constant) case constant when Win32::TaskScheduler::SUNDAY; 'sun' when Win32::TaskScheduler::MONDAY; 'mon' when Win32::TaskScheduler::TUESDAY; 'tues' when Win32::TaskScheduler::WEDNESDAY; 'wed' when Win32::TaskScheduler::THURSDAY; 'thurs' when Win32::TaskScheduler::FRIDAY; 'fri' when Win32::TaskScheduler::SATURDAY; 'sat' end end def day_of_week_name_to_constant(name) case name when 'sun'; Win32::TaskScheduler::SUNDAY when 'mon'; Win32::TaskScheduler::MONDAY when 'tues'; Win32::TaskScheduler::TUESDAY when 'wed'; Win32::TaskScheduler::WEDNESDAY when 'thurs'; Win32::TaskScheduler::THURSDAY when 'fri'; Win32::TaskScheduler::FRIDAY when 'sat'; Win32::TaskScheduler::SATURDAY end end def month_constant_to_number(constant) month_num = 1 while constant >> month_num - 1 > 1 month_num += 1 end month_num end def occurrence_constant_to_name(constant) case constant when Win32::TaskScheduler::FIRST_WEEK; 'first' when Win32::TaskScheduler::SECOND_WEEK; 'second' when Win32::TaskScheduler::THIRD_WEEK; 'third' when Win32::TaskScheduler::FOURTH_WEEK; 'fourth' when Win32::TaskScheduler::LAST_WEEK; 'last' end end def occurrence_name_to_constant(name) case name when 'first'; Win32::TaskScheduler::FIRST_WEEK when 'second'; Win32::TaskScheduler::SECOND_WEEK when 'third'; Win32::TaskScheduler::THIRD_WEEK when 'fourth'; Win32::TaskScheduler::FOURTH_WEEK when 'last'; Win32::TaskScheduler::LAST_WEEK end end end diff --git a/lib/puppet/provider/user/windows_adsi.rb b/lib/puppet/provider/user/windows_adsi.rb index 14b905d3b..00b6b432a 100644 --- a/lib/puppet/provider/user/windows_adsi.rb +++ b/lib/puppet/provider/user/windows_adsi.rb @@ -1,99 +1,99 @@ -require 'puppet/util/adsi' +require 'puppet/util/windows' Puppet::Type.type(:user).provide :windows_adsi do desc "Local user management for Windows." defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_features :manages_homedir, :manages_passwords def user - @user ||= Puppet::Util::ADSI::User.new(@resource[:name]) + @user ||= Puppet::Util::Windows::ADSI::User.new(@resource[:name]) end def groups user.groups.join(',') end def groups=(groups) user.set_groups(groups, @resource[:membership] == :minimum) end def create - @user = Puppet::Util::ADSI::User.create(@resource[:name]) + @user = Puppet::Util::Windows::ADSI::User.create(@resource[:name]) @user.password = @resource[:password] @user.commit [:comment, :home, :groups].each do |prop| send("#{prop}=", @resource[prop]) if @resource[prop] end if @resource.managehome? Puppet::Util::Windows::User.load_profile(@resource[:name], @resource[:password]) end end def exists? - Puppet::Util::ADSI::User.exists?(@resource[:name]) + Puppet::Util::Windows::ADSI::User.exists?(@resource[:name]) end def delete # lookup sid before we delete account sid = uid if @resource.managehome? - Puppet::Util::ADSI::User.delete(@resource[:name]) + Puppet::Util::Windows::ADSI::User.delete(@resource[:name]) if sid - Puppet::Util::ADSI::UserProfile.delete(sid) + Puppet::Util::Windows::ADSI::UserProfile.delete(sid) end end # Only flush if we created or modified a user, not deleted def flush @user.commit if @user end def comment user['Description'] end def comment=(value) user['Description'] = value end def home user['HomeDirectory'] end def home=(value) user['HomeDirectory'] = value end def password user.password_is?( @resource[:password] ) ? @resource[:password] : :absent end def password=(value) user.password = value end def uid Puppet::Util::Windows::Security.name_to_sid(@resource[:name]) end def uid=(value) fail "uid is read-only" end [:gid, :shell].each do |prop| define_method(prop) { nil } define_method("#{prop}=") do |v| fail "No support for managing property #{prop} of user #{@resource[:name]} on Windows" end end def self.instances - Puppet::Util::ADSI::User.map { |u| new(:ensure => :present, :name => u.name) } + Puppet::Util::Windows::ADSI::User.map { |u| new(:ensure => :present, :name => u.name) } end end diff --git a/lib/puppet/util/windows.rb b/lib/puppet/util/windows.rb index 5e0851cd8..250fd12bd 100644 --- a/lib/puppet/util/windows.rb +++ b/lib/puppet/util/windows.rb @@ -1,18 +1,24 @@ module Puppet::Util::Windows + module ADSI + class User; end + class UserProfile; end + class Group; end + end if Puppet::Util::Platform.windows? # these reference platform specific gems require 'puppet/util/windows/api_types' require 'puppet/util/windows/string' require 'puppet/util/windows/error' require 'puppet/util/windows/sid' require 'puppet/util/windows/security' require 'puppet/util/windows/user' require 'puppet/util/windows/process' require 'puppet/util/windows/file' require 'puppet/util/windows/root_certs' require 'puppet/util/windows/access_control_entry' require 'puppet/util/windows/access_control_list' require 'puppet/util/windows/security_descriptor' + require 'puppet/util/windows/adsi' end require 'puppet/util/windows/registry' end diff --git a/lib/puppet/util/adsi.rb b/lib/puppet/util/windows/adsi.rb similarity index 70% rename from lib/puppet/util/adsi.rb rename to lib/puppet/util/windows/adsi.rb index 4c30e18c2..b1d4d62a8 100644 --- a/lib/puppet/util/adsi.rb +++ b/lib/puppet/util/windows/adsi.rb @@ -1,368 +1,392 @@ -module Puppet::Util::ADSI +module Puppet::Util::Windows::ADSI + require 'ffi' + class << self + extend FFI::Library + def connectable?(uri) begin !! connect(uri) rescue false end end def connect(uri) begin WIN32OLE.connect(uri) rescue Exception => e raise Puppet::Error.new( "ADSI connection error: #{e}", e ) end end def create(name, resource_type) - Puppet::Util::ADSI.connect(computer_uri).Create(resource_type, name) + Puppet::Util::Windows::ADSI.connect(computer_uri).Create(resource_type, name) end def delete(name, resource_type) - Puppet::Util::ADSI.connect(computer_uri).Delete(resource_type, name) + Puppet::Util::Windows::ADSI.connect(computer_uri).Delete(resource_type, name) end + ffi_convention :stdcall + + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724295(v=vs.85).aspx + # BOOL WINAPI GetComputerName( + # _Out_ LPTSTR lpBuffer, + # _Inout_ LPDWORD lpnSize + # ); + ffi_lib :kernel32 + attach_function_private :GetComputerNameW, + [:lpwstr, :lpdword], :bool + + # taken from winbase.h + MAX_COMPUTERNAME_LENGTH = 31 + def computer_name unless @computer_name - buf = " " * 128 - Win32API.new('kernel32', 'GetComputerName', ['P','P'], 'I').call(buf, buf.length.to_s) - @computer_name = buf.unpack("A*")[0] + max_length = MAX_COMPUTERNAME_LENGTH + 1 # NULL terminated + buffer = FFI::MemoryPointer.new(max_length * 2) # wide string + buffer_size = FFI::MemoryPointer.new(:dword, 1) + buffer_size.write_dword(max_length) # length in TCHARs + + if ! GetComputerNameW(buffer, buffer_size) + raise Puppet::Util::Windows::Error.new("Failed to get computer name") + end + @computer_name = buffer.read_wide_string(buffer_size.read_dword) end @computer_name end def computer_uri(host = '.') "WinNT://#{host}" end def wmi_resource_uri( host = '.' ) "winmgmts:{impersonationLevel=impersonate}!//#{host}/root/cimv2" end # @api private def sid_uri_safe(sid) return sid_uri(sid) if sid.kind_of?(Win32::Security::SID) begin sid = Win32::Security::SID.new(Win32::Security::SID.string_to_sid(sid)) sid_uri(sid) rescue Win32::Security::SID::Error return nil end end def sid_uri(sid) raise Puppet::Error.new( "Must use a valid SID object" ) if !sid.kind_of?(Win32::Security::SID) "WinNT://#{sid.to_s}" end def uri(resource_name, resource_type, host = '.') "#{computer_uri(host)}/#{resource_name},#{resource_type}" end def wmi_connection connect(wmi_resource_uri) end def execquery(query) wmi_connection.execquery(query) end def sid_for_account(name) - Puppet.deprecation_warning "Puppet::Util::ADSI.sid_for_account is deprecated and will be removed in 3.0, use Puppet::Util::Windows::SID.name_to_sid instead." + Puppet.deprecation_warning "Puppet::Util::Windows::ADSI.sid_for_account is deprecated and will be removed in 3.0, use Puppet::Util::Windows::SID.name_to_sid instead." Puppet::Util::Windows::Security.name_to_sid(name) end end class User extend Enumerable attr_accessor :native_user attr_reader :name, :sid def initialize(name, native_user = nil) @name = name @native_user = native_user end def self.parse_name(name) if name =~ /\// raise Puppet::Error.new( "Value must be in DOMAIN\\user style syntax" ) end matches = name.scan(/((.*)\\)?(.*)/) domain = matches[0][1] || '.' account = matches[0][2] return account, domain end def native_user - @native_user ||= Puppet::Util::ADSI.connect(self.class.uri(*self.class.parse_name(@name))) + @native_user ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(@name))) end def sid @sid ||= Puppet::Util::Windows::Security.octet_string_to_sid_object(native_user.objectSID) end def self.uri(name, host = '.') - if sid_uri = Puppet::Util::ADSI.sid_uri_safe(name) then return sid_uri end + if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end host = '.' if ['NT AUTHORITY', 'BUILTIN', Socket.gethostname].include?(host) - Puppet::Util::ADSI.uri(name, 'user', host) + Puppet::Util::Windows::ADSI.uri(name, 'user', host) end def uri self.class.uri(sid.account, sid.domain) end def self.logon(name, password) Puppet::Util::Windows::User.password_is?(name, password) end def [](attribute) native_user.Get(attribute) end def []=(attribute, value) native_user.Put(attribute, value) end def commit begin native_user.SetInfo unless native_user.nil? rescue Exception => e raise Puppet::Error.new( "User update failed: #{e}", e ) end self end def password_is?(password) self.class.logon(name, password) end def add_flag(flag_name, value) flag = native_user.Get(flag_name) rescue 0 native_user.Put(flag_name, flag | value) commit end def password=(password) native_user.SetPassword(password) commit fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 add_flag("UserFlags", fADS_UF_DONT_EXPIRE_PASSWD) end def groups # WIN32OLE objects aren't enumerable, so no map groups = [] native_user.Groups.each {|g| groups << g.Name} rescue nil groups end def add_to_groups(*group_names) group_names.each do |group_name| - Puppet::Util::ADSI::Group.new(group_name).add_member_sids(sid) + Puppet::Util::Windows::ADSI::Group.new(group_name).add_member_sids(sid) end end alias add_to_group add_to_groups def remove_from_groups(*group_names) group_names.each do |group_name| - Puppet::Util::ADSI::Group.new(group_name).remove_member_sids(sid) + Puppet::Util::Windows::ADSI::Group.new(group_name).remove_member_sids(sid) end end alias remove_from_group remove_from_groups def set_groups(desired_groups, minimum = true) return if desired_groups.nil? or desired_groups.empty? desired_groups = desired_groups.split(',').map(&:strip) current_groups = self.groups # First we add the user to all the groups it should be in but isn't groups_to_add = desired_groups - current_groups add_to_groups(*groups_to_add) # Then we remove the user from all groups it is in but shouldn't be, if # that's been requested groups_to_remove = current_groups - desired_groups remove_from_groups(*groups_to_remove) unless minimum end def self.create(name) # Windows error 1379: The specified local group already exists. - raise Puppet::Error.new( "Cannot create user if group '#{name}' exists." ) if Puppet::Util::ADSI::Group.exists? name - new(name, Puppet::Util::ADSI.create(name, 'user')) + raise Puppet::Error.new( "Cannot create user if group '#{name}' exists." ) if Puppet::Util::Windows::ADSI::Group.exists? name + new(name, Puppet::Util::Windows::ADSI.create(name, 'user')) end def self.exists?(name) - Puppet::Util::ADSI::connectable?(User.uri(*User.parse_name(name))) + Puppet::Util::Windows::ADSI::connectable?(User.uri(*User.parse_name(name))) end def self.delete(name) - Puppet::Util::ADSI.delete(name, 'user') + Puppet::Util::Windows::ADSI.delete(name, 'user') end def self.each(&block) - wql = Puppet::Util::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"') + wql = Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"') users = [] wql.each do |u| users << new(u.name) end users.each(&block) end end class UserProfile def self.delete(sid) begin - Puppet::Util::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'") + Puppet::Util::Windows::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'") rescue => e # http://social.technet.microsoft.com/Forums/en/ITCG/thread/0f190051-ac96-4bf1-a47f-6b864bfacee5 # Prior to Vista SP1, there's no builtin way to programmatically # delete user profiles (except for delprof.exe). So try to delete # but warn if we fail raise e unless e.message.include?('80041010') Puppet.warning "Cannot delete user profile for '#{sid}' prior to Vista SP1" end end end class Group extend Enumerable attr_accessor :native_group attr_reader :name def initialize(name, native_group = nil) @name = name @native_group = native_group end def uri self.class.uri(name) end def self.uri(name, host = '.') - if sid_uri = Puppet::Util::ADSI.sid_uri_safe(name) then return sid_uri end + if sid_uri = Puppet::Util::Windows::ADSI.sid_uri_safe(name) then return sid_uri end - Puppet::Util::ADSI.uri(name, 'group', host) + Puppet::Util::Windows::ADSI.uri(name, 'group', host) end def native_group - @native_group ||= Puppet::Util::ADSI.connect(uri) + @native_group ||= Puppet::Util::Windows::ADSI.connect(uri) end def commit begin native_group.SetInfo unless native_group.nil? rescue Exception => e raise Puppet::Error.new( "Group update failed: #{e}", e ) end self end def self.name_sid_hash(names) return [] if names.nil? or names.empty? sids = names.map do |name| sid = Puppet::Util::Windows::Security.name_to_sid_object(name) raise Puppet::Error.new( "Could not resolve username: #{name}" ) if !sid [sid.to_s, sid] end Hash[ sids ] end def add_members(*names) - Puppet.deprecation_warning('Puppet::Util::ADSI::Group#add_members is deprecated; please use Puppet::Util::ADSI::Group#add_member_sids') + Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#add_member_sids') sids = self.class.name_sid_hash(names) add_member_sids(*sids.values) end alias add_member add_members def remove_members(*names) - Puppet.deprecation_warning('Puppet::Util::ADSI::Group#remove_members is deprecated; please use Puppet::Util::ADSI::Group#remove_member_sids') + Puppet.deprecation_warning('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#remove_member_sids') sids = self.class.name_sid_hash(names) remove_member_sids(*sids.values) end alias remove_member remove_members def add_member_sids(*sids) sids.each do |sid| - native_group.Add(Puppet::Util::ADSI.sid_uri(sid)) + native_group.Add(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def remove_member_sids(*sids) sids.each do |sid| - native_group.Remove(Puppet::Util::ADSI.sid_uri(sid)) + native_group.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def members # WIN32OLE objects aren't enumerable, so no map members = [] native_group.Members.each {|m| members << m.Name} members end def member_sids sids = [] native_group.Members.each do |m| sids << Puppet::Util::Windows::Security.octet_string_to_sid_object(m.objectSID) end sids end def set_members(desired_members) return if desired_members.nil? or desired_members.empty? current_hash = Hash[ self.member_sids.map { |sid| [sid.to_s, sid] } ] desired_hash = self.class.name_sid_hash(desired_members) # First we add all missing members members_to_add = (desired_hash.keys - current_hash.keys).map { |sid| desired_hash[sid] } add_member_sids(*members_to_add) # Then we remove all extra members members_to_remove = (current_hash.keys - desired_hash.keys).map { |sid| current_hash[sid] } remove_member_sids(*members_to_remove) end def self.create(name) # Windows error 2224: The account already exists. - raise Puppet::Error.new( "Cannot create group if user '#{name}' exists." ) if Puppet::Util::ADSI::User.exists? name - new(name, Puppet::Util::ADSI.create(name, 'group')) + raise Puppet::Error.new( "Cannot create group if user '#{name}' exists." ) if Puppet::Util::Windows::ADSI::User.exists? name + new(name, Puppet::Util::Windows::ADSI.create(name, 'group')) end def self.exists?(name) - Puppet::Util::ADSI.connectable?(Group.uri(name)) + Puppet::Util::Windows::ADSI.connectable?(Group.uri(name)) end def self.delete(name) - Puppet::Util::ADSI.delete(name, 'group') + Puppet::Util::Windows::ADSI.delete(name, 'group') end def self.each(&block) - wql = Puppet::Util::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' ) + wql = Puppet::Util::Windows::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' ) groups = [] wql.each do |g| groups << new(g.name) end groups.each(&block) end end end diff --git a/lib/puppet/util/windows/api_types.rb b/lib/puppet/util/windows/api_types.rb index f7325a588..0ebead29b 100644 --- a/lib/puppet/util/windows/api_types.rb +++ b/lib/puppet/util/windows/api_types.rb @@ -1,86 +1,90 @@ require 'ffi' require 'puppet/util/windows/string' module Puppet::Util::Windows::APITypes module ::FFI::Library # Wrapper method for attach_function + private def attach_function_private(*args) attach_function(*args) private args[0] end end class ::FFI::MemoryPointer def self.from_string_to_wide_string(str) str = Puppet::Util::Windows::String.wide_string(str) ptr = FFI::MemoryPointer.new(:byte, str.bytesize) # uchar here is synonymous with byte ptr.put_array_of_uchar(0, str.bytes.to_a) ptr end def read_bool # BOOL is always a 32-bit integer in Win32 # some Win32 APIs return 1 for true, while others are non-0 read_int32 != 0 end + alias_method :read_dword, :read_uint32 + def read_handle type_size == 4 ? read_uint32 : read_uint64 end def read_wide_string(char_length) # char_length is number of wide chars (typically excluding NULLs), *not* bytes str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE') str.encode(Encoding.default_external) end + + alias_method :write_dword, :write_uint32 end # FFI Types # https://github.com/ffi/ffi/wiki/Types # Windows - Common Data Types # http://msdn.microsoft.com/en-us/library/cc230309.aspx # Windows Data Types # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx FFI.typedef :uint16, :word FFI.typedef :uint32, :dword # uintptr_t is defined in an FFI conf as platform specific, either # ulong_long on x64 or just ulong on x86 FFI.typedef :uintptr_t, :handle # buffer_inout is similar to pointer (platform specific), but optimized for buffers FFI.typedef :buffer_inout, :lpwstr # buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers FFI.typedef :buffer_in, :lpcwstr # string is also similar to pointer, but should be used for const char * # NOTE that this is not wide, useful only for A suffixed functions FFI.typedef :string, :lpcstr # pointer in FFI is platform specific # NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL FFI.typedef :pointer, :lpvoid FFI.typedef :pointer, :lpword FFI.typedef :pointer, :lpdword FFI.typedef :pointer, :pdword FFI.typedef :pointer, :phandle FFI.typedef :pointer, :ulong_ptr FFI.typedef :pointer, :pbool # any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width # which is what FFI uses by default # instead create new aliases for these very special cases # NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it FFI.typedef :uint32, :win32_ulong FFI.typedef :int32, :win32_long # NOTE: FFI already defines ushort as a 16-bit unsigned like this: # FFI.typedef :uint16, :ushort # 8 bits per byte FFI.typedef :uchar, :byte end diff --git a/spec/integration/util/windows/security_spec.rb b/spec/integration/util/windows/security_spec.rb index fa0eadc0d..46edd96b8 100755 --- a/spec/integration/util/windows/security_spec.rb +++ b/spec/integration/util/windows/security_spec.rb @@ -1,863 +1,861 @@ #!/usr/bin/env ruby require 'spec_helper' -require 'puppet/util/adsi' - if Puppet.features.microsoft_windows? class WindowsSecurityTester require 'puppet/util/windows/security' include Puppet::Util::Windows::Security end end describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :all do @sids = { :current_user => Puppet::Util::Windows::Security.name_to_sid(Sys::Admin.get_login), :system => Win32::Security::SID::LocalSystem, :admin => Puppet::Util::Windows::Security.name_to_sid("Administrator"), :administrators => Win32::Security::SID::BuiltinAdministrators, :guest => Puppet::Util::Windows::Security.name_to_sid("Guest"), :users => Win32::Security::SID::BuiltinUsers, :power_users => Win32::Security::SID::PowerUsers, :none => Win32::Security::SID::Nobody, :everyone => Win32::Security::SID::Everyone } # The TCP/IP NetBIOS Helper service (aka 'lmhosts') has ended up # disabled on some VMs for reasons we couldn't track down. This # condition causes tests which rely on resolving UNC style paths # (like \\localhost) to fail with unhelpful error messages. # Put a check for this upfront to aid debug should this strike again. service = Puppet::Type.type(:service).new(:name => 'lmhosts') service.provider.status.should == :running end let (:sids) { @sids } let (:winsec) { WindowsSecurityTester.new } def set_group_depending_on_current_user(path) if sids[:current_user] == sids[:system] # if the current user is SYSTEM, by setting the group to # guest, SYSTEM is automagically given full control, so instead # override that behavior with SYSTEM as group and a specific mode winsec.set_group(sids[:system], path) mode = winsec.get_mode(path) winsec.set_mode(mode & ~WindowsSecurityTester::S_IRWXG, path) else winsec.set_group(sids[:guest], path) end end def grant_everyone_full_access(path) sd = winsec.get_security_descriptor(path) everyone = 'S-1-1-0' inherit = WindowsSecurityTester::OBJECT_INHERIT_ACE | WindowsSecurityTester::CONTAINER_INHERIT_ACE sd.dacl.allow(everyone, Windows::File::FILE_ALL_ACCESS, inherit) winsec.set_security_descriptor(path, sd) end shared_examples_for "only child owner" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0700, parent) check_delete(path) end it "should deny parent owner" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end it "should deny group" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end it "should deny other" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end end shared_examples_for "a securable object" do describe "on a volume that doesn't support ACLs" do [:owner, :group, :mode].each do |p| it "should return nil #{p}" do winsec.stubs(:supports_acl?).returns false winsec.send("get_#{p}", path).should be_nil end end end describe "on a volume that supports ACLs" do describe "for a normal user" do before :each do Puppet.features.stubs(:root?).returns(false) end after :each do winsec.set_mode(WindowsSecurityTester::S_IRWXU, parent) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) if Puppet::FileSystem.exist?(path) end describe "#supports_acl?" do %w[c:/ c:\\ c:/windows/system32 \\\\localhost\\C$ \\\\127.0.0.1\\C$\\foo].each do |path| it "should accept #{path}" do winsec.should be_supports_acl(path) end end it "should raise an exception if it cannot get volume information" do expect { winsec.supports_acl?('foobar') }.to raise_error(Puppet::Error, /Failed to get volume information/) end end describe "#owner=" do it "should allow setting to the current user" do winsec.set_owner(sids[:current_user], path) end it "should raise an exception when setting to a different user" do lambda { winsec.set_owner(sids[:guest], path) }.should raise_error(Puppet::Error, /This security ID may not be assigned as the owner of this object./) end end describe "#owner" do it "it should not be empty" do winsec.get_owner(path).should_not be_empty end it "should raise an exception if an invalid path is provided" do lambda { winsec.get_owner("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "#group=" do it "should allow setting to a group the current owner is a member of" do winsec.set_group(sids[:users], path) end # Unlike unix, if the user has permission to WRITE_OWNER, which the file owner has by default, # then they can set the primary group to a group that the user does not belong to. it "should allow setting to a group the current owner is not a member of" do winsec.set_group(sids[:power_users], path) end end describe "#group" do it "should not be empty" do winsec.get_group(path).should_not be_empty end it "should raise an exception if an invalid path is provided" do lambda { winsec.get_group("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end it "should preserve inherited full control for SYSTEM when setting owner and group" do # new file has SYSTEM system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.should_not be_empty # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these system_aces.any? do |ace| ace.mask == Windows::File::FILE_ALL_ACCESS end.should be_true # changing the owner/group will no longer make the SD protected winsec.set_group(sids[:power_users], path) winsec.set_owner(sids[:administrators], path) system_aces.find do |ace| ace.mask == Windows::File::FILE_ALL_ACCESS && ace.inherited? end.should_not be_nil end describe "#mode=" do (0000..0700).step(0100) do |mode| it "should enforce mode #{mode.to_s(8)}" do winsec.set_mode(mode, path) check_access(mode, path) end end it "should round-trip all 128 modes that do not require deny ACEs" do 0.upto(1).each do |s| 0.upto(7).each do |u| 0.upto(u).each do |g| 0.upto(g).each do |o| # if user is superset of group, and group superset of other, then # no deny ace is required, and mode can be converted to win32 # access mask, and back to mode without loss of information # (provided the owner and group are not the same) next if ((u & g) != g) or ((g & o) != o) mode = (s << 9 | u << 6 | g << 3 | o << 0) winsec.set_mode(mode, path) winsec.get_mode(path).to_s(8).should == mode.to_s(8) end end end end end it "should preserve full control for SYSTEM when setting mode" do # new file has SYSTEM system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.should_not be_empty # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these system_aces.any? do |ace| ace.mask == WindowsSecurityTester::FILE_ALL_ACCESS end.should be_true # changing the mode will make the SD protected winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) # and should have a non-inherited SYSTEM ACE(s) system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.each do |ace| ace.mask.should == Windows::File::FILE_ALL_ACCESS && ! ace.inherited? end end describe "for modes that require deny aces" do it "should map everyone to group and owner" do winsec.set_mode(0426, path) winsec.get_mode(path).to_s(8).should == "666" end it "should combine user and group modes when owner and group sids are equal" do winsec.set_group(winsec.get_owner(path), path) winsec.set_mode(0410, path) winsec.get_mode(path).to_s(8).should == "550" end end describe "for read-only objects" do before :each do winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) winsec.add_attributes(path, WindowsSecurityTester::FILE_ATTRIBUTE_READONLY) (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should be_nonzero end it "should make them writable if any sid has write permission" do winsec.set_mode(WindowsSecurityTester::S_IWUSR, path) (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should == 0 end it "should leave them read-only if no sid has write permission and should allow full access for SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IXGRP, path) (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should be_nonzero system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) # when running under SYSTEM account, and set_group / set_owner hasn't been called # SYSTEM full access will be restored system_aces.any? do |ace| ace.mask == Windows::File::FILE_ALL_ACCESS end.should be_true end end it "should raise an exception if an invalid path is provided" do lambda { winsec.set_mode(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "#mode" do it "should report when extra aces are encounted" do sd = winsec.get_security_descriptor(path) (544..547).each do |rid| sd.dacl.allow("S-1-5-32-#{rid}", WindowsSecurityTester::STANDARD_RIGHTS_ALL) end winsec.set_security_descriptor(path, sd) mode = winsec.get_mode(path) (mode & WindowsSecurityTester::S_IEXTRA).should == WindowsSecurityTester::S_IEXTRA end it "should return deny aces" do sd = winsec.get_security_descriptor(path) sd.dacl.deny(sids[:guest], WindowsSecurityTester::FILE_GENERIC_WRITE) winsec.set_security_descriptor(path, sd) guest_aces = winsec.get_aces_for_path_by_sid(path, sids[:guest]) guest_aces.find do |ace| ace.type == WindowsSecurityTester::ACCESS_DENIED_ACE_TYPE end.should_not be_nil end it "should skip inherit-only ace" do sd = winsec.get_security_descriptor(path) dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow( sids[:current_user], WindowsSecurityTester::STANDARD_RIGHTS_ALL | WindowsSecurityTester::SPECIFIC_RIGHTS_ALL ) dacl.allow( sids[:everyone], WindowsSecurityTester::FILE_GENERIC_READ, WindowsSecurityTester::INHERIT_ONLY_ACE | WindowsSecurityTester::OBJECT_INHERIT_ACE ) winsec.set_security_descriptor(path, sd) (winsec.get_mode(path) & WindowsSecurityTester::S_IRWXO).should == 0 end it "should raise an exception if an invalid path is provided" do lambda { winsec.get_mode("c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "inherited access control entries" do it "should be absent when the access control list is protected, and should not remove SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) mode = winsec.get_mode(path) [ WindowsSecurityTester::S_IEXTRA, WindowsSecurityTester::S_ISYSTEM_MISSING ].each do |flag| (mode & flag).should_not == flag end end it "should be present when the access control list is unprotected" do # add a bunch of aces to the parent with permission to add children allow = WindowsSecurityTester::STANDARD_RIGHTS_ALL | WindowsSecurityTester::SPECIFIC_RIGHTS_ALL inherit = WindowsSecurityTester::OBJECT_INHERIT_ACE | WindowsSecurityTester::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(parent) sd.dacl.allow( "S-1-1-0", #everyone allow, inherit ) (544..547).each do |rid| sd.dacl.allow( "S-1-5-32-#{rid}", WindowsSecurityTester::STANDARD_RIGHTS_ALL, inherit ) end winsec.set_security_descriptor(parent, sd) # unprotect child, it should inherit from parent winsec.set_mode(WindowsSecurityTester::S_IRWXU, path, false) (winsec.get_mode(path) & WindowsSecurityTester::S_IEXTRA).should == WindowsSecurityTester::S_IEXTRA end end end describe "for an administrator", :if => Puppet.features.root? do before :each do winsec.set_mode(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG, path) set_group_depending_on_current_user(path) winsec.set_owner(sids[:guest], path) lambda { File.open(path, 'r') }.should raise_error(Errno::EACCES) end after :each do if Puppet::FileSystem.exist?(path) winsec.set_owner(sids[:current_user], path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) end end describe "#owner=" do it "should accept a user sid" do winsec.set_owner(sids[:admin], path) winsec.get_owner(path).should == sids[:admin] end it "should accept a group sid" do winsec.set_owner(sids[:power_users], path) winsec.get_owner(path).should == sids[:power_users] end it "should raise an exception if an invalid sid is provided" do lambda { winsec.set_owner("foobar", path) }.should raise_error(Puppet::Error, /Failed to convert string SID/) end it "should raise an exception if an invalid path is provided" do lambda { winsec.set_owner(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "#group=" do it "should accept a group sid" do winsec.set_group(sids[:power_users], path) winsec.get_group(path).should == sids[:power_users] end it "should accept a user sid" do winsec.set_group(sids[:admin], path) winsec.get_group(path).should == sids[:admin] end it "should combine owner and group rights when they are the same sid" do winsec.set_owner(sids[:power_users], path) winsec.set_group(sids[:power_users], path) winsec.set_mode(0610, path) winsec.get_owner(path).should == sids[:power_users] winsec.get_group(path).should == sids[:power_users] # note group execute permission added to user ace, and then group rwx value # reflected to match # Exclude missing system ace, since that's not relevant (winsec.get_mode(path) & 0777).to_s(8).should == "770" end it "should raise an exception if an invalid sid is provided" do lambda { winsec.set_group("foobar", path) }.should raise_error(Puppet::Error, /Failed to convert string SID/) end it "should raise an exception if an invalid path is provided" do lambda { winsec.set_group(sids[:guest], "c:\\doesnotexist.txt") }.should raise_error(Puppet::Error, /The system cannot find the file specified./) end end describe "when the sid is NULL" do it "should retrieve an empty owner sid" it "should retrieve an empty group sid" end describe "when the sid refers to a deleted trustee" do it "should retrieve the user sid" do sid = nil - user = Puppet::Util::ADSI::User.create("delete_me_user") + user = Puppet::Util::Windows::ADSI::User.create("delete_me_user") user.commit begin sid = Sys::Admin::get_user(user.name).sid winsec.set_owner(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) ensure - Puppet::Util::ADSI::User.delete(user.name) + Puppet::Util::Windows::ADSI::User.delete(user.name) end winsec.get_owner(path).should == sid winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXU end it "should retrieve the group sid" do sid = nil - group = Puppet::Util::ADSI::Group.create("delete_me_group") + group = Puppet::Util::Windows::ADSI::Group.create("delete_me_group") group.commit begin sid = Sys::Admin::get_group(group.name).sid winsec.set_group(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXG, path) ensure - Puppet::Util::ADSI::Group.delete(group.name) + Puppet::Util::Windows::ADSI::Group.delete(group.name) end winsec.get_group(path).should == sid winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXG end end describe "#mode" do it "should deny all access when the DACL is empty, including SYSTEM" do sd = winsec.get_security_descriptor(path) # don't allow inherited aces to affect the test protect = true new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, [], protect) winsec.set_security_descriptor(path, new_sd) winsec.get_mode(path).should == WindowsSecurityTester::S_ISYSTEM_MISSING end # REMIND: ruby crashes when trying to set a NULL DACL # it "should allow all when it is nil" do # winsec.set_owner(sids[:current_user], path) # winsec.open_file(path, WindowsSecurityTester::READ_CONTROL | WindowsSecurityTester::WRITE_DAC) do |handle| # winsec.set_security_info(handle, WindowsSecurityTester::DACL_SECURITY_INFORMATION | WindowsSecurityTester::PROTECTED_DACL_SECURITY_INFORMATION, nil) # end # winsec.get_mode(path).to_s(8).should == "777" # end end describe "when the parent directory" do before :each do winsec.set_owner(sids[:current_user], parent) winsec.set_owner(sids[:current_user], path) winsec.set_mode(0777, path, false) end describe "is writable and executable" do describe "and sticky bit is set" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01700, parent) check_delete(path) end it "should allow parent owner" do winsec.set_owner(sids[:current_user], parent) winsec.set_group(sids[:guest], parent) winsec.set_mode(01700, parent) winsec.set_owner(sids[:current_user], path) winsec.set_group(sids[:guest], path) winsec.set_mode(0700, path) check_delete(path) end it "should deny group" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01770, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end it "should deny other" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01777, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) lambda { check_delete(path) }.should raise_error(Errno::EACCES) end end describe "and sticky bit is not set" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0700, parent) check_delete(path) end it "should allow parent owner" do winsec.set_owner(sids[:current_user], parent) winsec.set_group(sids[:guest], parent) winsec.set_mode(0700, parent) winsec.set_owner(sids[:current_user], path) winsec.set_group(sids[:guest], path) winsec.set_mode(0700, path) check_delete(path) end it "should allow group" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0770, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) check_delete(path) end it "should allow other" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0777, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) check_delete(path) end end end describe "is not writable" do before :each do winsec.set_group(sids[:current_user], parent) winsec.set_mode(0555, parent) end it_behaves_like "only child owner" end describe "is not executable" do before :each do winsec.set_group(sids[:current_user], parent) winsec.set_mode(0666, parent) end it_behaves_like "only child owner" end end end end end describe "file" do let (:parent) do tmpdir('win_sec_test_file') end let (:path) do path = File.join(parent, 'childfile') File.new(path, 'w').close path end after :each do # allow temp files to be cleaned up grant_everyone_full_access(parent) end it_behaves_like "a securable object" do def check_access(mode, path) if (mode & WindowsSecurityTester::S_IRUSR).nonzero? check_read(path) else lambda { check_read(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IWUSR).nonzero? check_write(path) else lambda { check_write(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IXUSR).nonzero? lambda { check_execute(path) }.should raise_error(Errno::ENOEXEC) else lambda { check_execute(path) }.should raise_error(Errno::EACCES) end end def check_read(path) File.open(path, 'r').close end def check_write(path) File.open(path, 'w').close end def check_execute(path) Kernel.exec(path) end def check_delete(path) File.delete(path) end end describe "locked files" do let (:explorer) { File.join(Dir::WINDOWS, "explorer.exe") } it "should get the owner" do winsec.get_owner(explorer).should match /^S-1-5-/ end it "should get the group" do winsec.get_group(explorer).should match /^S-1-5-/ end it "should get the mode" do winsec.get_mode(explorer).should == (WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG | WindowsSecurityTester::S_IEXTRA) end end end describe "directory" do let (:parent) do tmpdir('win_sec_test_dir') end let (:path) do path = File.join(parent, 'childdir') Dir.mkdir(path) path end after :each do # allow temp files to be cleaned up grant_everyone_full_access(parent) end it_behaves_like "a securable object" do def check_access(mode, path) if (mode & WindowsSecurityTester::S_IRUSR).nonzero? check_read(path) else lambda { check_read(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IWUSR).nonzero? check_write(path) else lambda { check_write(path) }.should raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IXUSR).nonzero? check_execute(path) else lambda { check_execute(path) }.should raise_error(Errno::EACCES) end end def check_read(path) Dir.entries(path) end def check_write(path) Dir.mkdir(File.join(path, "subdir")) end def check_execute(path) Dir.chdir(path) {} end def check_delete(path) Dir.rmdir(path) end end describe "inheritable aces" do it "should be applied to child objects" do mode640 = WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IWUSR | WindowsSecurityTester::S_IRGRP winsec.set_mode(mode640, path) newfile = File.join(path, "newfile.txt") File.new(newfile, "w").close newdir = File.join(path, "newdir") Dir.mkdir(newdir) [newfile, newdir].each do |p| mode = winsec.get_mode(p) (mode & 07777).to_s(8).should == mode640.to_s(8) end end end end context "security descriptor" do let(:path) { tmpfile('sec_descriptor') } let(:read_execute) { 0x201FF } let(:synchronize) { 0x100000 } before :each do FileUtils.touch(path) end it "preserves aces for other users" do dacl = Puppet::Util::Windows::AccessControlList.new sids_in_dacl = [sids[:current_user], sids[:users]] sids_in_dacl.each do |sid| dacl.allow(sid, read_execute) end sd = Puppet::Util::Windows::SecurityDescriptor.new(sids[:guest], sids[:guest], dacl, true) winsec.set_security_descriptor(path, sd) aces = winsec.get_security_descriptor(path).dacl.to_a aces.map(&:sid).should == sids_in_dacl aces.map(&:mask).all? { |mask| mask == read_execute }.should be_true end it "changes the sid for all aces that were assigned to the old owner" do sd = winsec.get_security_descriptor(path) sd.owner.should_not == sids[:guest] sd.dacl.allow(sd.owner, read_execute) sd.dacl.allow(sd.owner, synchronize) sd.owner = sids[:guest] winsec.set_security_descriptor(path, sd) dacl = winsec.get_security_descriptor(path).dacl aces = dacl.find_all { |ace| ace.sid == sids[:guest] } # only non-inherited aces will be reassigned to guest, so # make sure we find at least the two we added aces.size.should >= 2 end it "preserves INHERIT_ONLY_ACEs" do # inherit only aces can only be set on directories dir = tmpdir('inheritonlyace') inherit_flags = Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) sd.dacl.allow(sd.owner, Windows::File::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) winsec.set_owner(sids[:guest], dir) sd = winsec.get_security_descriptor(dir) sd.dacl.find do |ace| ace.sid == sids[:guest] && ace.inherit_only? end.should_not be_nil end it "allows deny ACEs with inheritance" do # inheritance can only be set on directories dir = tmpdir('denyaces') inherit_flags = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) sd.dacl.deny(sids[:guest], Windows::File::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) sd.dacl.find do |ace| ace.sid == sids[:guest] && ace.flags != 0 end.should_not be_nil end context "when managing mode" do it "removes aces for sids that are neither the owner nor group" do # add a guest ace, it's never owner or group sd = winsec.get_security_descriptor(path) sd.dacl.allow(sids[:guest], read_execute) winsec.set_security_descriptor(path, sd) # setting the mode, it should remove extra aces winsec.set_mode(0770, path) # make sure it's gone dacl = winsec.get_security_descriptor(path).dacl aces = dacl.find_all { |ace| ace.sid == sids[:guest] } aces.should be_empty end end end end diff --git a/spec/unit/provider/group/windows_adsi_spec.rb b/spec/unit/provider/group/windows_adsi_spec.rb index a7de859da..2b505b60a 100644 --- a/spec/unit/provider/group/windows_adsi_spec.rb +++ b/spec/unit/provider/group/windows_adsi_spec.rb @@ -1,168 +1,168 @@ #!/usr/bin/env ruby require 'spec_helper' -describe Puppet::Type.type(:group).provider(:windows_adsi) do +describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:group).new( :title => 'testers', :provider => :windows_adsi ) end let(:provider) { resource.provider } let(:connection) { stub 'connection' } before :each do - Puppet::Util::ADSI.stubs(:computer_name).returns('testcomputername') - Puppet::Util::ADSI.stubs(:connect).returns connection + Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') + Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do it "should enumerate all groups" do names = ['group1', 'group2', 'group3'] stub_groups = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns stub_groups described_class.instances.map(&:name).should =~ names end end - describe "group type :members property helpers", :if => Puppet.features.microsoft_windows? do + describe "group type :members property helpers" do let(:user1) { stub(:account => 'user1', :domain => '.', :to_s => 'user1sid') } let(:user2) { stub(:account => 'user2', :domain => '.', :to_s => 'user2sid') } before :each do Puppet::Util::Windows::Security.stubs(:name_to_sid_object).with('user1').returns(user1) Puppet::Util::Windows::Security.stubs(:name_to_sid_object).with('user2').returns(user2) end describe "#members_insync?" do it "should return false when current is nil" do provider.members_insync?(nil, ['user2']).should be_false end it "should return false when should is nil" do provider.members_insync?(['user1'], nil).should be_false end it "should return false for differing lists of members" do provider.members_insync?(['user1'], ['user2']).should be_false provider.members_insync?(['user1'], []).should be_false provider.members_insync?([], ['user2']).should be_false end it "should return true for same lists of members" do provider.members_insync?(['user1', 'user2'], ['user1', 'user2']).should be_true end it "should return true for same lists of unordered members" do provider.members_insync?(['user1', 'user2'], ['user2', 'user1']).should be_true end it "should return true for same lists of members irrespective of duplicates" do provider.members_insync?(['user1', 'user2', 'user2'], ['user2', 'user1', 'user1']).should be_true end end describe "#members_to_s" do it "should return an empty string on non-array input" do [Object.new, {}, 1, :symbol, ''].each do |input| provider.members_to_s(input).should be_empty end end it "should return an empty string on empty or nil users" do provider.members_to_s([]).should be_empty provider.members_to_s(nil).should be_empty end it "should return a user string like DOMAIN\\USER" do provider.members_to_s(['user1']).should == '.\user1' end it "should return a user string like DOMAIN\\USER,DOMAIN2\\USER2" do provider.members_to_s(['user1', 'user2']).should == '.\user1,.\user2' end end end describe "when managing members" do it "should be able to provide a list of members" do provider.group.stubs(:members).returns ['user1', 'user2', 'user3'] provider.members.should =~ ['user1', 'user2', 'user3'] end - it "should be able to set group members", :if => Puppet.features.microsoft_windows? do + it "should be able to set group members" do provider.group.stubs(:members).returns ['user1', 'user2'] member_sids = [ stub(:account => 'user1', :domain => 'testcomputername'), stub(:account => 'user2', :domain => 'testcomputername'), stub(:account => 'user3', :domain => 'testcomputername'), ] provider.group.stubs(:member_sids).returns(member_sids[0..1]) Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user2').returns(member_sids[1]) Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user3').returns(member_sids[2]) provider.group.expects(:remove_member_sids).with(member_sids[0]) provider.group.expects(:add_member_sids).with(member_sids[2]) provider.members = ['user2', 'user3'] end end describe 'when creating groups' do it "should be able to create a group" do resource[:members] = ['user1', 'user2'] group = stub 'group' - Puppet::Util::ADSI::Group.expects(:create).with('testers').returns group + Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group create = sequence('create') group.expects(:commit).in_sequence(create) group.expects(:set_members).with(['user1', 'user2']).in_sequence(create) provider.create end it 'should not create a group if a user by the same name exists' do - Puppet::Util::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) + Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create group if user 'testers' exists./ ) end it 'should commit a newly created group' do provider.group.expects( :commit ) provider.flush end end it "should be able to test whether a group exists" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.stubs(:connect).returns stub('connection') + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') provider.should be_exists - Puppet::Util::ADSI.stubs(:connect).returns nil + Puppet::Util::Windows::ADSI.stubs(:connect).returns nil provider.should_not be_exists end it "should be able to delete a group" do connection.expects(:Delete).with('group', 'testers') provider.delete end - it "should report the group's SID as gid", :if => Puppet.features.microsoft_windows? do + it "should report the group's SID as gid" do Puppet::Util::Windows::Security.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') provider.gid.should == 'S-1-5-32-547' end it "should fail when trying to manage the gid property" do provider.expects(:fail).with { |msg| msg =~ /gid is read-only/ } provider.send(:gid=, 500) end - it "should prefer the domain component from the resolved SID", :if => Puppet.features.microsoft_windows? do + it "should prefer the domain component from the resolved SID" do provider.members_to_s(['.\Administrators']).should == 'BUILTIN\Administrators' end end diff --git a/spec/unit/provider/user/windows_adsi_spec.rb b/spec/unit/provider/user/windows_adsi_spec.rb index 8d3ed1d0a..6dbd695e0 100755 --- a/spec/unit/provider/user/windows_adsi_spec.rb +++ b/spec/unit/provider/user/windows_adsi_spec.rb @@ -1,173 +1,173 @@ #!/usr/bin/env ruby require 'spec_helper' -describe Puppet::Type.type(:user).provider(:windows_adsi) do +describe Puppet::Type.type(:user).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:user).new( :title => 'testuser', :comment => 'Test J. User', :provider => :windows_adsi ) end let(:provider) { resource.provider } let(:connection) { stub 'connection' } before :each do - Puppet::Util::ADSI.stubs(:computer_name).returns('testcomputername') - Puppet::Util::ADSI.stubs(:connect).returns connection + Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') + Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end describe ".instances" do it "should enumerate all users" do names = ['user1', 'user2', 'user3'] stub_users = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(stub_users) described_class.instances.map(&:name).should =~ names end end - it "should provide access to a Puppet::Util::ADSI::User object" do - provider.user.should be_a(Puppet::Util::ADSI::User) + it "should provide access to a Puppet::Util::Windows::ADSI::User object" do + provider.user.should be_a(Puppet::Util::Windows::ADSI::User) end describe "when managing groups" do it 'should return the list of groups as a comma-separated list' do provider.user.stubs(:groups).returns ['group1', 'group2', 'group3'] provider.groups.should == 'group1,group2,group3' end it "should return absent if there are no groups" do provider.user.stubs(:groups).returns [] provider.groups.should == '' end it 'should be able to add a user to a set of groups' do resource[:membership] = :minimum provider.user.expects(:set_groups).with('group1,group2', true) provider.groups = 'group1,group2' resource[:membership] = :inclusive provider.user.expects(:set_groups).with('group1,group2', false) provider.groups = 'group1,group2' end end describe "when creating a user" do it "should create the user on the system and set its other properties" do resource[:groups] = ['group1', 'group2'] resource[:membership] = :inclusive resource[:comment] = 'a test user' resource[:home] = 'C:\Users\testuser' user = stub 'user' - Puppet::Util::ADSI::User.expects(:create).with('testuser').returns user + Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user user.stubs(:groups).returns(['group2', 'group3']) create = sequence('create') user.expects(:password=).in_sequence(create) user.expects(:commit).in_sequence(create) user.expects(:set_groups).with('group1,group2', false).in_sequence(create) user.expects(:[]=).with('Description', 'a test user') user.expects(:[]=).with('HomeDirectory', 'C:\Users\testuser') provider.create end - it "should load the profile if managehome is set", :if => Puppet.features.microsoft_windows? do + it "should load the profile if managehome is set" do resource[:password] = '0xDeadBeef' resource[:managehome] = true user = stub_everything 'user' - Puppet::Util::ADSI::User.expects(:create).with('testuser').returns user + Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user Puppet::Util::Windows::User.expects(:load_profile).with('testuser', '0xDeadBeef') provider.create end it "should set a user's password" do provider.user.expects(:password=).with('plaintextbad') provider.password = "plaintextbad" end it "should test a valid user password" do resource[:password] = 'plaintext' provider.user.expects(:password_is?).with('plaintext').returns true provider.password.should == 'plaintext' end it "should test a bad user password" do resource[:password] = 'plaintext' provider.user.expects(:password_is?).with('plaintext').returns false provider.password.should == :absent end it 'should not create a user if a group by the same name exists' do - Puppet::Util::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") ) + Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create user if group 'testuser' exists./ ) end end it 'should be able to test whether a user exists' do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.stubs(:connect).returns stub('connection') + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection') provider.should be_exists - Puppet::Util::ADSI.stubs(:connect).returns nil + Puppet::Util::Windows::ADSI.stubs(:connect).returns nil provider.should_not be_exists end it 'should be able to delete a user' do connection.expects(:Delete).with('user', 'testuser') provider.delete end - it 'should delete the profile if managehome is set', :if => Puppet.features.microsoft_windows? do + it 'should delete the profile if managehome is set' do resource[:managehome] = true sid = 'S-A-B-C' Puppet::Util::Windows::Security.expects(:name_to_sid).with('testuser').returns(sid) - Puppet::Util::ADSI::UserProfile.expects(:delete).with(sid) + Puppet::Util::Windows::ADSI::UserProfile.expects(:delete).with(sid) connection.expects(:Delete).with('user', 'testuser') provider.delete end it "should commit the user when flushed" do provider.user.expects(:commit) provider.flush end - it "should return the user's SID as uid", :if => Puppet.features.microsoft_windows? do + it "should return the user's SID as uid" do Puppet::Util::Windows::Security.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111') provider.uid.should == 'S-1-5-21-1362942247-2130103807-3279964888-1111' end it "should fail when trying to manage the uid property" do provider.expects(:fail).with { |msg| msg =~ /uid is read-only/ } provider.send(:uid=, 500) end [:gid, :shell].each do |prop| it "should fail when trying to manage the #{prop} property" do provider.expects(:fail).with { |msg| msg =~ /No support for managing property #{prop}/ } provider.send("#{prop}=", 'foo') end end end diff --git a/spec/unit/util/adsi_spec.rb b/spec/unit/util/windows/adsi_spec.rb similarity index 59% rename from spec/unit/util/adsi_spec.rb rename to spec/unit/util/windows/adsi_spec.rb index 491c4374b..60ad503f4 100755 --- a/spec/unit/util/adsi_spec.rb +++ b/spec/unit/util/windows/adsi_spec.rb @@ -1,437 +1,440 @@ #!/usr/bin/env ruby require 'spec_helper' -require 'puppet/util/adsi' +require 'puppet/util/windows' -describe Puppet::Util::ADSI do +describe Puppet::Util::Windows::ADSI, :if => Puppet.features.microsoft_windows? do let(:connection) { stub 'connection' } before(:each) do - Puppet::Util::ADSI.instance_variable_set(:@computer_name, 'testcomputername') - Puppet::Util::ADSI.stubs(:connect).returns connection + Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, 'testcomputername') + Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end after(:each) do - Puppet::Util::ADSI.instance_variable_set(:@computer_name, nil) + Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) end it "should generate the correct URI for a resource" do - Puppet::Util::ADSI.uri('test', 'user').should == "WinNT://./test,user" + Puppet::Util::Windows::ADSI.uri('test', 'user').should == "WinNT://./test,user" end it "should be able to get the name of the computer" do - Puppet::Util::ADSI.computer_name.should == 'testcomputername' + Puppet::Util::Windows::ADSI.computer_name.should == 'testcomputername' end it "should be able to provide the correct WinNT base URI for the computer" do - Puppet::Util::ADSI.computer_uri.should == "WinNT://." + Puppet::Util::Windows::ADSI.computer_uri.should == "WinNT://." end it "should generate a fully qualified WinNT URI" do - Puppet::Util::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername" + Puppet::Util::Windows::ADSI.computer_uri('testcomputername').should == "WinNT://testcomputername" end - describe ".sid_for_account", :if => Puppet.features.microsoft_windows? do + describe ".sid_for_account" do it "should return nil if the account does not exist" do Puppet::Util::Windows::Security.expects(:name_to_sid).with('foobar').returns nil - Puppet::Util::ADSI.sid_for_account('foobar').should be_nil + Puppet::Util::Windows::ADSI.sid_for_account('foobar').should be_nil end it "should return a SID for a passed user or group name" do Puppet::Util::Windows::Security.expects(:name_to_sid).with('testers').returns 'S-1-5-32-547' - Puppet::Util::ADSI.sid_for_account('testers').should == 'S-1-5-32-547' + Puppet::Util::Windows::ADSI.sid_for_account('testers').should == 'S-1-5-32-547' end it "should return a SID for a passed fully-qualified user or group name" do Puppet::Util::Windows::Security.expects(:name_to_sid).with('MACHINE\testers').returns 'S-1-5-32-547' - Puppet::Util::ADSI.sid_for_account('MACHINE\testers').should == 'S-1-5-32-547' + Puppet::Util::Windows::ADSI.sid_for_account('MACHINE\testers').should == 'S-1-5-32-547' end end - describe ".sid_uri", :if => Puppet.features.microsoft_windows? do + describe ".computer_name" do + it "should return a non-empty ComputerName string" do + Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) + Puppet::Util::Windows::ADSI.computer_name.should_not be_empty + end + end + + describe ".sid_uri" do it "should raise an error when the input is not a SID object" do [Object.new, {}, 1, :symbol, '', nil].each do |input| expect { - Puppet::Util::ADSI.sid_uri(input) + Puppet::Util::Windows::ADSI.sid_uri(input) }.to raise_error(Puppet::Error, /Must use a valid SID object/) end end it "should return a SID uri for a well-known SID (SYSTEM)" do sid = Win32::Security::SID.new('SYSTEM') - Puppet::Util::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18' + Puppet::Util::Windows::ADSI.sid_uri(sid).should == 'WinNT://S-1-5-18' end end - describe Puppet::Util::ADSI::User do + describe Puppet::Util::Windows::ADSI::User do let(:username) { 'testuser' } let(:domain) { 'DOMAIN' } let(:domain_username) { "#{domain}\\#{username}"} it "should generate the correct URI" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI::User.uri(username).should == "WinNT://./#{username},user" + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI::User.uri(username).should == "WinNT://./#{username},user" end it "should generate the correct URI for a user with a domain" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user" + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI::User.uri(username, domain).should == "WinNT://#{domain}/#{username},user" end it "should be able to parse a username without a domain" do - Puppet::Util::ADSI::User.parse_name(username).should == [username, '.'] + Puppet::Util::Windows::ADSI::User.parse_name(username).should == [username, '.'] end it "should be able to parse a username with a domain" do - Puppet::Util::ADSI::User.parse_name(domain_username).should == [username, domain] + Puppet::Util::Windows::ADSI::User.parse_name(domain_username).should == [username, domain] end it "should raise an error with a username that contains a /" do expect { - Puppet::Util::ADSI::User.parse_name("#{domain}/#{username}") + Puppet::Util::Windows::ADSI::User.parse_name("#{domain}/#{username}") }.to raise_error(Puppet::Error, /Value must be in DOMAIN\\user style syntax/) end it "should be able to create a user" do adsi_user = stub('adsi') connection.expects(:Create).with('user', username).returns(adsi_user) - Puppet::Util::ADSI::Group.expects(:exists?).with(username).returns(false) + Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(username).returns(false) - user = Puppet::Util::ADSI::User.create(username) + user = Puppet::Util::Windows::ADSI::User.create(username) - user.should be_a(Puppet::Util::ADSI::User) + user.should be_a(Puppet::Util::Windows::ADSI::User) user.native_user.should == adsi_user end it "should be able to check the existence of a user" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection - Puppet::Util::ADSI::User.exists?(username).should be_true + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection + Puppet::Util::Windows::ADSI::User.exists?(username).should be_true end it "should be able to check the existence of a domain user" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection - Puppet::Util::ADSI::User.exists?(domain_username).should be_true + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection + Puppet::Util::Windows::ADSI::User.exists?(domain_username).should be_true end - it "should be able to confirm the existence of a user with a well-known SID", - :if => Puppet.features.microsoft_windows? do + it "should be able to confirm the existence of a user with a well-known SID" do system_user = Win32::Security::SID::LocalSystem # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::User.exists?(system_user).should be_true + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::User.exists?(system_user).should be_true end - it "should return nil with an unknown SID", - :if => Puppet.features.microsoft_windows? do + it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::User.exists?(bogus_sid).should be_false + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::User.exists?(bogus_sid).should be_false end it "should be able to delete a user" do connection.expects(:Delete).with('user', username) - Puppet::Util::ADSI::User.delete(username) + Puppet::Util::Windows::ADSI::User.delete(username) end it "should return an enumeration of IADsUser wrapped objects" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrator' wmi_users = [stub('WMI', :name => name)] - Puppet::Util::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) + Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) native_user = stub('IADsUser') homedir = "C:\\Users\\#{name}" native_user.expects(:Get).with('HomeDirectory').returns(homedir) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_user) - users = Puppet::Util::ADSI::User.to_a + users = Puppet::Util::Windows::ADSI::User.to_a users.length.should == 1 users[0].name.should == name users[0]['HomeDirectory'].should == homedir end describe "an instance" do let(:adsi_user) { stub('user', :objectSID => []) } let(:sid) { stub(:account => username, :domain => 'testcomputername') } - let(:user) { Puppet::Util::ADSI::User.new(username, adsi_user) } + let(:user) { Puppet::Util::Windows::ADSI::User.new(username, adsi_user) } it "should provide its groups as a list of names" do names = ["group1", "group2"] groups = names.map { |name| mock('group', :Name => name) } adsi_user.expects(:Groups).returns(groups) user.groups.should =~ names end it "should be able to test whether a given password is correct" do - Puppet::Util::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) - Puppet::Util::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) + Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) + Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) user.password_is?('pwdwrong').should be_false user.password_is?('pwdright').should be_true end it "should be able to set a password" do adsi_user.expects(:SetPassword).with('pwd') adsi_user.expects(:SetInfo).at_least_once flagname = "UserFlags" fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 adsi_user.expects(:Get).with(flagname).returns(0) adsi_user.expects(:Put).with(flagname, fADS_UF_DONT_EXPIRE_PASSWD) user.password = 'pwd' end - it "should generate the correct URI", :if => Puppet.features.microsoft_windows? do + it "should generate the correct URI" do Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid) user.uri.should == "WinNT://testcomputername/#{username},user" end - describe "when given a set of groups to which to add the user", :if => Puppet.features.microsoft_windows? do + describe "when given a set of groups to which to add the user" do let(:groups_to_set) { 'group1,group2' } before(:each) do Puppet::Util::Windows::Security.stubs(:octet_string_to_sid_object).returns(sid) user.expects(:groups).returns ['group2', 'group3'] end describe "if membership is specified as inclusive" do it "should add the user to those groups, and remove it from groups not in the list" do group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") group3 = stub 'group1' group3.expects(:Remove).with("WinNT://testcomputername/#{username},user") - Puppet::Util::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice - Puppet::Util::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 - Puppet::Util::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3 + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user").twice + Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 + Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group3,group').returns group3 user.set_groups(groups_to_set, false) end end describe "if membership is specified as minimum" do it "should add the user to the specified groups without affecting its other memberships" do group1 = stub 'group1' group1.expects(:Add).with("WinNT://testcomputername/#{username},user") - Puppet::Util::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user") - Puppet::Util::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sid).returns("WinNT://testcomputername/#{username},user") + Puppet::Util::Windows::ADSI.expects(:connect).with('WinNT://./group1,group').returns group1 user.set_groups(groups_to_set, true) end end end end end - describe Puppet::Util::ADSI::Group do + describe Puppet::Util::Windows::ADSI::Group do let(:groupname) { 'testgroup' } describe "an instance" do let(:adsi_group) { stub 'group' } - let(:group) { Puppet::Util::ADSI::Group.new(groupname, adsi_group) } + let(:group) { Puppet::Util::Windows::ADSI::Group.new(groupname, adsi_group) } let(:someone_sid){ stub(:account => 'someone', :domain => 'testcomputername')} - it "should be able to add a member (deprecated)", :if => Puppet.features.microsoft_windows? do - Puppet.expects(:deprecation_warning).with('Puppet::Util::ADSI::Group#add_members is deprecated; please use Puppet::Util::ADSI::Group#add_member_sids') + it "should be able to add a member (deprecated)" do + Puppet.expects(:deprecation_warning).with('Puppet::Util::Windows::ADSI::Group#add_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#add_member_sids') Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('someone').returns(someone_sid) - Puppet::Util::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") adsi_group.expects(:Add).with("WinNT://testcomputername/someone,user") group.add_member('someone') end - it "should raise when adding a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do + it "should raise when adding a member that can't resolve to a SID (deprecated)" do expect { group.add_member('foobar') }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) end - it "should be able to remove a member (deprecated)", :if => Puppet.features.microsoft_windows? do - Puppet.expects(:deprecation_warning).with('Puppet::Util::ADSI::Group#remove_members is deprecated; please use Puppet::Util::ADSI::Group#remove_member_sids') + it "should be able to remove a member (deprecated)" do + Puppet.expects(:deprecation_warning).with('Puppet::Util::Windows::ADSI::Group#remove_members is deprecated; please use Puppet::Util::Windows::ADSI::Group#remove_member_sids') Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('someone').returns(someone_sid) - Puppet::Util::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(someone_sid).returns("WinNT://testcomputername/someone,user") adsi_group.expects(:Remove).with("WinNT://testcomputername/someone,user") group.remove_member('someone') end - it "should raise when removing a member that can't resolve to a SID (deprecated)", :if => Puppet.features.microsoft_windows? do + it "should raise when removing a member that can't resolve to a SID (deprecated)" do expect { group.remove_member('foobar') }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) end - describe "should be able to use SID objects", :if => Puppet.features.microsoft_windows? do + describe "should be able to use SID objects" do let(:system) { Puppet::Util::Windows::Security.name_to_sid_object('SYSTEM') } it "to add a member" do adsi_group.expects(:Add).with("WinNT://S-1-5-18") group.add_member_sids(system) end it "to remove a member" do adsi_group.expects(:Remove).with("WinNT://S-1-5-18") group.remove_member_sids(system) end end it "should provide its groups as a list of names" do names = ['user1', 'user2'] users = names.map { |name| mock('user', :Name => name) } adsi_group.expects(:Members).returns(users) group.members.should =~ names end - it "should be able to add a list of users to a group", :if => Puppet.features.microsoft_windows? do + it "should be able to add a list of users to a group" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN'), stub(:account => 'user2', :domain => 'testcomputername'), stub(:account => 'user3', :domain => 'DOMAIN2'), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::Security.expects(:octet_string_to_sid_object).with([0]).returns(sids[0]) Puppet::Util::Windows::Security.expects(:octet_string_to_sid_object).with([1]).returns(sids[1]) Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('user2').returns(sids[1]) Puppet::Util::Windows::Security.expects(:name_to_sid_object).with('DOMAIN2\user3').returns(sids[2]) - Puppet::Util::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") - Puppet::Util::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") + Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i])} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).with('WinNT://DOMAIN/user1,user') adsi_group.expects(:Add).with('WinNT://DOMAIN2/user3,user') group.set_members(['user2', 'DOMAIN2\user3']) end - it "should raise an error when a username does not resolve to a SID", :if => Puppet.features.microsoft_windows? do + it "should raise an error when a username does not resolve to a SID" do expect { adsi_group.expects(:Members).returns [] group.set_members(['foobar']) }.to raise_error(Puppet::Error, /Could not resolve username: foobar/) end it "should generate the correct URI" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) group.uri.should == "WinNT://./#{groupname},group" end end it "should generate the correct URI" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI::Group.uri("people").should == "WinNT://./people,group" + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI::Group.uri("people").should == "WinNT://./people,group" end it "should be able to create a group" do adsi_group = stub("adsi") connection.expects(:Create).with('group', groupname).returns(adsi_group) - Puppet::Util::ADSI::User.expects(:exists?).with(groupname).returns(false) + Puppet::Util::Windows::ADSI::User.expects(:exists?).with(groupname).returns(false) - group = Puppet::Util::ADSI::Group.create(groupname) + group = Puppet::Util::Windows::ADSI::Group.create(groupname) - group.should be_a(Puppet::Util::ADSI::Group) + group.should be_a(Puppet::Util::Windows::ADSI::Group) group.native_group.should == adsi_group end it "should be able to confirm the existence of a group" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection - Puppet::Util::ADSI::Group.exists?(groupname).should be_true + Puppet::Util::Windows::ADSI::Group.exists?(groupname).should be_true end - it "should be able to confirm the existence of a group with a well-known SID", - :if => Puppet.features.microsoft_windows? do + it "should be able to confirm the existence of a group with a well-known SID" do service_group = Win32::Security::SID::Service # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::Group.exists?(service_group).should be_true + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::Group.exists?(service_group).should be_true end - it "should return nil with an unknown SID", - :if => Puppet.features.microsoft_windows? do + it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here - Puppet::Util::ADSI.unstub(:connect) - Puppet::Util::ADSI::Group.exists?(bogus_sid).should be_false + Puppet::Util::Windows::ADSI.unstub(:connect) + Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid).should be_false end it "should be able to delete a group" do connection.expects(:Delete).with('group', groupname) - Puppet::Util::ADSI::Group.delete(groupname) + Puppet::Util::Windows::ADSI::Group.delete(groupname) end it "should return an enumeration of IADsGroup wrapped objects" do - Puppet::Util::ADSI.stubs(:sid_uri_safe).returns(nil) + Puppet::Util::Windows::ADSI.stubs(:sid_uri_safe).returns(nil) name = 'Administrators' wmi_groups = [stub('WMI', :name => name)] - Puppet::Util::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) + Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) native_group = stub('IADsGroup') native_group.expects(:Members).returns([stub(:Name => 'Administrator')]) - Puppet::Util::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group) + Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_group) - groups = Puppet::Util::ADSI::Group.to_a + groups = Puppet::Util::Windows::ADSI::Group.to_a groups.length.should == 1 groups[0].name.should == name groups[0].members.should == ['Administrator'] end end - describe Puppet::Util::ADSI::UserProfile do + describe Puppet::Util::Windows::ADSI::UserProfile do it "should be able to delete a user profile" do connection.expects(:Delete).with("Win32_UserProfile.SID='S-A-B-C'") - Puppet::Util::ADSI::UserProfile.delete('S-A-B-C') + Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end it "should warn on 2003" do connection.expects(:Delete).raises(RuntimeError, "Delete (WIN32OLERuntimeError) OLE error code:80041010 in SWbemServicesEx Invalid class HRESULT error code:0x80020009 Exception occurred.") Puppet.expects(:warning).with("Cannot delete user profile for 'S-A-B-C' prior to Vista SP1") - Puppet::Util::ADSI::UserProfile.delete('S-A-B-C') + Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end end end