sys-admin 1.8.1-universal-mingw32 → 1.8.2-universal-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +4 -3
- data/CHANGES.md +7 -0
- data/MANIFEST.md +4 -3
- data/README.md +5 -0
- data/Rakefile +5 -2
- data/lib/bsd/sys/admin.rb +28 -24
- data/lib/darwin/sys/admin.rb +13 -9
- data/lib/linux/sys/admin.rb +18 -14
- data/lib/sunos/sys/admin.rb +16 -12
- data/lib/sys/admin/common.rb +5 -4
- data/lib/sys/admin/custom.rb +6 -3
- data/lib/sys/admin.rb +7 -1
- data/lib/sys-admin.rb +2 -0
- data/lib/unix/sys/admin.rb +6 -6
- data/lib/windows/sys/admin.rb +77 -81
- data/spec/spec_helper.rb +10 -1
- data/spec/sys_admin_shared.rb +15 -0
- data/spec/sys_admin_unix_spec.rb +54 -52
- data/spec/sys_admin_windows_spec.rb +79 -86
- data/sys-admin.gemspec +11 -7
- data.tar.gz.sig +0 -0
- metadata +34 -4
- metadata.gz.sig +0 -0
- data/spec/sys_admin_version_spec.rb +0 -11
data/lib/windows/sys/admin.rb
CHANGED
@@ -4,7 +4,9 @@ require 'win32/security'
|
|
4
4
|
require 'win32/registry'
|
5
5
|
require 'socket'
|
6
6
|
|
7
|
+
# The Sys module serves as a namespace only.
|
7
8
|
module Sys
|
9
|
+
# The Admin class provides a unified, cross platform replacement for the Etc module.
|
8
10
|
class Admin
|
9
11
|
extend FFI::Library
|
10
12
|
|
@@ -13,6 +15,7 @@ module Sys
|
|
13
15
|
#
|
14
16
|
class Error < StandardError; end
|
15
17
|
|
18
|
+
# rubocop:disable Naming/ConstantName
|
16
19
|
SidTypeUser = 1
|
17
20
|
SidTypeGroup = 2
|
18
21
|
SidTypeDomain = 3
|
@@ -22,8 +25,9 @@ module Sys
|
|
22
25
|
SidTypeInvalid = 7
|
23
26
|
SidTypeUnknown = 8
|
24
27
|
SidTypeComputer = 9
|
28
|
+
# rubocop:enable Naming/ConstantName
|
25
29
|
|
26
|
-
HKEY =
|
30
|
+
HKEY = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\'.freeze
|
27
31
|
private_constant :HKEY
|
28
32
|
|
29
33
|
# Retrieves the user's home directory. For local accounts query the
|
@@ -59,10 +63,10 @@ module Sys
|
|
59
63
|
def self.munge_options(opts)
|
60
64
|
rhash = {}
|
61
65
|
|
62
|
-
opts.each
|
66
|
+
opts.each do |k, v|
|
63
67
|
k = k.to_s.downcase.to_sym
|
64
68
|
rhash[k] = v
|
65
|
-
|
69
|
+
end
|
66
70
|
|
67
71
|
rhash
|
68
72
|
end
|
@@ -96,7 +100,7 @@ module Sys
|
|
96
100
|
|
97
101
|
# Used by the get_login method
|
98
102
|
ffi_lib :advapi32
|
99
|
-
attach_function :GetUserNameW, [
|
103
|
+
attach_function :GetUserNameW, %i[pointer pointer], :bool
|
100
104
|
private_class_method :GetUserNameW
|
101
105
|
|
102
106
|
# Creates the given +user+. If no domain option is specified,
|
@@ -146,13 +150,13 @@ module Sys
|
|
146
150
|
adsi = WIN32OLE.connect(moniker)
|
147
151
|
user = adsi.create('user', name)
|
148
152
|
|
149
|
-
options.each
|
153
|
+
options.each do |option, value|
|
150
154
|
if option.to_s == 'password'
|
151
155
|
user.setpassword(value)
|
152
156
|
else
|
153
157
|
user.put(option.to_s, value)
|
154
158
|
end
|
155
|
-
|
159
|
+
end
|
156
160
|
|
157
161
|
user.setinfo
|
158
162
|
rescue WIN32OLERuntimeError => err
|
@@ -199,13 +203,13 @@ module Sys
|
|
199
203
|
begin
|
200
204
|
adsi = WIN32OLE.connect("WinNT://#{domain}/#{name},user")
|
201
205
|
|
202
|
-
options.each
|
206
|
+
options.each do |option, value|
|
203
207
|
if option.to_s == 'password'
|
204
208
|
adsi.setpassword(value)
|
205
209
|
else
|
206
210
|
adsi.put(option.to_s, value)
|
207
211
|
end
|
208
|
-
|
212
|
+
end
|
209
213
|
|
210
214
|
adsi.setinfo
|
211
215
|
rescue WIN32OLERuntimeError => err
|
@@ -277,7 +281,7 @@ module Sys
|
|
277
281
|
# Adds +user+ to +group+ on the specified +domain+, or the localhost
|
278
282
|
# if no domain is specified.
|
279
283
|
#
|
280
|
-
def self.add_group_member(user, group, domain=nil)
|
284
|
+
def self.add_group_member(user, group, domain = nil)
|
281
285
|
domain ||= Socket.gethostname
|
282
286
|
adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
|
283
287
|
adsi.Add("WinNT://#{domain}/#{user}")
|
@@ -288,7 +292,7 @@ module Sys
|
|
288
292
|
# Removes +user+ from +group+ on the specified +domain+, or the localhost
|
289
293
|
# if no domain is specified.
|
290
294
|
#
|
291
|
-
def self.remove_group_member(user, group, domain=nil)
|
295
|
+
def self.remove_group_member(user, group, domain = nil)
|
292
296
|
domain ||= Socket.gethostname
|
293
297
|
adsi = WIN32OLE.connect("WinNT://#{domain}/#{group},group")
|
294
298
|
adsi.Remove("WinNT://#{domain}/#{user}")
|
@@ -397,7 +401,7 @@ module Sys
|
|
397
401
|
options = munge_options(options)
|
398
402
|
|
399
403
|
host = options.delete(:host) || Socket.gethostname
|
400
|
-
cs =
|
404
|
+
cs = 'winmgmts:{impersonationLevel=impersonate}!'
|
401
405
|
cs << "//#{host}/root/cimv2"
|
402
406
|
|
403
407
|
begin
|
@@ -406,20 +410,20 @@ module Sys
|
|
406
410
|
raise Error, err
|
407
411
|
end
|
408
412
|
|
409
|
-
query =
|
413
|
+
query = 'select * from win32_useraccount'
|
410
414
|
|
411
415
|
i = 0
|
412
416
|
|
413
|
-
options.each
|
417
|
+
options.each do |opt, val|
|
414
418
|
if i == 0
|
415
419
|
query << " where #{opt} = '#{val}'"
|
416
420
|
i += 1
|
417
421
|
else
|
418
422
|
query << " and #{opt} = '#{val}'"
|
419
423
|
end
|
420
|
-
|
424
|
+
end
|
421
425
|
|
422
|
-
if usr.
|
426
|
+
if usr.is_a?(Numeric)
|
423
427
|
if i == 0
|
424
428
|
query << " where sid like '%-#{usr}'"
|
425
429
|
else
|
@@ -435,14 +439,12 @@ module Sys
|
|
435
439
|
|
436
440
|
domain = options[:domain] || host
|
437
441
|
|
438
|
-
|
442
|
+
# rubocop:disable Metrics/BlockLength
|
443
|
+
wmi.execquery(query).each do |user|
|
439
444
|
uid = user.sid.split('-').last.to_i
|
440
445
|
|
441
|
-
# Because our 'like' query isn't fulproof, let's parse
|
442
|
-
|
443
|
-
if usr.kind_of?(Numeric)
|
444
|
-
next if usr != uid
|
445
|
-
end
|
446
|
+
# Because our 'like' query isn't fulproof, let's parse the SID again to make sure
|
447
|
+
next if usr.is_a?(Numeric) && usr != uid
|
446
448
|
|
447
449
|
groups, primary_group = *get_groups(domain, user.name)
|
448
450
|
|
@@ -470,7 +472,8 @@ module Sys
|
|
470
472
|
end
|
471
473
|
|
472
474
|
return user_object
|
473
|
-
|
475
|
+
end
|
476
|
+
# rubocop:enable Metrics/BlockLength
|
474
477
|
|
475
478
|
# If we're here, it means it wasn't found.
|
476
479
|
raise Error, "no user found for '#{usr}'"
|
@@ -499,32 +502,32 @@ module Sys
|
|
499
502
|
options = munge_options(options)
|
500
503
|
|
501
504
|
host = options.delete(:host) || Socket.gethostname
|
502
|
-
cs =
|
505
|
+
cs = 'winmgmts:{impersonationLevel=impersonate}!'
|
503
506
|
cs << "//#{host}/root/cimv2"
|
504
507
|
|
505
508
|
begin
|
506
509
|
wmi = WIN32OLE.connect(cs)
|
507
|
-
rescue WIN32OLERuntimeError =>
|
508
|
-
raise Error,
|
510
|
+
rescue WIN32OLERuntimeError => err
|
511
|
+
raise Error, err
|
509
512
|
end
|
510
513
|
|
511
|
-
query =
|
514
|
+
query = 'select * from win32_useraccount'
|
512
515
|
|
513
516
|
i = 0
|
514
517
|
|
515
|
-
options.each
|
518
|
+
options.each do |opt, val|
|
516
519
|
if i == 0
|
517
520
|
query << " where #{opt} = '#{val}'"
|
518
521
|
i += 1
|
519
522
|
else
|
520
523
|
query << " and #{opt} = '#{val}'"
|
521
524
|
end
|
522
|
-
|
525
|
+
end
|
523
526
|
|
524
527
|
array = []
|
525
528
|
domain = options[:domain] || host
|
526
529
|
|
527
|
-
wmi.execquery(query).each
|
530
|
+
wmi.execquery(query).each do |user|
|
528
531
|
uid = user.sid.split('-').last.to_i
|
529
532
|
|
530
533
|
usr = User.new do |u|
|
@@ -550,7 +553,7 @@ module Sys
|
|
550
553
|
end
|
551
554
|
|
552
555
|
array.push(usr)
|
553
|
-
|
556
|
+
end
|
554
557
|
|
555
558
|
array
|
556
559
|
end
|
@@ -586,7 +589,7 @@ module Sys
|
|
586
589
|
options = munge_options(options)
|
587
590
|
|
588
591
|
host = options.delete(:host) || Socket.gethostname
|
589
|
-
cs =
|
592
|
+
cs = 'winmgmts:{impersonationLevel=impersonate}!'
|
590
593
|
cs << "//#{host}/root/cimv2"
|
591
594
|
|
592
595
|
begin
|
@@ -595,20 +598,20 @@ module Sys
|
|
595
598
|
raise Error, err
|
596
599
|
end
|
597
600
|
|
598
|
-
query =
|
601
|
+
query = 'select * from win32_group'
|
599
602
|
|
600
603
|
i = 0
|
601
604
|
|
602
|
-
options.each
|
605
|
+
options.each do |opt, val|
|
603
606
|
if i == 0
|
604
607
|
query << " where #{opt} = '#{val}'"
|
605
608
|
i += 1
|
606
609
|
else
|
607
610
|
query << " and #{opt} = '#{val}'"
|
608
611
|
end
|
609
|
-
|
612
|
+
end
|
610
613
|
|
611
|
-
if grp.
|
614
|
+
if grp.is_a?(Integer)
|
612
615
|
if i == 0
|
613
616
|
query << " where sid like '%-#{grp}'"
|
614
617
|
else
|
@@ -624,14 +627,11 @@ module Sys
|
|
624
627
|
|
625
628
|
domain = options[:domain] || host
|
626
629
|
|
627
|
-
wmi.execquery(query).each
|
628
|
-
gid = group.sid.split(
|
630
|
+
wmi.execquery(query).each do |group|
|
631
|
+
gid = group.sid.split('-').last.to_i
|
629
632
|
|
630
|
-
# Because our 'like' query isn't fulproof, let's parse
|
631
|
-
|
632
|
-
if grp.kind_of?(Integer)
|
633
|
-
next if grp != gid
|
634
|
-
end
|
633
|
+
# Because our 'like' query isn't fulproof, let's parse the SID again to make sure
|
634
|
+
next if grp.is_a?(Integer) && grp != gid
|
635
635
|
|
636
636
|
group_object = Group.new do |g|
|
637
637
|
g.caption = group.caption
|
@@ -648,7 +648,7 @@ module Sys
|
|
648
648
|
end
|
649
649
|
|
650
650
|
return group_object
|
651
|
-
|
651
|
+
end
|
652
652
|
|
653
653
|
# If we're here, it means it wasn't found.
|
654
654
|
raise Error, "no group found for '#{grp}'"
|
@@ -678,7 +678,7 @@ module Sys
|
|
678
678
|
options = munge_options(options)
|
679
679
|
|
680
680
|
host = options.delete(:host) || Socket.gethostname
|
681
|
-
cs =
|
681
|
+
cs = 'winmgmts:{impersonationLevel=impersonate}!'
|
682
682
|
cs << "//#{host}/root/cimv2"
|
683
683
|
|
684
684
|
begin
|
@@ -687,28 +687,28 @@ module Sys
|
|
687
687
|
raise Error, err
|
688
688
|
end
|
689
689
|
|
690
|
-
query =
|
690
|
+
query = 'select * from win32_group'
|
691
691
|
|
692
692
|
i = 0
|
693
693
|
|
694
|
-
options.each
|
694
|
+
options.each do |opt, val|
|
695
695
|
if i == 0
|
696
696
|
query << " where #{opt} = '#{val}'"
|
697
697
|
i += 1
|
698
698
|
else
|
699
699
|
query << " and #{opt} = '#{val}'"
|
700
700
|
end
|
701
|
-
|
701
|
+
end
|
702
702
|
|
703
703
|
array = []
|
704
704
|
domain = options[:domain] || host
|
705
705
|
|
706
|
-
wmi.execquery(query).each
|
706
|
+
wmi.execquery(query).each do |group|
|
707
707
|
grp = Group.new do |g|
|
708
708
|
g.caption = group.caption
|
709
709
|
g.description = group.description
|
710
710
|
g.domain = group.domain
|
711
|
-
g.gid = group.sid.split(
|
711
|
+
g.gid = group.sid.split('-').last.to_i
|
712
712
|
g.install_date = group.installdate
|
713
713
|
g.local = group.localaccount
|
714
714
|
g.name = group.name
|
@@ -719,11 +719,12 @@ module Sys
|
|
719
719
|
end
|
720
720
|
|
721
721
|
array.push(grp)
|
722
|
-
|
722
|
+
end
|
723
723
|
|
724
724
|
array
|
725
725
|
end
|
726
726
|
|
727
|
+
# The User class encapsulates the information typically found within an /etc/passwd entry.
|
727
728
|
class User
|
728
729
|
# An account for users whose primary account is in another domain.
|
729
730
|
TEMP_DUPLICATE = 0x0100
|
@@ -839,9 +840,7 @@ module Sys
|
|
839
840
|
|
840
841
|
# Returns the SID type as a human readable string.
|
841
842
|
#
|
842
|
-
|
843
|
-
@sid_type
|
844
|
-
end
|
843
|
+
attr_reader :sid_type
|
845
844
|
|
846
845
|
# Sets the SID (Security Identifier) type to +stype+, which can be
|
847
846
|
# one of the following constant values:
|
@@ -877,7 +876,7 @@ module Sys
|
|
877
876
|
when Admin::SidTypeComputer
|
878
877
|
@sid_type = 'computer'
|
879
878
|
else
|
880
|
-
@sid_type = '
|
879
|
+
@sid_type = 'not_found'
|
881
880
|
end
|
882
881
|
end
|
883
882
|
|
@@ -918,6 +917,7 @@ module Sys
|
|
918
917
|
end
|
919
918
|
end
|
920
919
|
|
920
|
+
# The Group class encapsulates the information typically found within an /etc/group entry.
|
921
921
|
class Group
|
922
922
|
# Short description of the object.
|
923
923
|
attr_accessor :caption
|
@@ -961,14 +961,12 @@ module Sys
|
|
961
961
|
# Returns whether or not the group is a local group.
|
962
962
|
#
|
963
963
|
def local?
|
964
|
-
|
964
|
+
@local
|
965
965
|
end
|
966
966
|
|
967
967
|
# Returns the type of SID (Security Identifier) as a stringified value.
|
968
968
|
#
|
969
|
-
|
970
|
-
@sid_type
|
971
|
-
end
|
969
|
+
attr_reader :sid_type
|
972
970
|
|
973
971
|
# Sets the SID (Security Identifier) type to +stype+, which can be
|
974
972
|
# one of the following constant values:
|
@@ -984,34 +982,32 @@ module Sys
|
|
984
982
|
# * Admin::SidTypeComputer
|
985
983
|
#
|
986
984
|
def sid_type=(stype)
|
987
|
-
if stype.
|
985
|
+
if stype.is_a?(String)
|
988
986
|
@sid_type = stype.downcase
|
989
987
|
else
|
990
988
|
case stype
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
989
|
+
when Admin::SidTypeUser
|
990
|
+
@sid_type = 'user'
|
991
|
+
when Admin::SidTypeGroup
|
992
|
+
@sid_type = 'group'
|
993
|
+
when Admin::SidTypeDomain
|
994
|
+
@sid_type = 'domain'
|
995
|
+
when Admin::SidTypeAlias
|
996
|
+
@sid_type = 'alias'
|
997
|
+
when Admin::SidTypeWellKnownGroup
|
998
|
+
@sid_type = 'well_known_group'
|
999
|
+
when Admin::SidTypeDeletedAccount
|
1000
|
+
@sid_type = 'deleted_account'
|
1001
|
+
when Admin::SidTypeInvalid
|
1002
|
+
@sid_type = 'invalid'
|
1003
|
+
when Admin::SidTypeUnknown
|
1004
|
+
@sid_type = 'unknown'
|
1005
|
+
when Admin::SidTypeComputer
|
1006
|
+
@sid_type = 'computer'
|
1007
|
+
else
|
1008
|
+
@sid_type = 'not_found'
|
1011
1009
|
end
|
1012
1010
|
end
|
1013
|
-
|
1014
|
-
@sid_type
|
1015
1011
|
end
|
1016
1012
|
end
|
1017
1013
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rspec'
|
2
4
|
require 'sys-admin'
|
5
|
+
require 'sys_admin_shared'
|
3
6
|
|
4
7
|
RSpec.configure do |config|
|
8
|
+
config.include_context(Sys::Admin)
|
5
9
|
config.filter_run_excluding(:darwin) if Gem::Platform.local.os != 'darwin'
|
6
10
|
config.filter_run_excluding(:windows) unless Gem.win_platform?
|
7
11
|
|
8
12
|
if Gem.win_platform?
|
9
|
-
config.filter_run_excluding(:unix)
|
10
13
|
require 'win32-security'
|
11
14
|
require 'socket'
|
15
|
+
|
16
|
+
config.filter_run_excluding(:unix)
|
17
|
+
|
18
|
+
config.before(:each, :requires_elevated => true) do
|
19
|
+
skip 'skipped unless administrator privileges' unless Win32::Security.elevated_security?
|
20
|
+
end
|
12
21
|
end
|
13
22
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples Sys::Admin do
|
4
|
+
example 'version is set to expected value' do
|
5
|
+
expect(described_class::VERSION).to eq('1.8.2')
|
6
|
+
end
|
7
|
+
|
8
|
+
example 'version constant is frozen' do
|
9
|
+
expect(described_class::VERSION).to be_frozen
|
10
|
+
end
|
11
|
+
|
12
|
+
example 'constructor is private' do
|
13
|
+
expect{ described_class.new }.to raise_error(NoMethodError)
|
14
|
+
end
|
15
|
+
end
|