puppet 5.5.6 → 5.5.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +12 -12
  4. data/Rakefile +9 -0
  5. data/lib/puppet/application.rb +5 -0
  6. data/lib/puppet/application/apply.rb +1 -0
  7. data/lib/puppet/application/master.rb +9 -7
  8. data/lib/puppet/application/script.rb +1 -1
  9. data/lib/puppet/defaults.rb +51 -31
  10. data/lib/puppet/etc.rb +20 -0
  11. data/lib/puppet/file_serving/fileset.rb +1 -1
  12. data/lib/puppet/functions.rb +123 -0
  13. data/lib/puppet/functions/new.rb +37 -53
  14. data/lib/puppet/functions/warning.rb +1 -1
  15. data/lib/puppet/loaders.rb +1 -0
  16. data/lib/puppet/parser/functions.rb +3 -1
  17. data/lib/puppet/parser/functions/sprintf.rb +12 -1
  18. data/lib/puppet/pops/evaluator/runtime3_converter.rb +16 -0
  19. data/lib/puppet/pops/evaluator/runtime3_support.rb +3 -4
  20. data/lib/puppet/pops/issues.rb +8 -0
  21. data/lib/puppet/pops/loader/loader.rb +2 -2
  22. data/lib/puppet/pops/loader/loader_paths.rb +3 -1
  23. data/lib/puppet/pops/loader/module_loaders.rb +1 -1
  24. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +62 -0
  25. data/lib/puppet/pops/loaders.rb +5 -21
  26. data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
  27. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  28. data/lib/puppet/pops/validation/checker4_0.rb +31 -6
  29. data/lib/puppet/pops/validation/validator_factory_4_0.rb +1 -0
  30. data/lib/puppet/property/keyvalue.rb +70 -8
  31. data/lib/puppet/provider/aix_object.rb +483 -0
  32. data/lib/puppet/provider/exec.rb +54 -57
  33. data/lib/puppet/provider/group/aix.rb +40 -115
  34. data/lib/puppet/provider/group/pw.rb +4 -8
  35. data/lib/puppet/provider/group/windows_adsi.rb +7 -4
  36. data/lib/puppet/provider/nameservice.rb +1 -25
  37. data/lib/puppet/provider/nameservice/directoryservice.rb +5 -3
  38. data/lib/puppet/provider/package/portage.rb +2 -2
  39. data/lib/puppet/provider/package/windows.rb +2 -2
  40. data/lib/puppet/provider/package/windows/exe_package.rb +3 -10
  41. data/lib/puppet/provider/package/zypper.rb +1 -1
  42. data/lib/puppet/provider/service/launchd.rb +19 -3
  43. data/lib/puppet/provider/service/windows.rb +49 -40
  44. data/lib/puppet/provider/user/aix.rb +180 -246
  45. data/lib/puppet/provider/user/windows_adsi.rb +9 -1
  46. data/lib/puppet/resource/catalog.rb +1 -5
  47. data/lib/puppet/type/augeas.rb +1 -1
  48. data/lib/puppet/type/exec.rb +16 -14
  49. data/lib/puppet/type/file.rb +2 -2
  50. data/lib/puppet/type/file/source.rb +9 -5
  51. data/lib/puppet/type/group.rb +65 -23
  52. data/lib/puppet/type/k5login.rb +2 -2
  53. data/lib/puppet/type/notify.rb +1 -1
  54. data/lib/puppet/type/package.rb +3 -6
  55. data/lib/puppet/type/resources.rb +12 -2
  56. data/lib/puppet/type/schedule.rb +8 -1
  57. data/lib/puppet/type/selboolean.rb +2 -2
  58. data/lib/puppet/type/selmodule.rb +3 -4
  59. data/lib/puppet/type/service.rb +2 -5
  60. data/lib/puppet/type/tidy.rb +1 -1
  61. data/lib/puppet/type/user.rb +15 -20
  62. data/lib/puppet/type/yumrepo.rb +2 -2
  63. data/lib/puppet/type/zone.rb +2 -2
  64. data/lib/puppet/util.rb +7 -3
  65. data/lib/puppet/util/execution.rb +15 -1
  66. data/lib/puppet/util/posix.rb +15 -0
  67. data/lib/puppet/util/storage.rb +12 -0
  68. data/lib/puppet/util/windows.rb +4 -2
  69. data/lib/puppet/util/windows/adsi.rb +235 -205
  70. data/lib/puppet/util/windows/process.rb +23 -3
  71. data/lib/puppet/util/windows/security.rb +14 -0
  72. data/lib/puppet/util/windows/service.rb +977 -0
  73. data/lib/puppet/util/windows/user.rb +3 -5
  74. data/lib/puppet/version.rb +1 -1
  75. data/locales/ja/puppet.po +705 -374
  76. data/locales/puppet.pot +485 -261
  77. data/man/man5/puppet.conf.5 +36 -15
  78. data/man/man8/puppet-agent.8 +1 -1
  79. data/man/man8/puppet-apply.8 +1 -1
  80. data/man/man8/puppet-ca.8 +1 -1
  81. data/man/man8/puppet-catalog.8 +1 -1
  82. data/man/man8/puppet-cert.8 +1 -1
  83. data/man/man8/puppet-certificate.8 +1 -1
  84. data/man/man8/puppet-certificate_request.8 +1 -1
  85. data/man/man8/puppet-certificate_revocation_list.8 +1 -1
  86. data/man/man8/puppet-config.8 +1 -1
  87. data/man/man8/puppet-describe.8 +1 -1
  88. data/man/man8/puppet-device.8 +1 -1
  89. data/man/man8/puppet-doc.8 +1 -1
  90. data/man/man8/puppet-epp.8 +1 -1
  91. data/man/man8/puppet-facts.8 +1 -1
  92. data/man/man8/puppet-filebucket.8 +1 -1
  93. data/man/man8/puppet-generate.8 +1 -1
  94. data/man/man8/puppet-help.8 +1 -1
  95. data/man/man8/puppet-key.8 +1 -1
  96. data/man/man8/puppet-lookup.8 +1 -1
  97. data/man/man8/puppet-man.8 +1 -1
  98. data/man/man8/puppet-master.8 +1 -1
  99. data/man/man8/puppet-module.8 +1 -1
  100. data/man/man8/puppet-node.8 +1 -1
  101. data/man/man8/puppet-parser.8 +1 -1
  102. data/man/man8/puppet-plugin.8 +1 -1
  103. data/man/man8/puppet-report.8 +1 -1
  104. data/man/man8/puppet-resource.8 +1 -1
  105. data/man/man8/puppet-script.8 +1 -1
  106. data/man/man8/puppet-status.8 +1 -1
  107. data/man/man8/puppet.8 +2 -2
  108. data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_input.out +1 -0
  109. data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_output.out +1 -0
  110. data/spec/fixtures/unit/provider/user/aix/aix_passwd_file.out +32 -0
  111. data/spec/integration/parser/collection_spec.rb +4 -8
  112. data/spec/integration/provider/service/windows_spec.rb +5 -5
  113. data/spec/integration/type/file_spec.rb +6 -6
  114. data/spec/integration/util/windows/adsi_spec.rb +6 -5
  115. data/spec/integration/util/windows/security_spec.rb +10 -7
  116. data/spec/integration/util/windows/user_spec.rb +37 -17
  117. data/spec/spec_helper.rb +0 -1
  118. data/spec/unit/application/apply_spec.rb +41 -2
  119. data/spec/unit/application/master_spec.rb +7 -0
  120. data/spec/unit/application_spec.rb +21 -3
  121. data/spec/unit/defaults_spec.rb +20 -0
  122. data/spec/unit/etc_spec.rb +25 -0
  123. data/spec/unit/file_serving/fileset_spec.rb +11 -11
  124. data/spec/unit/gettext/config_spec.rb +1 -1
  125. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +6 -6
  126. data/spec/unit/pops/loaders/loaders_spec.rb +40 -7
  127. data/spec/unit/pops/parser/parse_heredoc_spec.rb +16 -0
  128. data/spec/unit/pops/validator/validator_spec.rb +129 -10
  129. data/spec/unit/property/keyvalue_spec.rb +97 -6
  130. data/spec/unit/provider/aix_object_spec.rb +805 -0
  131. data/spec/unit/provider/group/aix_spec.rb +57 -0
  132. data/spec/unit/provider/group/pw_spec.rb +0 -6
  133. data/spec/unit/provider/group/windows_adsi_spec.rb +34 -35
  134. data/spec/unit/provider/nameservice/directoryservice_spec.rb +2 -2
  135. data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
  136. data/spec/unit/provider/package/windows_spec.rb +4 -4
  137. data/spec/unit/provider/service/launchd_spec.rb +19 -0
  138. data/spec/unit/provider/service/windows_spec.rb +71 -78
  139. data/spec/unit/provider/user/aix_spec.rb +162 -116
  140. data/spec/unit/provider/user/windows_adsi_spec.rb +4 -4
  141. data/spec/unit/resource/catalog_spec.rb +2 -2
  142. data/spec/unit/ssl/certificate_authority_spec.rb +0 -1
  143. data/spec/unit/type/group_spec.rb +111 -13
  144. data/spec/unit/type/resources_spec.rb +18 -0
  145. data/spec/unit/util/execution_spec.rb +77 -0
  146. data/spec/unit/util/posix_spec.rb +28 -0
  147. data/spec/unit/util/storage_spec.rb +107 -0
  148. data/spec/unit/util/windows/adsi_spec.rb +108 -13
  149. data/spec/unit/util/windows/service_spec.rb +669 -0
  150. metadata +17 -5
  151. data/lib/puppet/provider/aixobject.rb +0 -392
  152. data/spec/unit/provider/aixobject_spec.rb +0 -101
