File: adsi_spec.rb

package info (click to toggle)
puppet 5.5.22-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 21,316 kB
  • sloc: ruby: 254,925; sh: 1,608; xml: 219; makefile: 153; sql: 103
file content (187 lines) | stat: -rw-r--r-- 8,283 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
require 'spec_helper'
require 'puppet/util/windows'

describe Puppet::Util::Windows::ADSI::User,
  :if => Puppet.features.microsoft_windows? do

  describe ".initialize" do
    it "cannot reference BUILTIN accounts like SYSTEM due to WinNT moniker limitations" do
      system = Puppet::Util::Windows::ADSI::User.new('SYSTEM')
      # trying to retrieve COM object should fail to load with a localized version of:
      # ADSI connection error: failed to parse display name of moniker `WinNT://./SYSTEM,user'
      #     HRESULT error code:0x800708ad
      #           The user name could not be found.
      # Matching on error code alone is sufficient
      expect { system.native_object }.to raise_error(/0x800708ad/)
    end
  end

  describe '.each' do
    it 'should return a list of users with UTF-8 names' do
      begin
        original_codepage = Encoding.default_external
        Encoding.default_external = Encoding::CP850 # Western Europe

        Puppet::Util::Windows::ADSI::User.each do |user|
          expect(user.name.encoding).to be(Encoding::UTF_8)
        end
      ensure
        Encoding.default_external = original_codepage
      end
    end
  end

  describe '.[]' do
    it 'should return string attributes as UTF-8' do
      administrator = Puppet::Util::Windows::ADSI::User.new('Administrator')
      expect(administrator['Description'].encoding).to eq(Encoding::UTF_8)
    end
  end

  describe '.groups' do
    it 'should return a list of groups with UTF-8 names' do
      begin
        original_codepage = Encoding.default_external
        Encoding.default_external = Encoding::CP850 # Western Europe


        # lookup by English name Administrator is OK on localized Windows
        administrator = Puppet::Util::Windows::ADSI::User.new('Administrator')
        administrator.groups.each do |name|
          expect(name.encoding).to be(Encoding::UTF_8)
        end
      ensure
        Encoding.default_external = original_codepage
      end
    end
  end
end

