#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::SID::Principal, :if => Puppet::Util::Platform.windows? do let (:current_user_sid) { Puppet::Util::Windows::ADSI::User.current_user_sid } let (:system_bytes) { [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] } let (:null_sid_bytes) { [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] } let (:administrator_bytes) { [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] } let (:computer_sid) { Puppet::Util::Windows::SID.name_to_principal(Puppet::Util::Windows::ADSI.computer_name) } # BUILTIN is localized on German Windows, but not French # looking this up like this dilutes the values of the tests as we're comparing two mechanisms # for returning the same values, rather than to a known good let (:builtin_localized) { Puppet::Util::Windows::SID.sid_to_name('S-1-5-32') } describe ".lookup_account_name" do it "should create an instance from a well-known account name" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_name('NULL SID') expect(principal.account).to eq('NULL SID') expect(principal.sid_bytes).to eq(null_sid_bytes) expect(principal.sid).to eq('S-1-0-0') expect(principal.domain).to eq('') expect(principal.domain_account).to eq('NULL SID') expect(principal.account_type).to eq(:SidTypeWellKnownGroup) expect(principal.to_s).to eq('NULL SID') end it "should create an instance from a well-known account prefixed with NT AUTHORITY" do # a special case that can be used to lookup an account on a localized Windows principal = Puppet::Util::Windows::SID::Principal.lookup_account_name('NT AUTHORITY\\SYSTEM') expect(principal.sid_bytes).to eq(system_bytes) expect(principal.sid).to eq('S-1-5-18') # guard these 3 checks on a US Windows with 1033 - primary language id of 9 primary_language_id = 9 # even though lookup in English, returned values may be localized # French Windows returns AUTORITE NT\\Syst\u00E8me, German Windows returns NT-AUTORIT\u00C4T\\SYSTEM if (Puppet::Util::Windows::Process.get_system_default_ui_language & primary_language_id == primary_language_id) expect(principal.account).to eq('SYSTEM') expect(principal.domain).to eq('NT AUTHORITY') expect(principal.domain_account).to eq('NT AUTHORITY\\SYSTEM') expect(principal.to_s).to eq('NT AUTHORITY\\SYSTEM') end # Windows API LookupAccountSid behaves differently if current user is SYSTEM if current_user_sid.sid_bytes != system_bytes account_type = :SidTypeWellKnownGroup else account_type = :SidTypeUser end expect(principal.account_type).to eq(account_type) end it "should create an instance from a local account prefixed with hostname" do running_as_system = (current_user_sid.sid_bytes == system_bytes) username = running_as_system ? # need to return localized name of Administrator account Puppet::Util::Windows::SID.sid_to_name(computer_sid.sid + '-500').split('\\').last : current_user_sid.account user_exists = Puppet::Util::Windows::ADSI::User.exists?(".\\#{username}") # when running as SYSTEM (in Jenkins CI), then Administrator should be used # otherwise running in AppVeyor there is no Administrator and a the current local user can be used skip if (running_as_system && !user_exists) hostname = Puppet::Util::Windows::ADSI.computer_name principal = Puppet::Util::Windows::SID::Principal.lookup_account_name("#{hostname}\\#{username}") expect(principal.account).to match(/^#{Regexp.quote(username)}$/i) # skip SID and bytes in this case since the most interesting thing here is domain_account expect(principal.domain).to match(/^#{Regexp.quote(hostname)}$/i) expect(principal.domain_account).to match(/^#{Regexp.quote(hostname)}\\#{Regexp.quote(username)}$/i) expect(principal.account_type).to eq(:SidTypeUser) end it "should create an instance from a well-known BUILTIN alias" do # by looking up the localized name of the account, the test value is diluted # this localizes Administrators AND BUILTIN qualified_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') domain, name = qualified_name.split('\\') principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(name) expect(principal.account).to eq(name) expect(principal.sid_bytes).to eq(administrator_bytes) expect(principal.sid).to eq('S-1-5-32-544') expect(principal.domain).to eq(domain) expect(principal.domain_account).to eq(qualified_name) expect(principal.account_type).to eq(:SidTypeAlias) expect(principal.to_s).to eq(qualified_name) end it "should raise an error when trying to lookup an account that doesn't exist" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_name('ConanTheBarbarian') }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1332) # ERROR_NONE_MAPPED end end it "should return a BUILTIN domain principal for empty account names" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_name('') expect(principal.account_type).to eq(:SidTypeDomain) expect(principal.sid).to eq('S-1-5-32') expect(principal.account).to eq(builtin_localized) expect(principal.domain).to eq(builtin_localized) expect(principal.domain_account).to eq(builtin_localized) expect(principal.to_s).to eq(builtin_localized) end it "should return a BUILTIN domain principal for BUILTIN account names" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(builtin_localized) expect(principal.account_type).to eq(:SidTypeDomain) expect(principal.sid).to eq('S-1-5-32') expect(principal.account).to eq(builtin_localized) expect(principal.domain).to eq(builtin_localized) expect(principal.domain_account).to eq(builtin_localized) expect(principal.to_s).to eq(builtin_localized) end end describe ".lookup_account_sid" do it "should create an instance from a user SID" do # take the computer account bytes and append the equivalent of -501 for Guest bytes = (computer_sid.sid_bytes + [245, 1, 0, 0]) # computer SID bytes start with [1, 4, ...] but need to be [1, 5, ...] bytes[1] = 5 principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid(bytes) # use the returned SID to lookup localized Guest account name in Windows guest_name = Puppet::Util::Windows::SID.sid_to_name(principal.sid) expect(principal.sid_bytes).to eq(bytes) expect(principal.sid).to eq(computer_sid.sid + '-501') expect(principal.account).to eq(guest_name.split('\\')[1]) expect(principal.domain).to eq(computer_sid.domain) expect(principal.domain_account).to eq(guest_name) expect(principal.account_type).to eq(:SidTypeUser) expect(principal.to_s).to eq(guest_name) end it "should create an instance from a well-known group SID" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid(null_sid_bytes) expect(principal.sid_bytes).to eq(null_sid_bytes) expect(principal.sid).to eq('S-1-0-0') expect(principal.account).to eq('NULL SID') expect(principal.domain).to eq('') expect(principal.domain_account).to eq('NULL SID') expect(principal.account_type).to eq(:SidTypeWellKnownGroup) expect(principal.to_s).to eq('NULL SID') end it "should create an instance from a well-known BUILTIN Alias SID" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid(administrator_bytes) # by looking up the localized name of the account, the test value is diluted # this localizes Administrators AND BUILTIN qualified_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') domain, name = qualified_name.split('\\') expect(principal.account).to eq(name) expect(principal.sid_bytes).to eq(administrator_bytes) expect(principal.sid).to eq('S-1-5-32-544') expect(principal.domain).to eq(domain) expect(principal.domain_account).to eq(qualified_name) expect(principal.account_type).to eq(:SidTypeAlias) expect(principal.to_s).to eq(qualified_name) end it "should raise an error when trying to lookup nil" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid(nil) }.to raise_error(Puppet::Util::Windows::Error, /must not be nil/) end it "should raise an error when trying to lookup non-byte array" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid('ConanTheBarbarian') }.to raise_error(Puppet::Util::Windows::Error, /array/) end it "should raise an error when trying to lookup an empty array" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid([]) }.to raise_error(Puppet::Util::Windows::Error, /at least 1 byte long/) end # https://technet.microsoft.com/en-us/library/cc962011.aspx # "... The structure used in all SIDs created by Windows NT and Windows 2000 is revision level 1. ..." # Therefore a value of zero for the revision, is not a valid SID it "should raise an error when trying to lookup completely invalid SID bytes" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid([0]) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(87) # ERROR_INVALID_PARAMETER end end it "should raise an error when trying to lookup a valid SID that doesn't have a matching account" do principal = Puppet::Util::Windows::SID::Principal expect { # S-1-1-1 which is not a valid account principal.lookup_account_sid([1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0]) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1332) # ERROR_NONE_MAPPED end end it "should return a domain principal for BUILTIN SID S-1-5-32" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid([1, 1, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0]) expect(principal.account_type).to eq(:SidTypeDomain) expect(principal.sid).to eq('S-1-5-32') expect(principal.account).to eq(builtin_localized) expect(principal.domain).to eq(builtin_localized) expect(principal.domain_account).to eq(builtin_localized) expect(principal.to_s).to eq(builtin_localized) end end describe "it should create matching Principal objects" do let(:builtin_sid) { [1, 1, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0] } let(:sid_principal) { Puppet::Util::Windows::SID::Principal.lookup_account_sid(builtin_sid) } ['.', ''].each do |name| it "when comparing the one looked up via SID S-1-5-32 to one looked up via non-canonical name #{name} for the BUILTIN domain" do name_principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(name) # compares canonical sid expect(sid_principal).to eq(name_principal) # compare all properties that have public accessors sid_principal.public_methods(false).reject { |m| m == :== }.each do |method| expect(sid_principal.send(method)).to eq(name_principal.send(method)) end end end it "when comparing the one looked up via SID S-1-5-32 to one looked up via non-canonical localized name for the BUILTIN domain" do name_principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(builtin_localized) # compares canonical sid expect(sid_principal).to eq(name_principal) # compare all properties that have public accessors sid_principal.public_methods(false).reject { |m| m == :== }.each do |method| expect(sid_principal.send(method)).to eq(name_principal.send(method)) end end end end