@@ -392,9 +392,7 @@ module Puppet
392
392
  that the user is a part of.
393
393
 
394
394
  If `inclusive` is specified, Puppet will ensure that the user is a
395
- member of **only** specified groups.
396
-
397
- Defaults to `minimum`."
395
+ member of **only** specified groups."
398
396
 
399
397
  newvalues(:inclusive, :minimum)
400
398
 
@@ -405,13 +403,13 @@ module Puppet
405
403
  desc "Whether the user is a system user, according to the OS's criteria;
406
404
  on most platforms, a UID less than or equal to 500 indicates a system
407
405
  user. This parameter is only used when the resource is created and will
408
- not affect the UID when the user is present. Defaults to `false`."
406
+ not affect the UID when the user is present."
409
407
 
410
408
  defaultto false
411
409
  end
412
410
 
413
411
  newparam(:allowdupe, :boolean => true, :parent => Puppet::Parameter::Boolean) do
414
- desc "Whether to allow duplicate UIDs. Defaults to `false`."
412
+ desc "Whether to allow duplicate UIDs."
415
413
 
416
414
  defaultto false
417
415
  end
@@ -419,13 +417,16 @@ module Puppet
419
417
  newparam(:managehome, :boolean => true, :parent => Puppet::Parameter::Boolean) do
420
418
  desc "Whether to manage the home directory when Puppet creates or removes the user.
421
419
  This creates the home directory if Puppet also creates the user account, and deletes the
422
- home directory if Puppet also removes the user account. Defaults to `false`.
420
+ home directory if Puppet also removes the user account.
423
421
 
424
422
  This parameter has no effect unless Puppet is also creating or removing the user in the
425
423
  resource at the same time. For instance, Puppet creates a home directory for a managed
426
424
  user if `ensure => present` and the user does not exist at the time of the Puppet run.
427
425
  If the home directory is then deleted manually, Puppet will not recreate it on the next