describe Puppet::Util::Windows::ADSI::Group,
  :if => Puppet.features.microsoft_windows? do

  let (:administrator_bytes) { [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] }
  let (:administrators_principal) { Puppet::Util::Windows::SID::Principal.lookup_account_sid(administrator_bytes) }

  describe '.each' do
    it 'should return a list of groups with UTF-8 names' do
      begin
        original_codepage = Encoding.default_external
        Encoding.default_external = Encoding::CP850 # Western Europe

        Puppet::Util::Windows::ADSI::Group.each do |group|
          expect(group.name.encoding).to be(Encoding::UTF_8)
        end
      ensure
        Encoding.default_external = original_codepage
      end
    end
  end

  describe '.members' do
    it 'should return a list of members resolvable with Puppet::Util::Windows::ADSI::Group.name_sid_hash' do
      temp_groupname = "g#{SecureRandom.uuid}"
      temp_username  = "u#{SecureRandom.uuid}"[0..12]
      # From https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/password-must-meet-complexity-requirements
      specials = "~!@#$%^&*_-+=`|\(){}[]:;\"'<>,.?/"
      temp_password = "p#{SecureRandom.uuid[0..7]}-#{SecureRandom.uuid.upcase[0..7]}-#{specials[rand(specials.length)]}"

      # select a virtual account that requires an authority to be able to resolve to SID
      # the Dhcp service is chosen for no particular reason aside from it's a service available on all Windows versions
      dhcp_virtualaccount = Puppet::Util::Windows::SID.name_to_principal('NT SERVICE\Dhcp')

      # adding :SidTypeGroup as a group member will cause error in IAdsUser::Add
      # adding :SidTypeDomain (such as S-1-5-80 / NT SERVICE or computer name) won't error
      #   but also won't be returned as a group member
      # uncertain how to obtain :SidTypeComputer (perhaps AD? the local machine is :SidTypeDomain)
      users = [
        # Use sid_to_name to get localized names of SIDs - BUILTIN, SYSTEM, NT AUTHORITY, Everyone are all localized
        # :SidTypeWellKnownGroup
        # SYSTEM is prefixed with the NT Authority authority, resolveable with or without authority
        { :sid => 'S-1-5-18', :name => Puppet::Util::Windows::SID.sid_to_name('S-1-5-18') },
        # Everyone is not prefixed with an authority, resolveable with or without NT AUTHORITY authority
        { :sid => 'S-1-1-0', :name => Puppet::Util::Windows::SID.sid_to_name('S-1-1-0') },
        # Dhcp service account is prefixed with NT SERVICE authority, requires authority to resolve SID
        # behavior is similar to IIS APPPOOL\DefaultAppPool
        { :sid => dhcp_virtualaccount.sid, :name => dhcp_virtualaccount.domain_account },

        # :SidTypeAlias with authority component
        # Administrators group is prefixed with BUILTIN authority, can be resolved with or without authority
        { :sid => 'S-1-5-32-544', :name => Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') },
      ]

      begin
        # :SidTypeUser as user on localhost, can be resolved with or without authority prefix
        user = Puppet::Util::Windows::ADSI::User.create(temp_username)
        # appveyor sometimes requires a password
        user.password = temp_password
        user.commit()
        users.push({ :sid => user.sid.sid, :name => Puppet::Util::Windows::ADSI.computer_name + '\\' + temp_username })

        # create a test group and add above 5 members by SID
        group = described_class.create(temp_groupname)
        group.commit()
        group.set_members(users.map { |u| u[:sid]} )

        # most importantly make sure that all name are convertible to SIDs
        expect { described_class.name_sid_hash(group.members) }.to_not raise_error

        # also verify the names returned are as expected
        expected_usernames = users.map { |u| u[:name] }
        expect(group.members.map(&:domain_account)).to eq(expected_usernames)
      ensure
        described_class.delete(temp_groupname) if described_class.exists?(temp_groupname)
        Puppet::Util::Windows::ADSI::User.delete(temp_username) if Puppet::Util::Windows::ADSI::User.exists?(temp_username)
      end
    end

    it 'should return a list of Principal objects even with unresolvable SIDs' do
      members = [
        # NULL SID is not localized
        double('WIN32OLE', {
          :objectSID => [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
          :Name => 'NULL SID',
          :ole_respond_to? => true,
        }),
        # unresolvable SID is a different story altogether
        double('WIN32OLE', {
          # completely valid SID, but Name is just a stringified version
          :objectSID => [1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 5, 113, 65, 218, 15, 127, 9, 57, 219, 4, 84, 126, 88, 4, 0, 0],
          :Name => 'S-1-5-21-3661721861-956923663-2119435483-1112',
          :ole_respond_to? => true,
        })
      ]

      admins_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544')
      admins = Puppet::Util::Windows::ADSI::Group.new(admins_name)

      # touch the native_object member to have it lazily loaded, so COM objects can be stubbed
      admins.native_object
      allow(admins.native_object).to receive(:Members).and_return(members)

      # well-known NULL SID
      expect(admins.members[0].sid).to eq('S-1-0-0')
      expect(admins.members[0].account_type).to eq(:SidTypeWellKnownGroup)

      # unresolvable SID
      expect(admins.members[1].sid).to eq('S-1-5-21-3661721861-956923663-2119435483-1112')
      expect(admins.members[1].account).to eq('S-1-5-21-3661721861-956923663-2119435483-1112')
      expect(admins.members[1].account_type).to eq(:SidTypeUnknown)
    end

    it 'should return a list of members with UTF-8 names' do
      begin
        original_codepage = Encoding.default_external
        Encoding.default_external = Encoding::CP850 # Western Europe

        # lookup by English name Administrators is not OK on localized Windows
        admins = Puppet::Util::Windows::ADSI::Group.new(administrators_principal.account)
        admins.members.map(&:domain_account).each do |name|
          expect(name.encoding).to be(Encoding::UTF_8)
        end
      ensure
        Encoding.default_external = original_codepage
      end
    end
  end
end