428
- run."
426
+ run.
427
+
428
+ Note that on Windows, this manages creation/deletion of the user profile instead of the
429
+ home directory. The user profile is stored in the `C:\Users\<username>` directory."
429
430
 
430
431
  defaultto false
431
432
 
@@ -545,7 +546,7 @@ module Puppet
545
546
  newparam(:role_membership) do
546
547
  desc "Whether specified roles should be considered the **complete list**
547
548
  (`inclusive`) or the **minimum list** (`minimum`) of roles the user
548
- has. Defaults to `minimum`."
549
+ has."
549
550
 
550
551
  newvalues(:inclusive, :minimum)
551
552
 
@@ -571,7 +572,7 @@ module Puppet
571
572
  newparam(:auth_membership) do
572
573
  desc "Whether specified auths should be considered the **complete list**
573
574
  (`inclusive`) or the **minimum list** (`minimum`) of auths the user
574
- has. Defaults to `minimum`."
575
+ has."
575
576
 
576
577
  newvalues(:inclusive, :minimum)
577
578
 
@@ -597,7 +598,7 @@ module Puppet
597
598
  newparam(:profile_membership) do
598
599
  desc "Whether specified roles should be treated as the **complete list**
599
600
  (`inclusive`) or the **minimum list** (`minimum`) of roles
600
- of which the user is a member. Defaults to `minimum`."
601
+ of which the user is a member."
601
602
 
602
603
  newvalues(:inclusive, :minimum)
603
604
 
@@ -610,16 +611,12 @@ module Puppet
610
611
  def membership
611
612
  :key_membership
612
613
  end
613
-
614
- validate do |value|
615
- raise ArgumentError, _("Key/value pairs must be separated by an =") unless value.include?("=")
616
- end
617
614
  end
618
615
 
619
616
  newparam(:key_membership) do
620
617
  desc "Whether specified key/value pairs should be considered the
621
618
  **complete list** (`inclusive`) or the **minimum list** (`minimum`) of
622
- the user's attributes. Defaults to `minimum`."
619
+ the user's attributes."
623
620
 
624
621
  newvalues(:inclusive, :minimum)
625
622
 
@@ -637,6 +634,8 @@ module Puppet
637
634
  newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do
638
635
  desc "Specify AIX attributes for the user in an array of attribute = value pairs."
639
636
 
637
+ self.log_only_changed_or_new_keys = true
638
+
640
639
  def membership
641
640
  :attribute_membership
642
641
  end
@@ -644,16 +643,12 @@ module Puppet
644
643
  def delimiter
645
644
  " "
646
645
  end
647
-
648
- validate do |value|
649
- raise ArgumentError, _("Attributes value pairs must be separated by an =") unless value.include?("=")
650
- end
651
646
  end
652
647
 
653
648
  newparam(:attribute_membership) do
654
649
  desc "Whether specified attribute value pairs should be treated as the
655
650
  **complete list** (`inclusive`) or the **minimum list** (`minimum`) of
656
- attribute/value pairs for the user. Defaults to `minimum`."
651
+ attribute/value pairs for the user."
657
652
 
658
653
  newvalues(:inclusive, :minimum)
659
654
 
@@ -273,7 +273,7 @@ Puppet::Type.newtype(:yumrepo) do
273
273
  desc "Enable bandwidth throttling for downloads. This option
274
274
  can be expressed as a absolute data rate in bytes/sec or a
275
275
  percentage `60%`. An SI prefix (k, M or G) may be appended
276
- to the data rate values.\n#{ABSENT_DOC}"
276
+ to the data rate values.#{ABSENT_DOC}"
277
277
 
278
278
  newvalues(/^\d+[kMG%]?$/, :absent)
279
279
  end
@@ -283,7 +283,7 @@ Puppet::Type.newtype(:yumrepo) do
283
283
  in bytes/second. Used with the `throttle` option. If `throttle`
284
284
  is a percentage and `bandwidth` is `0` then bandwidth throttling
285
285
  will be disabled. If `throttle` is expressed as a data rate then
286
- this option is ignored.\n#{ABSENT_DOC}"
286
+ this option is ignored.#{ABSENT_DOC}"
287
287
 
288
288
  newvalues(/^\d+[kMG]?$/, :absent)
289
289
  end
@@ -262,7 +262,7 @@ end
262
262
  # Specify the sysidcfg file. This is pretty hackish, because it's
263
263
  # only used to boot the zone the very first time.
264
264
  newparam(:sysidcfg) do
265
- desc %{The text to go into the `sysidcfg` file when the zone is first
265
+ desc "The text to go into the `sysidcfg` file when the zone is first
266
266
  booted. The best way is to use a template:
267
267
 
268
268
  # $confdir/modules/site/templates/sysidcfg.erb
@@ -290,7 +290,7 @@ end
290
290
  }
291
291
 
292
292
  The `sysidcfg` only matters on the first booting of the zone,
293
- so Puppet only checks for it at that time.}
293
+ so Puppet only checks for it at that time."
294
294
  end
295
295
 
296
296
  newparam(:create_args) do
@@ -479,9 +479,13 @@ module Util
479
479
 
480
480
  def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block)
481
481
  child_pid = Kernel.fork do
482
- $stdin.reopen(stdin)
483
- $stdout.reopen(stdout)
484
- $stderr.reopen(stderr)
482
+ STDIN.reopen(stdin)
483
+ STDOUT.reopen(stdout)
484
+ STDERR.reopen(stderr)
485
+
486
+ $stdin = STDIN
487
+ $stdout = STDOUT
488
+ $stderr = STDERR
485
489
 
486
490
  begin
487
491
  Dir.foreach('/proc/self/fd') do |f|
@@ -114,6 +114,9 @@ module Puppet::Util::Execution
114
114
  # an Array the first element should be the executable and the rest of the
115
115
  # elements should be the individual arguments to that executable.
116
116
  # @param options [Hash] a Hash of options
117
+ # @option options [String] :cwd the directory from which to run the command. Raises an error if the directory does not exist.
118
+ # This option is only available on the agent. It cannot be used on the master, meaning it cannot be used in, for example,
119
+ # regular functions, hiera backends, or report processors.
117
120
  # @option options [Boolean] :failonfail if this value is set to true, then this method will raise an error if the
118
121
  # command is not executed successfully.
119
122
  # @option options [Integer, String] :uid (nil) the user id of the user that the process should be run as. Will be ignored if the
@@ -154,6 +157,7 @@ module Puppet::Util::Execution
154
157
  :override_locale => true,
155
158
  :custom_environment => {},
156
159
  :sensitive => false,
160
+ :suppress_window => false,
157
161
  }
158
162
 
159
163
  options = default_options.merge(options)
@@ -186,6 +190,11 @@ module Puppet::Util::Execution
186
190
 
187
191
  null_file = Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null'
188
192
 
193
+ cwd = options[:cwd]
194
+ if cwd && ! Puppet::FileSystem.directory?(cwd)
195
+ raise ArgumentError, _("Working directory %{cwd} does not exist!") % { cwd: cwd }
196
+ end
197
+
189
198
  begin
190
199
  stdin = Puppet::FileSystem.open(options[:stdinfile] || null_file, nil, 'r')
191
200
  # On Windows, continue to use the file-based approach to avoid breaking people's existing
@@ -320,7 +329,6 @@ module Puppet::Util::Execution
320
329
  #
321
330
  def self.execute_posix(command, options, stdin, stdout, stderr)
322
331
  child_pid = Puppet::Util.safe_posix_fork(stdin, stdout, stderr) do
323
-
324
332
  # We can't just call Array(command), and rely on it returning
325
333
  # things like ['foo'], when passed ['foo'], because
326
334
  # Array(command) will call command.to_a internally, which when
@@ -329,6 +337,12 @@ module Puppet::Util::Execution
329
337
  command = [command].flatten
330
338
  Process.setsid
331
339
  begin
340
+ # We need to chdir to our cwd before changing privileges as there's a
341
+ # chance that the user may not have permissions to access the cwd, which
342
+ # would cause execute_posix to fail.
343
+ cwd = options[:cwd]
344
+ Dir.chdir(cwd) if cwd
345
+
332
346
  Puppet::Util::SUIDManager.change_privileges(options[:uid], options[:gid], true)
333
347
 
334
348
  # if the caller has requested that we override locale environment variables,
@@ -9,7 +9,22 @@ module Puppet::Util::POSIX
9
9
  # environment for "exec" runs
10
10
  USER_ENV_VARS = ['HOME', 'USER', 'LOGNAME']
11
11
 
12
+ class << self
13
+ # Returns an array of all the groups that the user's a member of.
14
+ def groups_of(user)
15
+ groups = []
16
+ Puppet::Etc.group do |group|
17
+ groups << group.name if group.mem.include?(user)
18
+ end
19
+
20
+ uniq_groups = groups.uniq
21
+ if uniq_groups != groups
22
+ Puppet.debug(_('Removing any duplicate group entries'))
23
+ end
12
24
 
25
+ uniq_groups
26
+ end
27
+ end
13
28
 
14
29
  # Retrieve a field from a POSIX Etc object. The id can be either an integer
15
30
  # or a name. This only works for users and groups. It's also broken on
@@ -82,6 +82,18 @@ class Puppet::Util::Storage
82
82
 
83
83
  Puppet.info _("Creating state file %{file}") % { file: Puppet[:statefile] } unless Puppet::FileSystem.exist?(Puppet[:statefile])
84
84
 
85
+ if Puppet[:statettl] == 0 || Puppet[:statettl] == Float::INFINITY
86
+ Puppet.debug "Not pruning old state cache entries"
87
+ else
88
+ Puppet::Util.benchmark(:debug, "Pruned old state cache entries in %{seconds} seconds") do
89
+ ttl_cutoff = Time.now - Puppet[:statettl]
90
+
91
+ @@state.reject! do |k,v|
92
+ @@state[k][:checked] && @@state[k][:checked] < ttl_cutoff
93
+ end
94
+ end
95
+ end
96
+
85
97
  Puppet::Util.benchmark(:debug, "Stored state in %{seconds} seconds") do
86
98
  Puppet::Util::Yaml.dump(@@state, Puppet[:statefile])
87
99
  end
@@ -1,8 +1,9 @@
1
1
  module Puppet::Util::Windows
2
2
  module ADSI
3
- class User; end
3
+ class ADSIObject; end
4
+ class User < ADSIObject; end
4
5
  class UserProfile; end
5
- class Group; end
6
+ class Group < ADSIObject; end
6
7
  end
7
8
  module File; end
8
9
  module Registry
@@ -31,5 +32,6 @@ module Puppet::Util::Windows
31
32
  require 'puppet/util/windows/adsi'
32
33
  require 'puppet/util/windows/registry'
33
34
  require 'puppet/util/windows/eventlog'
35
+ require 'puppet/util/windows/service'
34
36
  end
35
37
  end
@@ -106,138 +106,201 @@ module Puppet::Util::Windows::ADSI
106
106
  [:lpwstr, :lpdword], :win32_bool
107
107
  end
108
108
 
109
- module Shared
110
- def localized_domains
111
- @localized_domains ||= [
112
- # localized version of BUILTIN
113
- # for instance VORDEFINIERT on German Windows
114
- Puppet::Util::Windows::SID.sid_to_name('S-1-5-32').upcase,
115
- # localized version of NT AUTHORITY (can't use S-1-5)
116
- # for instance AUTORITE NT on French Windows
117
- Puppet::Util::Windows::SID.name_to_principal('SYSTEM').domain.upcase
118
- ]
119
- end
120
-
121
- def uri(name, host = '.')
122
- host = '.' if (localized_domains << Socket.gethostname.upcase).include?(host.upcase)
109
+ # Common base class shared by the User and Group
110
+ # classes below.
111
+ class ADSIObject
112
+ extend Enumerable
123
113
 
124
- # group or user
125
- account_type = self.name.split('::').last.downcase
114
+ # Define some useful class-level methods
115
+ class << self
116
+ # Is either 'user' or 'group'
117
+ attr_reader :object_class
118
+
119
+ def localized_domains
120
+ @localized_domains ||= [
121
+ # localized version of BUILTIN
122
+ # for instance VORDEFINIERT on German Windows
123
+ Puppet::Util::Windows::SID.sid_to_name('S-1-5-32').upcase,
124
+ # localized version of NT AUTHORITY (can't use S-1-5)
125
+ # for instance AUTORITE NT on French Windows
126
+ Puppet::Util::Windows::SID.name_to_principal('SYSTEM').domain.upcase
127
+ ]
128
+ end
129
+
130
+ def uri(name, host = '.')
131
+ host = '.' if (localized_domains << Socket.gethostname.upcase).include?(host.upcase)
132
+ Puppet::Util::Windows::ADSI.uri(name, @object_class, host)
133
+ end
134
+
135
+ def parse_name(name)
136
+ if name =~ /\//
137
+ raise Puppet::Error.new( _("Value must be in DOMAIN\\%{object_class} style syntax") % { object_class: @object_class } )
138
+ end
139
+
140
+ matches = name.scan(/((.*)\\)?(.*)/)
141
+ domain = matches[0][1] || '.'
142
+ account = matches[0][2]
143
+
144
+ return account, domain
145
+ end
146
+
147
+ # returns Puppet::Util::Windows::SID::Principal[]
148
+ # may contain objects that represent unresolvable SIDs
149
+ def get_sids(adsi_child_collection)
150
+ sids = []
151
+ adsi_child_collection.each do |m|
152
+ sids << Puppet::Util::Windows::SID.ads_to_principal(m)
153
+ end
154
+
155
+ sids
156
+ end
157
+
158
+ def name_sid_hash(names)
159
+ return {} if names.nil? || names.empty?
160
+
161
+ sids = names.map do |name|
162
+ sid = Puppet::Util::Windows::SID.name_to_principal(name)
163
+ raise Puppet::Error.new( _("Could not resolve name: %{name}") % { name: name } ) if !sid
164
+ [sid.sid, sid]
165
+ end
166
+
167
+ Hash[ sids ]
168
+ end
126
169
 
127
- Puppet::Util::Windows::ADSI.uri(name, account_type, host)
128
- end
129
170
 
130
- def parse_name(name)
131
- if name =~ /\//
132
- raise Puppet::Error.new( _("Value must be in DOMAIN\\user style syntax") )
171
+ def delete(name)
172
+ Puppet::Util::Windows::ADSI.delete(name, @object_class)
133
173
  end
134
174
 
135
- matches = name.scan(/((.*)\\)?(.*)/)
136
- domain = matches[0][1] || '.'
137
- account = matches[0][2]
138
-
139
- return account, domain
140
- end
175
+ def exists?(name_or_sid)
176
+ well_known = false
177
+ if (sid = Puppet::Util::Windows::SID.name_to_principal(name_or_sid))
178
+ # Examples of SidType include SidTypeUser, SidTypeGroup
179
+ return true if sid.account_type == "SidType#{@object_class.capitalize}".to_sym
180
+
181
+ # 'well known group' is special as it can be a group like Everyone OR a user like SYSTEM
182
+ # so try to resolve it
183
+ # https://msdn.microsoft.com/en-us/library/cc234477.aspx
184
+ well_known = sid.account_type == :SidTypeWellKnownGroup
185
+ return false if sid.account_type != :SidTypeAlias && !well_known
186
+ name_or_sid = "#{sid.domain}\\#{sid.account}"
187
+ end
141
188
 
142
- # returns Puppet::Util::Windows::SID::Principal[]
143
- # may contain objects that represent unresolvable SIDs
144
- def get_sids(adsi_child_collection)
145
- sids = []
146
- adsi_child_collection.each do |m|
147
- sids << Puppet::Util::Windows::SID.ads_to_principal(m)
189
+ object = Puppet::Util::Windows::ADSI.connect(uri(*parse_name(name_or_sid)))
190
+ object.Class.downcase == @object_class
191
+ rescue
192
+ # special accounts like SYSTEM or special groups like Authenticated Users cannot
193
+ # resolve via monikers like WinNT://./SYSTEM,user or WinNT://./Authenticated Users,group
194
+ # -- they'll fail to connect. thus, given a validly resolved SID, this failure is
195
+ # ambiguous as it may indicate either a group like Service or an account like SYSTEM
196
+ well_known
148
197
  end
149
198
 
150
- sids
151
- end
199
+ def list_all
200
+ raise NotImplementedError, _("Subclass must implement class-level method 'list_all'!")
201
+ end
152
202
 
153
- def name_sid_hash(names)
154
- return {} if names.nil? || names.empty?
203
+ def each(&block)
204
+ objects = []
205
+ list_all.each do |o|
206
+ # Setting WIN32OLE.codepage in the microsoft_windows feature ensures
207
+ # values are returned as UTF-8
208
+ objects << new(o.name)
209
+ end
155
210
 
156
- sids = names.map do |name|
157
- sid = Puppet::Util::Windows::SID.name_to_principal(name)
158
- raise Puppet::Error.new( _("Could not resolve name: %{name}") % { name: name } ) if !sid
159
- [sid.sid, sid]
211
+ objects.each(&block)
160
212
  end
161
-
162
- Hash[ sids ]
163
213
  end
164
- end
165
214
 
166
- class User
167
- extend Enumerable
168
- extend Puppet::Util::Windows::ADSI::Shared
169
- extend FFI::Library
170
-
171
- # https://msdn.microsoft.com/en-us/library/aa746340.aspx
172
- # IADsUser interface
173
-
174
- require 'puppet/util/windows/sid'
175
-
176
- attr_accessor :native_user
177
- attr_reader :name, :sid
178
- def initialize(name, native_user = nil)
215
+ attr_reader :name
216
+ def initialize(name, native_object = nil)
179
217
  @name = name
180
- @native_user = native_user
181
- end
182
-
183
- def native_user
184
- @native_user ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(@name)))
218
+ @native_object = native_object
185
219
  end
186
220
 
187
- def sid
188
- @sid ||= Puppet::Util::Windows::SID.octet_string_to_principal(native_user.objectSID)
221
+ def object_class
222
+ self.class.object_class
189
223
  end
190
224
 
191
225
  def uri
192
226
  self.class.uri(sid.account, sid.domain)
193
227
  end
194
228
 
195
- def self.logon(name, password)
196
- Puppet::Util::Windows::User.password_is?(name, password)
229
+ def native_object
230
+ @native_object ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(name)))
231
+ end
232
+
233
+ def sid
234
+ @sid ||= Puppet::Util::Windows::SID.octet_string_to_principal(native_object.objectSID)
197
235
  end
198
236
 
199
237
  def [](attribute)
200
238
  # Setting WIN32OLE.codepage in the microsoft_windows feature ensures
201
239
  # values are returned as UTF-8
202
- native_user.Get(attribute)
240
+ native_object.Get(attribute)
203
241
  end
204
242
 
205
243
  def []=(attribute, value)
206
- native_user.Put(attribute, value)
244
+ native_object.Put(attribute, value)
207
245
  end
208
246
 
209
247
  def commit
210
248
  begin
211
- native_user.SetInfo unless native_user.nil?
249
+ native_object.SetInfo
212
250
  rescue WIN32OLERuntimeError => e
213
251
  # ERROR_BAD_USERNAME 2202L from winerror.h
214
252
  if e.message =~ /8007089A/m
215
253
  raise Puppet::Error.new(
216
- _("Puppet is not able to create/delete domain users with the user resource."),
217
- e
254
+ _("Puppet is not able to create/delete domain %{object_class} objects with the %{object_class} resource.") % { object_class: object_class },
218
255
  )
219
256
  end
220
257
 
221
- raise Puppet::Error.new( _("User update failed: %{e}") % { e: e }, e )
258
+ raise Puppet::Error.new( _("%{object_class} update failed: %{error}") % { object_class: object_class.capitalize, error: e }, e )
222
259
  end
223
260
  self
224
261
  end
262
+ end
263
+
264
+ class User < ADSIObject
265
+ extend FFI::Library
266
+
267
+ require 'puppet/util/windows/sid'
268
+
269
+ # https://msdn.microsoft.com/en-us/library/aa746340.aspx
270
+ # IADsUser interface
271
+ @object_class = 'user'
272
+
273
+ class << self
274
+ def list_all
275
+ Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"')
276
+ end
277
+
278
+ def logon(name, password)
279
+ Puppet::Util::Windows::User.password_is?(name, password)
280
+ end
281
+
282
+ def create(name)
283
+ # Windows error 1379: The specified local group already exists.
284
+ raise Puppet::Error.new(_("Cannot create user if group '%{name}' exists.") % { name: name }) if Puppet::Util::Windows::ADSI::Group.exists? name
285
+ new(name, Puppet::Util::Windows::ADSI.create(name, @object_class))
286
+ end
287
+ end
225
288
 
226
289
  def password_is?(password)
227
290
  self.class.logon(name, password)
228
291
  end
229
292
 
230
293
  def add_flag(flag_name, value)
231
- flag = native_user.Get(flag_name) rescue 0
294
+ flag = native_object.Get(flag_name) rescue 0
232
295
 
233
- native_user.Put(flag_name, flag | value)
296
+ native_object.Put(flag_name, flag | value)
234
297
 
235
298
  commit
236
299
  end
237
300
 
238
301
  def password=(password)
239
302
  if !password.nil?
240
- native_user.SetPassword(password)
303
+ native_object.SetPassword(password)
241
304
  commit
242
305
  end
243
306
 
@@ -251,7 +314,7 @@ module Puppet::Util::Windows::ADSI
251
314
  groups = []
252
315
  # Setting WIN32OLE.codepage in the microsoft_windows feature ensures
253
316
  # values are returned as UTF-8
254
- native_user.Groups.each {|g| groups << g.Name} rescue nil
317
+ native_object.Groups.each {|g| groups << g.Name} rescue nil
255
318
  groups
256
319
  end
257
320
 
@@ -281,9 +344,13 @@ module Puppet::Util::Windows::ADSI
281
344
  end
282
345
 
283
346
  def group_sids
284
- self.class.get_sids(native_user.Groups)
347
+ self.class.get_sids(native_object.Groups)
285
348
  end
286
349
 
350
+ # TODO: This code's pretty similar to set_members in the Group class. Would be nice
351
+ # to refactor them into the ADSIObject class at some point. This was not done originally
352
+ # because these use different methods to do stuff that are also aliased to other methods,
353
+ # so the shared code isn't exactly a 1:1 mapping.
287
354
  def set_groups(desired_groups, minimum = true)
288
355
  return if desired_groups.nil?
289
356
 
@@ -311,10 +378,82 @@ module Puppet::Util::Windows::ADSI
311
378
  end
312
379
  end
313
380
 
314
- def self.create(name)
315
- # Windows error 1379: The specified local group already exists.
316
- raise Puppet::Error.new(_("Cannot create user if group '%{name}' exists.") % { name: name }) if Puppet::Util::Windows::ADSI::Group.exists? name
317
- new(name, Puppet::Util::Windows::ADSI.create(name, 'user'))
381
+ # Declare all of the available user flags on the system. Note that
382
+ # ADS_UF is read as ADS_UserFlag
383
+ # https://docs.microsoft.com/en-us/windows/desktop/api/iads/ne-iads-ads_user_flag
384
+ # and
385
+ # https://support.microsoft.com/en-us/help/305144/how-to-use-the-useraccountcontrol-flags-to-manipulate-user-account-pro
386
+ # for the flag values.
387
+ ADS_USERFLAGS = {
388
+ ADS_UF_SCRIPT: 0x0001,
389
+ ADS_UF_ACCOUNTDISABLE: 0x0002,
390
+ ADS_UF_HOMEDIR_REQUIRED: 0x0008,
391
+ ADS_UF_LOCKOUT: 0x0010,
392
+ ADS_UF_PASSWD_NOTREQD: 0x0020,
393
+ ADS_UF_PASSWD_CANT_CHANGE: 0x0040,
394
+ ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: 0x0080,
395
+ ADS_UF_TEMP_DUPLICATE_ACCOUNT: 0x0100,
396
+ ADS_UF_NORMAL_ACCOUNT: 0x0200,
397
+ ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: 0x0800,
398
+ ADS_UF_WORKSTATION_TRUST_ACCOUNT: 0x1000,
399
+ ADS_UF_SERVER_TRUST_ACCOUNT: 0x2000,
400
+ ADS_UF_DONT_EXPIRE_PASSWD: 0x10000,
401
+ ADS_UF_MNS_LOGON_ACCOUNT: 0x20000,
402
+ ADS_UF_SMARTCARD_REQUIRED: 0x40000,
403
+ ADS_UF_TRUSTED_FOR_DELEGATION: 0x80000,
404
+ ADS_UF_NOT_DELEGATED: 0x100000,
405
+ ADS_UF_USE_DES_KEY_ONLY: 0x200000,
406
+ ADS_UF_DONT_REQUIRE_PREAUTH: 0x400000,
407
+ ADS_UF_PASSWORD_EXPIRED: 0x800000,
408
+ ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: 0x1000000
409
+ }
410
+
411
+ def userflag_set?(flag)
412
+ flag_value = ADS_USERFLAGS[flag] || 0
413
+ ! (self['UserFlags'] & flag_value).zero?
414
+ end
415
+
416
+ # Common helper for set_userflags and unset_userflags.
417
+ #
418
+ # @api private
419
+ def op_userflags(*flags, &block)
420
+ # Avoid an unnecessary set + commit operation.
421
+ return if flags.empty?
422
+
423
+ unrecognized_flags = flags.reject { |flag| ADS_USERFLAGS.keys.include?(flag) }
424
+ unless unrecognized_flags.empty?
425
+ raise ArgumentError, _("Unrecognized ADS UserFlags: %{unrecognized_flags}") % { unrecognized_flags: unrecognized_flags.join(', ') }
426
+ end
427
+
428
+ self['UserFlags'] = flags.inject(self['UserFlags'], &block)
429
+ end
430
+
431
+ def set_userflags(*flags)
432
+ op_userflags(*flags) { |userflags, flag| userflags | ADS_USERFLAGS[flag] }
433
+ end
434
+
435
+ def unset_userflags(*flags)
436
+ op_userflags(*flags) { |userflags, flag| userflags & ~ADS_USERFLAGS[flag] }
437
+ end
438
+
439
+ def disabled?
440
+ userflag_set?(:ADS_UF_ACCOUNTDISABLE)
441
+ end
442
+
443
+ def locked_out?
444
+ # Note that the LOCKOUT flag is known to be inaccurate when using the
445
+ # LDAP IADsUser provider, but this class consistently uses the WinNT
446
+ # provider, which is expected to be accurate.
447
+ userflag_set?(:ADS_UF_LOCKOUT)
448
+ end
449
+
450
+ def expired?
451
+ expires = native_object.Get('AccountExpirationDate')
452
+ expires && expires < Time.now
453
+ rescue WIN32OLERuntimeError => e
454
+ # This OLE error code indicates the property can't be found in the cache
455
+ raise e unless e.message =~ /8000500D/m
456
+ false
318
457
  end
319
458
 
320
459
  # UNLEN from lmcons.h - https://stackoverflow.com/a/2155176
@@ -341,47 +480,6 @@ module Puppet::Util::Windows::ADSI
341
480
  Puppet::Util::Windows::SID.name_to_principal(current_user_name)
342
481
  end
343
482
 
344
- def self.exists?(name_or_sid)
345
- well_known = false
346
- if (sid = Puppet::Util::Windows::SID.name_to_principal(name_or_sid))
347
- return true if sid.account_type == :SidTypeUser
348
-
349
- # 'well known group' is special as it can be a group like Everyone OR a user like SYSTEM
350
- # so try to resolve it
351
- # https://msdn.microsoft.com/en-us/library/cc234477.aspx
352
- well_known = sid.account_type == :SidTypeWellKnownGroup
353
- return false if sid.account_type != :SidTypeAlias && !well_known
354
- name_or_sid = "#{sid.domain}\\#{sid.account}"
355
- end
356
-
357
- user = Puppet::Util::Windows::ADSI.connect(User.uri(*User.parse_name(name_or_sid)))
358
- # otherwise, verify that the account is actually a User account
359
- user.Class == 'User'
360
- rescue
361
- # special accounts like SYSTEM cannot resolve via moniker like WinNT://./SYSTEM,user
362
- # and thus fail to connect - so given a validly resolved SID, this failure is ambiguous as it
363
- # may indicate either a group like Service or an account like SYSTEM
364
- well_known
365
- end
366
-
367
-
368
- def self.delete(name)
369
- Puppet::Util::Windows::ADSI.delete(name, 'user')
370
- end
371
-
372
- def self.each(&block)
373
- wql = Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"')
374
-
375
- users = []
376
- wql.each do |u|
377
- # Setting WIN32OLE.codepage in the microsoft_windows feature ensures
378
- # values are returned as UTF-8
379
- users << new(u.name)
380
- end
381
-
382
- users.each(&block)
383
- end
384
-
385
483
  ffi_convention :stdcall
386
484
 
387
485
  # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724432(v=vs.85).aspx
@@ -410,58 +508,33 @@ module Puppet::Util::Windows::ADSI
410
508
  end
411
509
  end
412
510
 
413
- class Group
414
- extend Enumerable
415
- extend Puppet::Util::Windows::ADSI::Shared
511
+ class Group < ADSIObject
416
512
 
417
513
  # https://msdn.microsoft.com/en-us/library/aa706021.aspx
418
514
  # IADsGroup interface
515
+ @object_class = 'group'
419
516
 
420
- attr_accessor :native_group
421
- attr_reader :name, :sid
422
- def initialize(name, native_group = nil)
423
- @name = name
424
- @native_group = native_group
425
- end
426
-
427
- def uri
428
- self.class.uri(sid.account, sid.domain)
429
- end
430
-
431
- def native_group
432
- @native_group ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(name)))
433
- end
434
-
435
- def sid
436
- @sid ||= Puppet::Util::Windows::SID.octet_string_to_principal(native_group.objectSID)
437
- end
438
-
439
- def commit
440
- begin
441
- native_group.SetInfo unless native_group.nil?
442
- rescue WIN32OLERuntimeError => e
443
- # ERROR_BAD_USERNAME 2202L from winerror.h
444
- if e.message =~ /8007089A/m
445
- raise Puppet::Error.new(
446
- _("Puppet is not able to create/delete domain groups with the group resource."),
447
- e
448
- )
449
- end
517
+ class << self
518
+ def list_all
519
+ Puppet::Util::Windows::ADSI.execquery('select name from win32_group where localaccount = "TRUE"')
520
+ end
450
521
 
451
- raise Puppet::Error.new( _("Group update failed: %{error}") % { error: e }, e )
522
+ def create(name)
523
+ # Windows error 2224: The account already exists.
524
+ raise Puppet::Error.new( _("Cannot create group if user '%{name}' exists.") % { name: name } ) if Puppet::Util::Windows::ADSI::User.exists?(name)
525
+ new(name, Puppet::Util::Windows::ADSI.create(name, @object_class))
452
526
  end
453
- self
454
527
  end
455
528
 
456
529
  def add_member_sids(*sids)
457
530
  sids.each do |sid|
458
- native_group.Add(Puppet::Util::Windows::ADSI.sid_uri(sid))
531
+ native_object.Add(Puppet::Util::Windows::ADSI.sid_uri(sid))
459
532
  end
460
533
  end
461
534
 
462
535
  def remove_member_sids(*sids)
463
536
  sids.each do |sid|
464
- native_group.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid))
537
+ native_object.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid))
465
538
  end
466
539
  end
467
540
 
@@ -469,13 +542,15 @@ module Puppet::Util::Windows::ADSI
469
542
  # may contain objects that represent unresolvable SIDs
470
543
  # qualified account names are returned by calling #domain_account
471
544
  def members
472
- self.class.get_sids(native_group.Members)
545
+ self.class.get_sids(native_object.Members)
473
546
  end
474
547
  alias member_sids members
475
548
 
476
549
  def set_members(desired_members, inclusive = true)
477
550
  return if desired_members.nil?
478
551
 
552
+ desired_members = desired_members.split(',').map(&:strip)
553
+
479
554
  current_hash = Hash[ self.member_sids.map { |sid| [sid.sid, sid] } ]
480
555
  desired_hash = self.class.name_sid_hash(desired_members)
481
556
 
@@ -496,50 +571,5 @@ module Puppet::Util::Windows::ADSI
496
571
  remove_member_sids(*members_to_remove)
497
572
  end
498
573
  end
499
-
500
- def self.create(name)
501
- # Windows error 2224: The account already exists.
502
- raise Puppet::Error.new( _("Cannot create group if user '%{name}' exists.") % { name: name } ) if Puppet::Util::Windows::ADSI::User.exists? name
503
- new(name, Puppet::Util::Windows::ADSI.create(name, 'group'))
504
- end
505
-
506
- def self.exists?(name_or_sid)
507
- well_known = false
508
- if (sid = Puppet::Util::Windows::SID.name_to_principal(name_or_sid))
509
- return true if sid.account_type == :SidTypeGroup
510
-
511
- # 'well known group' is special as it can be a group like Everyone OR a user like SYSTEM
512
- # so try to resolve it
513
- # https://msdn.microsoft.com/en-us/library/cc234477.aspx
514
- well_known = sid.account_type == :SidTypeWellKnownGroup
515
- return false if sid.account_type != :SidTypeAlias && !well_known
516
- name_or_sid = "#{sid.domain}\\#{sid.account}"
517
- end
518
-
519
- user = Puppet::Util::Windows::ADSI.connect(Group.uri(*Group.parse_name(name_or_sid)))
520
- user.Class == 'Group'
521
- rescue
522
- # special groups like Authenticated Users cannot resolve via moniker like WinNT://./Authenticated Users,group
523
- # and thus fail to connect - so given a validly resolved SID, this failure is ambiguous as it
524
- # may indicate either a group like Service or an account like SYSTEM
525
- well_known
526
- end
527
-
528
- def self.delete(name)
529
- Puppet::Util::Windows::ADSI.delete(name, 'group')
530
- end
531
-
532
- def self.each(&block)
533
- wql = Puppet::Util::Windows::ADSI.execquery('select name from win32_group where localaccount = "TRUE"')
534
-
535
- groups = []
536
- wql.each do |g|
537
- # Setting WIN32OLE.codepage in the microsoft_windows feature ensures
538
- # values are returned as UTF-8
539
- groups << new(g.name)
540
- end
541
-
542
- groups.each(&block)
543
- end
544
574
  end
545
575
  end