win32-file-security 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGES CHANGED
@@ -1,2 +1,8 @@
1
+ = 1.0.1 - 1-Jan-2013
2
+ * Added a working implementation of File.owned?
3
+ * Added a working implementation of File.chown.
4
+ * Added the File.owner method.
5
+ * Made the FFI functions private.
6
+
1
7
  = 1.0.0 - 19-Dec-2012
2
8
  * Initial release as an independent library.
data/README CHANGED
@@ -13,6 +13,8 @@
13
13
  require 'win32/file/security
14
14
 
15
15
  p File.get_permissions('file.txt')
16
+ p File.owner('file.txt')
17
+ p File.owned?('file.txt')
16
18
 
17
19
  == Notes
18
20
  If you have the win32-file gem already installed then you do not need this
@@ -33,7 +35,7 @@
33
35
  Artistic 2.0
34
36
 
35
37
  == Copyright
36
- (C) 2003-2012, Daniel J. Berger, All Rights Reserved
38
+ (C) 2003-2013, Daniel J. Berger, All Rights Reserved
37
39
 
38
40
  == Warranty
39
41
  This package is provided "as is" and without any express or
data/Rakefile CHANGED
@@ -25,6 +25,13 @@ namespace 'test' do
25
25
  t.verbose = true
26
26
  end
27
27
 
28
+ Rake::TestTask.new('constants') do |t|
29
+ task :test => :clean
30
+ t.warning = true
31
+ t.verbose = true
32
+ t.test_files = FileList['test/test_win32_file_security_constants']
33
+ end
34
+
28
35
  Rake::TestTask.new('encryption') do |t|
29
36
  task :test => :clean
30
37
  t.warning = true
@@ -32,6 +39,20 @@ namespace 'test' do
32
39
  t.test_files = FileList['test/test_win32_file_security_encryption']
33
40
  end
34
41
 
42
+ Rake::TestTask.new('ffi') do |t|
43
+ task :test => :clean
44
+ t.warning = true
45
+ t.verbose = true
46
+ t.test_files = FileList['test/test_win32_file_security_ffi']
47
+ end
48
+
49
+ Rake::TestTask.new('ownership') do |t|
50
+ task :test => :clean
51
+ t.warning = true
52
+ t.verbose = true
53
+ t.test_files = FileList['test/test_win32_file_security_ownership']
54
+ end
55
+
35
56
  Rake::TestTask.new('permissions') do |t|
36
57
  task :test => :clean
37
58
  t.warning = true
@@ -1,18 +1,33 @@
1
1
  module Windows
2
2
  module File
3
3
  module Constants
4
- SE_DACL_PRESENT = 4
5
- DACL_SECURITY_INFORMATION = 4
6
- ACCESS_ALLOWED_ACE_TYPE = 0
7
- ERROR_INSUFFICIENT_BUFFER = 122
8
- ACL_REVISION2 = 2
9
- ALLOW_ACE_LENGTH = 62
10
- OBJECT_INHERIT_ACE = 0x1
11
- CONTAINER_INHERIT_ACE = 0x2
12
- INHERIT_ONLY_ACE = 0x8
13
- MAXDWORD = 0xFFFFFFFF
4
+ SE_DACL_PRESENT = 4
5
+ OWNER_SECURITY_INFORMATION = 1
6
+ DACL_SECURITY_INFORMATION = 4
7
+ ACCESS_ALLOWED_ACE_TYPE = 0
8
+ ERROR_INSUFFICIENT_BUFFER = 122
9
+ ACL_REVISION2 = 2
10
+ ALLOW_ACE_LENGTH = 62
11
+ OBJECT_INHERIT_ACE = 0x1
12
+ CONTAINER_INHERIT_ACE = 0x2
13
+ INHERIT_ONLY_ACE = 0x8
14
+ MAXDWORD = 0xFFFFFFFF
15
+ TOKEN_QUERY = 0x00000008
16
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
17
+ TokenUser = 1
18
+
19
+ SECURITY_DESCRIPTOR_REVISION = 1
14
20
  SECURITY_DESCRIPTOR_MIN_LENGTH = 20
15
21
 
22
+ SE_KERNEL_OBJECT = 6
23
+ SE_FILE_OBJECT = 1
24
+ SE_PRIVILEGE_ENABLED = 0x00000002
25
+ SE_SECURITY_NAME = "SeSecurityPrivilege"
26
+ SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"
27
+ SE_BACKUP_NAME = "SeBackupPrivilege"
28
+ SE_RESTORE_NAME = "SeRestorePrivilege"
29
+ SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"
30
+
16
31
  ## Security Rights
17
32
 
18
33
  SYNCHRONIZE = 0x100000
@@ -30,6 +45,9 @@ module Windows
30
45
  GENERIC_ALL = 0x10000000
31
46
  GENERIC_RIGHTS_CHK = 0xF0000000
32
47
  REST_RIGHTS_MASK = 0x001FFFFF
48
+ READ_CONTROL = 0x20000
49
+ WRITE_DAC = 0x40000
50
+ WRITE_OWNER = 0x80000
33
51
 
34
52
  FILE_READ_DATA = 1
35
53
  FILE_LIST_DIRECTORY = 1
@@ -0,0 +1,62 @@
1
+ require 'ffi'
2
+
3
+ module Windows
4
+ module File
5
+ module Functions
6
+
7
+ # Make FFI functions private
8
+ module FFI::Library
9
+ def attach_pfunc(*args)
10
+ attach_function(*args)
11
+ private args[0]
12
+ end
13
+ end
14
+
15
+ extend FFI::Library
16
+
17
+ # For convenience
18
+ typedef :pointer, :ptr
19
+ typedef :buffer_in, :buf_in
20
+ typedef :buffer_out, :buf_out
21
+ typedef :string, :str
22
+
23
+
24
+ ffi_lib :advapi32
25
+
26
+ attach_pfunc :AddAce, [:ptr, :ulong, :ulong, :ptr, :ulong], :bool
27
+ attach_pfunc :AdjustTokenPrivileges, [:ulong, :bool, :ptr, :ulong, :ptr, :ptr], :bool
28
+ attach_pfunc :CopySid, [:ulong, :ptr, :ptr], :bool
29
+ attach_pfunc :EncryptFileW, [:buf_in], :bool
30
+ attach_pfunc :DecryptFileW, [:buf_in, :ulong], :bool
31
+ attach_pfunc :FileEncryptionStatusW, [:buf_in, :ptr], :bool
32
+ attach_pfunc :GetAce, [:ptr, :ulong, :ptr], :bool
33
+ attach_pfunc :GetFileSecurityW, [:buf_in, :ulong, :ptr, :ulong, :ptr], :bool
34
+ attach_pfunc :GetLengthSid, [:ptr], :ulong
35
+ attach_pfunc :GetSecurityDescriptorControl, [:ptr, :ptr, :ptr], :bool
36
+ attach_pfunc :GetSecurityDescriptorOwner, [:ptr, :ptr, :ptr], :bool
37
+ attach_pfunc :GetSecurityDescriptorDacl, [:ptr, :ptr, :ptr, :ptr], :ulong
38
+ attach_pfunc :GetSecurityInfo, [:ulong, :ulong, :ulong, :ptr, :ptr, :ptr, :ptr, :ptr], :ulong
39
+ attach_pfunc :GetTokenInformation, [:ulong, :int, :ptr, :ulong, :ptr], :bool
40
+ attach_pfunc :InitializeAcl, [:ptr, :ulong, :ulong], :bool
41
+ attach_pfunc :InitializeSecurityDescriptor, [:ptr, :ulong], :bool
42
+ attach_pfunc :LookupAccountNameW, [:buf_in, :buf_in, :ptr, :ptr, :ptr, :ptr, :ptr], :bool
43
+ attach_pfunc :LookupAccountSidW, [:buf_in, :ptr, :ptr, :ptr, :ptr, :ptr, :ptr], :bool
44
+ attach_pfunc :LookupPrivilegeValueA, [:str, :str, :ptr], :bool
45
+ attach_pfunc :OpenProcessToken, [:ulong, :ulong, :ptr], :bool
46
+ attach_pfunc :SetFileSecurityW, [:buf_in, :ulong, :ptr], :bool
47
+ attach_pfunc :SetSecurityDescriptorDacl, [:ptr, :bool, :ptr, :bool], :bool
48
+ attach_pfunc :SetSecurityDescriptorOwner, [:ptr, :ptr, :bool], :bool
49
+
50
+ ffi_lib :kernel32
51
+
52
+ attach_pfunc :CloseHandle, [:ulong], :bool
53
+ attach_pfunc :GetCurrentProcess, [], :ulong
54
+ attach_pfunc :GetVolumeInformationW, [:buf_in, :buf_out, :ulong, :ptr, :ptr, :ptr, :buf_out, :ulong], :bool
55
+
56
+ ffi_lib :shlwapi
57
+
58
+ attach_pfunc :PathStripToRootW, [:buf_in], :bool
59
+ attach_pfunc :PathIsRootW, [:buf_in], :bool
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,7 @@
1
+ class String
2
+ # Convenience method for converting strings to UTF-16LE for wide character
3
+ # functions that require it.
4
+ def wincode
5
+ (self.tr(File::SEPARATOR, File::ALT_SEPARATOR) + 0.chr).encode('UTF-16LE')
6
+ end
7
+ end
@@ -24,7 +24,7 @@ module Windows
24
24
  :Header, ACE_HEADER,
25
25
  :Mask, :ulong,
26
26
  :SidStart, :ulong,
27
- :dummy, [:uchar, 40]
27
+ :dummy, [:uchar, 40]
28
28
  )
29
29
  end
30
30
 
@@ -37,6 +37,21 @@ module Windows
37
37
  :Sbz2, :ushort
38
38
  )
39
39
  end
40
+
41
+ class LUID < FFI::Struct
42
+ layout(:LowPart, :ulong, :HighPart, :long)
43
+ end
44
+
45
+ class LUID_AND_ATTRIBUTES < FFI::Struct
46
+ layout(:Luid, LUID, :Attributes, :ulong)
47
+ end
48
+
49
+ class TOKEN_PRIVILEGES < FFI::Struct
50
+ layout(
51
+ :PrivilegeCount, :ulong,
52
+ :Privileges, [LUID_AND_ATTRIBUTES, 1]
53
+ )
54
+ end
40
55
  end
41
56
  end
42
57
  end
@@ -1,6 +1,8 @@
1
- require File.join(File.dirname(__FILE__), 'windows', 'constants')
2
- require File.join(File.dirname(__FILE__), 'windows', 'structs')
3
- require File.join(File.dirname(__FILE__), 'windows', 'functions')
1
+ require File.join(File.dirname(__FILE__), 'security', 'constants')
2
+ require File.join(File.dirname(__FILE__), 'security', 'structs')
3
+ require File.join(File.dirname(__FILE__), 'security', 'functions')
4
+ require File.join(File.dirname(__FILE__), 'security', 'helper')
5
+ require 'socket'
4
6
 
5
7
  class File
6
8
  include Windows::File::Constants
@@ -10,9 +12,11 @@ class File
10
12
  extend Windows::File::Functions
11
13
 
12
14
  # The version of the win32-file library
13
- WIN32_FILE_SECURITY_VERSION = '1.0.0'
15
+ WIN32_FILE_SECURITY_VERSION = '1.0.1'
14
16
 
15
17
  class << self
18
+ remove_method(:owned?)
19
+ remove_method(:chown)
16
20
 
17
21
  # Returns the encryption status of a file as a string. Possible return
18
22
  # values are:
@@ -188,9 +192,7 @@ class File
188
192
  size_needed_ptr
189
193
  )
190
194
 
191
- unless bool
192
- raise SystemCallError.new("GetFileSecurity", FFI.errno)
193
- end
195
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
194
196
 
195
197
  control_ptr = FFI::MemoryPointer.new(:ulong)
196
198
  revision_ptr = FFI::MemoryPointer.new(:ulong)
@@ -246,7 +248,7 @@ class File
246
248
 
247
249
  use_ptr = FFI::MemoryPointer.new(:pointer)
248
250
 
249
- val = LookupAccountSidW(
251
+ bool = LookupAccountSidW(
250
252
  wide_host,
251
253
  ace_pptr.read_pointer + 8,
252
254
  name,
@@ -256,9 +258,7 @@ class File
256
258
  use_ptr
257
259
  )
258
260
 
259
- if val == 0
260
- raise SystemCallError.new("LookupAccountSid", FFI.errno)
261
- end
261
+ raise SystemCallError.new("LookupAccountSid", FFI.errno) unless bool
262
262
 
263
263
  # The x2 multiplier is necessary due to wide char strings.
264
264
  name = name.read_string(name_size.read_ulong * 2).delete(0.chr)
@@ -289,7 +289,6 @@ class File
289
289
  # * FILE_DELETE_CHILD
290
290
  # * FILE_READ_ATTRIBUTES
291
291
  # * FILE_WRITE_ATTRIBUTES
292
- # * STANDARD_RIGHTS_ALL
293
292
  # * FULL
294
293
  # * READ
295
294
  # * ADD
@@ -299,11 +298,11 @@ class File
299
298
  # * WRITE_DAC
300
299
  # * WRITE_OWNER
301
300
  # * SYNCHRONIZE
301
+ # * STANDARD_RIGHTS_ALL
302
302
  # * STANDARD_RIGHTS_REQUIRED
303
303
  # * STANDARD_RIGHTS_READ
304
304
  # * STANDARD_RIGHTS_WRITE
305
305
  # * STANDARD_RIGHTS_EXECUTE
306
- # * STANDARD_RIGHTS_ALL
307
306
  # * SPECIFIC_RIGHTS_ALL
308
307
  # * ACCESS_SYSTEM_SECURITY
309
308
  # * MAXIMUM_ALLOWED
@@ -466,5 +465,267 @@ class File
466
465
 
467
466
  sec_array
468
467
  end
468
+
469
+ # Returns true if the effective user ID of the process is the same as the
470
+ # owner of the named file.
471
+ #
472
+ # Example:
473
+ #
474
+ # p File.owned?('some_file.txt') # => true
475
+ # p File.owned?('C:/Windows/regedit.ext') # => false
476
+ #--
477
+ # This method was redefined for MS Windows.
478
+ #
479
+ def owned?(file)
480
+ return_value = false
481
+ wide_file = file.wincode
482
+ size_needed_ptr = FFI::MemoryPointer.new(:ulong)
483
+
484
+ # First pass, get the size needed
485
+ bool = GetFileSecurityW(
486
+ wide_file,
487
+ OWNER_SECURITY_INFORMATION,
488
+ nil,
489
+ 0,
490
+ size_needed_ptr
491
+ )
492
+
493
+ size_needed = size_needed_ptr.read_ulong
494
+
495
+ security_ptr = FFI::MemoryPointer.new(size_needed)
496
+
497
+ # Second pass, this time with the appropriately sized security pointer
498
+ bool = GetFileSecurityW(
499
+ wide_file,
500
+ OWNER_SECURITY_INFORMATION,
501
+ security_ptr,
502
+ security_ptr.size,
503
+ size_needed_ptr
504
+ )
505
+
506
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
507
+
508
+ sid_ptr = FFI::MemoryPointer.new(:pointer)
509
+ defaulted = FFI::MemoryPointer.new(:bool)
510
+
511
+ unless GetSecurityDescriptorOwner(security_ptr, sid_ptr, defaulted)
512
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
513
+ end
514
+
515
+ sid = sid_ptr.read_pointer
516
+
517
+ token = FFI::MemoryPointer.new(:ulong)
518
+
519
+ begin
520
+ # Get the current process sid
521
+ unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
522
+ raise SystemCallError, FFI.errno, "OpenProcessToken"
523
+ end
524
+
525
+ token = token.read_ulong
526
+ rlength = FFI::MemoryPointer.new(:ulong)
527
+ tuser = 0.chr * 512
528
+
529
+ bool = GetTokenInformation(
530
+ token,
531
+ TokenUser,
532
+ tuser,
533
+ tuser.size,
534
+ rlength
535
+ )
536
+
537
+ unless bool
538
+ raise SystemCallError, FFI.errno, "GetTokenInformation"
539
+ end
540
+
541
+ string_sid = tuser[8, (rlength.read_ulong - 8)]
542
+
543
+ # Now compare the sid strings
544
+ if string_sid == sid.read_string(string_sid.size)
545
+ return_value = true
546
+ end
547
+ ensure
548
+ CloseHandle(token)
549
+ end
550
+
551
+ return_value
552
+ end
553
+
554
+ # Changes the owner of the named file(s) to the given owner (userid).
555
+ # It will typically require elevated privileges in order to change the
556
+ # owner of a file.
557
+ #
558
+ # This group argument is currently ignored, but is included in the method
559
+ # definition for compatibility with the current spec. Also note that the
560
+ # owner should be a string, not a numeric ID.
561
+ #
562
+ # Example:
563
+ #
564
+ # File.chown('some_user', nil, 'some_file.txt')
565
+ #--
566
+ # In the future we may allow the owner argument to be a SID or a RID and
567
+ # simply adjust accordingly.
568
+ #
569
+ def chown(owner, group, *files)
570
+ token = FFI::MemoryPointer.new(:ulong)
571
+
572
+ begin
573
+ bool = OpenProcessToken(
574
+ GetCurrentProcess(),
575
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
576
+ token
577
+ )
578
+
579
+ raise SystemCallError.new("OpenProcessToken", FFI.errno) unless bool
580
+
581
+ token_handle = token.read_ulong
582
+
583
+ privs = [
584
+ SE_SECURITY_NAME,
585
+ SE_TAKE_OWNERSHIP_NAME,
586
+ SE_BACKUP_NAME,
587
+ SE_RESTORE_NAME,
588
+ SE_CHANGE_NOTIFY_NAME
589
+ ]
590
+
591
+ privs.each{ |name|
592
+ luid = LUID.new
593
+
594
+ unless LookupPrivilegeValueA(nil, name, luid)
595
+ raise SystemCallError.new("LookupPrivilegeValue", FFI.errno)
596
+ end
597
+
598
+ tp = TOKEN_PRIVILEGES.new
599
+ tp[:PrivilegeCount] = 1
600
+ tp[:Privileges][0][:Luid] = luid
601
+ tp[:Privileges][0][:Attributes] = SE_PRIVILEGE_ENABLED
602
+
603
+ unless AdjustTokenPrivileges(token_handle, false, tp, 0, nil, nil)
604
+ raise SystemCallError.new("AdjustTokenPrivileges", FFI.errno)
605
+ end
606
+ }
607
+
608
+ sid = FFI::MemoryPointer.new(:uchar)
609
+ sid_size = FFI::MemoryPointer.new(:ulong)
610
+ dom = FFI::MemoryPointer.new(:uchar)
611
+ dom_size = FFI::MemoryPointer.new(:ulong)
612
+ use = FFI::MemoryPointer.new(:ulong)
613
+
614
+ wowner = owner.wincode
615
+
616
+ # First run, get needed sizes
617
+ LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
618
+
619
+ sid = FFI::MemoryPointer.new(:uchar, sid_size.read_ulong * 2)
620
+ dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
621
+
622
+ # Second run with required sizes
623
+ unless LookupAccountNameW(nil, wowner, sid, sid_size, dom, dom_size, use)
624
+ raise SystemCallError.new("LookupAccountName", FFI.errno)
625
+ end
626
+
627
+ files.each{ |file|
628
+ wfile = file.wincode
629
+
630
+ size = FFI::MemoryPointer.new(:ulong)
631
+ sec = FFI::MemoryPointer.new(:ulong)
632
+
633
+ # First pass, get the size needed
634
+ GetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, sec, sec.size, size)
635
+
636
+ security = FFI::MemoryPointer.new(size.read_ulong)
637
+
638
+ # Second pass, this time with the appropriately sized security pointer
639
+ bool = GetFileSecurityW(
640
+ wfile,
641
+ OWNER_SECURITY_INFORMATION,
642
+ security,
643
+ security.size,
644
+ size
645
+ )
646
+
647
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
648
+
649
+ unless InitializeSecurityDescriptor(security, SECURITY_DESCRIPTOR_REVISION)
650
+ raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
651
+ end
652
+
653
+ unless SetSecurityDescriptorOwner(security, sid, false)
654
+ raise SystemCallError.new("SetSecurityDescriptorOwner", FFI.errno)
655
+ end
656
+
657
+ unless SetFileSecurityW(wfile, OWNER_SECURITY_INFORMATION, security)
658
+ raise SystemCallError.new("SetFileSecurity", FFI.errno)
659
+ end
660
+ }
661
+ ensure
662
+ CloseHandle(token.read_ulong)
663
+ end
664
+
665
+ files.size
666
+ end
667
+
668
+ # Returns the owner of the specified file in domain\\userid format.
669
+ #
670
+ # Example:
671
+ #
672
+ # p File.owner('some_file.txt') # => "your_domain\\some_user"
673
+ #
674
+ def owner(file)
675
+ size_needed = FFI::MemoryPointer.new(:ulong)
676
+
677
+ # First pass, get the size needed
678
+ bool = GetFileSecurityW(
679
+ file.wincode,
680
+ OWNER_SECURITY_INFORMATION,
681
+ nil,
682
+ 0,
683
+ size_needed
684
+ )
685
+
686
+ security = FFI::MemoryPointer.new(size_needed.read_ulong)
687
+
688
+ # Second pass, this time with the appropriately sized security pointer
689
+ bool = GetFileSecurityW(
690
+ file.wincode,
691
+ OWNER_SECURITY_INFORMATION,
692
+ security,
693
+ security.size,
694
+ size_needed
695
+ )
696
+
697
+ raise SystemCallError.new("GetFileSecurity", FFI.errno) unless bool
698
+
699
+ sid = FFI::MemoryPointer.new(:pointer)
700
+ defaulted = FFI::MemoryPointer.new(:bool)
701
+
702
+ unless GetSecurityDescriptorOwner(security, sid, defaulted)
703
+ raise SystemCallError.new("GetFileSecurity", FFI.errno)
704
+ end
705
+
706
+ sid = sid.read_pointer
707
+
708
+ name = FFI::MemoryPointer.new(:uchar)
709
+ name_size = FFI::MemoryPointer.new(:ulong)
710
+ dom = FFI::MemoryPointer.new(:uchar)
711
+ dom_size = FFI::MemoryPointer.new(:ulong)
712
+ use = FFI::MemoryPointer.new(:pointer)
713
+
714
+ # First call, get sizes needed
715
+ LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
716
+
717
+ name = FFI::MemoryPointer.new(:uchar, name_size.read_ulong * 2)
718
+ dom = FFI::MemoryPointer.new(:uchar, dom_size.read_ulong * 2)
719
+
720
+ # Second call, get desired information
721
+ unless LookupAccountSidW(nil, sid, name, name_size, dom, dom_size, use)
722
+ raise SystemCallError.new("LookupAccountSid", FFI.errno)
723
+ end
724
+
725
+ name = name.read_string(name.size).tr(0.chr, '').strip
726
+ domain = dom.read_string(dom.size).tr(0.chr, '').strip
727
+
728
+ domain << "\\" << name
729
+ end
469
730
  end
470
731
  end
@@ -0,0 +1,54 @@
1
+ ########################################################################
2
+ # test_win32_file_security_constants.rb
3
+ #
4
+ # Tests to ensure that certain constants are defined for the
5
+ # win32-file-security library.
6
+ ########################################################################
7
+ require 'test-unit'
8
+ require 'win32/file/security'
9
+
10
+ class TC_Win32_File_Constants < Test::Unit::TestCase
11
+ test "file security rights constants are defined" do
12
+ assert_not_nil(File::FILE_READ_DATA)
13
+ assert_not_nil(File::FILE_WRITE_DATA)
14
+ assert_not_nil(File::FILE_APPEND_DATA)
15
+ assert_not_nil(File::FILE_READ_EA)
16
+ assert_not_nil(File::FILE_EXECUTE)
17
+ assert_not_nil(File::FILE_DELETE_CHILD)
18
+ assert_not_nil(File::FILE_READ_ATTRIBUTES)
19
+ assert_not_nil(File::FILE_WRITE_ATTRIBUTES)
20
+ end
21
+
22
+ test "standard security rights constants are defined" do
23
+ assert_not_nil(File::STANDARD_RIGHTS_ALL)
24
+ assert_not_nil(File::STANDARD_RIGHTS_REQUIRED)
25
+ assert_not_nil(File::STANDARD_RIGHTS_READ)
26
+ assert_not_nil(File::STANDARD_RIGHTS_WRITE)
27
+ assert_not_nil(File::STANDARD_RIGHTS_EXECUTE)
28
+ end
29
+
30
+ test "generic security rights constants are defined" do
31
+ assert_not_nil(File::GENERIC_READ)
32
+ assert_not_nil(File::GENERIC_WRITE)
33
+ assert_not_nil(File::GENERIC_EXECUTE)
34
+ assert_not_nil(File::GENERIC_ALL)
35
+ end
36
+
37
+ test "combined security rights constants are defined" do
38
+ assert_not_nil(File::FULL)
39
+ assert_not_nil(File::READ)
40
+ assert_not_nil(File::CHANGE)
41
+ assert_not_nil(File::ADD)
42
+ assert_not_nil(File::DELETE)
43
+ end
44
+
45
+ test "miscellaneous security rights constants are defined" do
46
+ assert_not_nil(File::READ_CONTROL)
47
+ assert_not_nil(File::WRITE_DAC)
48
+ assert_not_nil(File::WRITE_OWNER)
49
+ assert_not_nil(File::SYNCHRONIZE)
50
+ assert_not_nil(File::SPECIFIC_RIGHTS_ALL)
51
+ assert_not_nil(File::ACCESS_SYSTEM_SECURITY)
52
+ assert_not_nil(File::MAXIMUM_ALLOWED)
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ #############################################################################
2
+ # test_win32_file_security_ffi.rb
3
+ #
4
+ # Tests to ensure that the FFI functions are private
5
+ #############################################################################
6
+ require 'test-unit'
7
+ require 'win32/file/security'
8
+
9
+ class TC_Win32_File_Security_FFI < Test::Unit::TestCase
10
+ def setup
11
+ @singleton_methods = File.methods.map{ |m| m.to_s }
12
+ @instance_methods = File.instance_methods.map{ |m| m.to_s }
13
+ end
14
+
15
+ test "internal ffi functions are not public as singleton methods" do
16
+ assert_false(@singleton_methods.include?('AddAce'))
17
+ assert_false(@singleton_methods.include?('CloseHandle'))
18
+ assert_false(@singleton_methods.include?('GetFileSecurityW'))
19
+ assert_false(@singleton_methods.include?('PathIsRootW'))
20
+ end
21
+
22
+ test "internal ffi functions are not public as instance methods" do
23
+ assert_false(@instance_methods.include?('AddAce'))
24
+ assert_false(@instance_methods.include?('CloseHandle'))
25
+ assert_false(@instance_methods.include?('GetFileSecurityW'))
26
+ assert_false(@instance_methods.include?('PathIsRootW'))
27
+ end
28
+
29
+ def teardown
30
+ @singleton_methods = nil
31
+ @instance_methods = nil
32
+ end
33
+ end
@@ -0,0 +1,108 @@
1
+ #############################################################################
2
+ # test_win32_file_ownership.rb
3
+ #
4
+ # Test case for the file ownership related methods
5
+ #############################################################################
6
+ require 'etc'
7
+ require 'socket'
8
+ require 'sys/admin'
9
+ require 'test-unit'
10
+ require 'win32/security'
11
+ require 'win32/file/security'
12
+
13
+ class TC_Win32_File_Security_Ownership < Test::Unit::TestCase
14
+ def self.startup
15
+ Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
16
+ @@file = File.join(Dir.pwd, 'ownership_test.txt')
17
+ File.open(@@file, 'w'){ |fh| fh.puts "This is an ownership test." }
18
+
19
+ @@host = Socket.gethostname
20
+ @@temp = "Temp"
21
+ @@login = Etc.getlogin
22
+
23
+ if Win32::Security.elevated_security?
24
+ Sys::Admin.add_user(:name => @@temp, :description => "Delete Me")
25
+ end
26
+ end
27
+
28
+ def setup
29
+ @elevated = Win32::Security.elevated_security?
30
+ end
31
+
32
+ test "owned? method basic functionality" do
33
+ assert_respond_to(File, :owned?)
34
+ assert_nothing_raised{ File.owned?(@@file) }
35
+ assert_boolean(File.owned?(@@file))
36
+ end
37
+
38
+ test "owned? method returns expected result" do
39
+ if Win32::Security.elevated_security?
40
+ assert_false(File.owned?(@@file))
41
+ else
42
+ assert_true(File.owned?(@@file))
43
+ end
44
+ assert_false(File.owned?("C:\\Windows\\regedit.exe"))
45
+ end
46
+
47
+ test "owned? requires a single argument" do
48
+ assert_raise(ArgumentError){ File.owned? }
49
+ assert_raise(ArgumentError){ File.owned?(@@file, @@file) }
50
+ end
51
+
52
+ test "owner method basic functionality" do
53
+ assert_respond_to(File, :owner)
54
+ assert_nothing_raised{ File.owner(@@file) }
55
+ assert_kind_of(String, File.owner(@@file))
56
+ end
57
+
58
+ test "owner method returns the expected value" do
59
+ if Win32::Security.elevated_security?
60
+ expected = "BUILTIN\\Administrators"
61
+ else
62
+ expected = @@host + "\\" + @@login
63
+ end
64
+ assert_equal(expected, File.owner(@@file))
65
+ end
66
+
67
+ test "owner method requires a single argument" do
68
+ assert_raise(ArgumentError){ File.owner }
69
+ assert_raise(ArgumentError){ File.owner(@@file, @@file) }
70
+ end
71
+
72
+ test "chown method basic functionality" do
73
+ assert_respond_to(File, :chown)
74
+ end
75
+
76
+ test "chown works as expected" do
77
+ omit_unless(@elevated)
78
+ original_owner = File.owner(@@file)
79
+ expected_owner = @@host + "\\" + @@temp
80
+
81
+ assert_nothing_raised{ File.chown(@@temp, nil, @@file) }
82
+ assert_equal(expected_owner, File.owner(@@file))
83
+ assert_nothing_raised{ File.chown(original_owner, nil, @@file) }
84
+ assert_equal(original_owner, File.owner(@@file))
85
+ end
86
+
87
+ test "chown returns the number of files processed" do
88
+ omit_unless(@elevated)
89
+ assert_equal(1, File.chown(@@temp, nil, @@file))
90
+ end
91
+
92
+ test "chown requires at least two arguments" do
93
+ assert_raise(ArgumentError){ File.chown }
94
+ assert_raise(ArgumentError){ File.chown(@@temp) }
95
+ end
96
+
97
+ def teardown
98
+ @elevated = nil
99
+ end
100
+
101
+ def self.shutdown
102
+ Sys::Admin.delete_user(@@temp) if Win32::Security.elevated_security?
103
+ File.delete(@@file) if File.exists?(@@file)
104
+ @@file = nil
105
+ @@login = nil
106
+ @@host = nil
107
+ end
108
+ end
@@ -8,6 +8,6 @@ require 'win32/file/security'
8
8
 
9
9
  class TC_Win32_File_Security_Version < Test::Unit::TestCase
10
10
  test "version is set to expected value" do
11
- assert_equal('1.0.0', File::WIN32_FILE_SECURITY_VERSION)
11
+ assert_equal('1.0.1', File::WIN32_FILE_SECURITY_VERSION)
12
12
  end
13
13
  end
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'win32-file-security'
5
- spec.version = '1.0.0'
5
+ spec.version = '1.0.1'
6
6
  spec.authors = ['Daniel J. Berger', 'Park Heesob']
7
7
  spec.license = 'Artistic 2.0'
8
8
  spec.email = 'djberg96@gmail.com'
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.add_dependency('ffi')
18
18
  spec.add_development_dependency('test-unit')
19
19
  spec.add_development_dependency('win32-security')
20
+ spec.add_development_dependency('sys-admin')
20
21
 
21
22
  spec.description = <<-EOF
22
23
  The win32-file-security library adds security related methods to the
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32-file-security
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-19 00:00:00.000000000 Z
13
+ date: 2013-01-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ffi
@@ -60,6 +60,22 @@ dependencies:
60
60
  - - ! '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: sys-admin
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
63
79
  description: ! " The win32-file-security library adds security related methods
64
80
  to the\n core File class for MS Windows. This includes the ability to get or\n
65
81
  \ set permissions, as well as encrypt or decrypt files.\n"
@@ -72,14 +88,18 @@ extra_rdoc_files:
72
88
  - MANIFEST
73
89
  files:
74
90
  - CHANGES
91
+ - lib/win32/file/security/constants.rb
92
+ - lib/win32/file/security/functions.rb
93
+ - lib/win32/file/security/helper.rb
94
+ - lib/win32/file/security/structs.rb
75
95
  - lib/win32/file/security.rb
76
- - lib/win32/file/windows/constants.rb
77
- - lib/win32/file/windows/functions.rb
78
- - lib/win32/file/windows/structs.rb
79
96
  - MANIFEST
80
97
  - Rakefile
81
98
  - README
99
+ - test/test_win32_file_security_constants.rb
82
100
  - test/test_win32_file_security_encryption.rb
101
+ - test/test_win32_file_security_ffi.rb
102
+ - test/test_win32_file_security_ownership.rb
83
103
  - test/test_win32_file_security_permissions.rb
84
104
  - test/test_win32_file_security_version.rb
85
105
  - win32-file-security.gemspec
@@ -109,6 +129,9 @@ signing_key:
109
129
  specification_version: 3
110
130
  summary: File security methods for the File class on MS Windows
111
131
  test_files:
132
+ - test/test_win32_file_security_constants.rb
112
133
  - test/test_win32_file_security_encryption.rb
134
+ - test/test_win32_file_security_ffi.rb
135
+ - test/test_win32_file_security_ownership.rb
113
136
  - test/test_win32_file_security_permissions.rb
114
137
  - test/test_win32_file_security_version.rb
@@ -1,46 +0,0 @@
1
- require 'ffi'
2
-
3
- module Windows
4
- module File
5
- module Functions
6
- extend FFI::Library
7
- ffi_lib :advapi32
8
-
9
- attach_function :AddAce, [:pointer, :ulong, :ulong, :pointer, :ulong], :bool
10
- attach_function :CopySid, [:ulong, :pointer, :pointer], :bool
11
- attach_function :EncryptFileW, [:buffer_in], :bool
12
- attach_function :DecryptFileW, [:buffer_in, :ulong], :bool
13
- attach_function :FileEncryptionStatusW, [:buffer_in, :pointer], :bool
14
- attach_function :GetAce, [:pointer, :ulong, :pointer], :bool
15
- attach_function :GetFileSecurityW, [:buffer_in, :ulong, :pointer, :ulong, :pointer], :bool
16
- attach_function :GetLengthSid, [:pointer], :ulong
17
- attach_function :GetSecurityDescriptorControl, [:pointer, :pointer, :pointer], :bool
18
- attach_function :GetSecurityDescriptorDacl, [:pointer, :pointer, :pointer, :pointer], :ulong
19
- attach_function :InitializeAcl, [:pointer, :ulong, :ulong], :bool
20
- attach_function :InitializeSecurityDescriptor, [:pointer, :ulong], :bool
21
- attach_function :LookupAccountNameW, [:buffer_in, :buffer_in, :pointer, :pointer, :pointer, :pointer, :pointer], :bool
22
- attach_function :LookupAccountSidW, [:buffer_in, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :bool
23
- attach_function :SetFileSecurityW, [:buffer_in, :ulong, :pointer], :bool
24
- attach_function :SetSecurityDescriptorDacl, [:pointer, :bool, :pointer, :bool], :bool
25
-
26
- ffi_lib :kernel32
27
-
28
- attach_function :GetVolumeInformationW,
29
- [:buffer_in, :buffer_out, :ulong, :pointer, :pointer, :pointer, :buffer_out, :ulong],
30
- :bool
31
-
32
- ffi_lib :shlwapi
33
-
34
- attach_function :PathStripToRootW, [:buffer_in], :bool
35
- attach_function :PathIsRootW, [:buffer_in], :bool
36
- end
37
- end
38
- end
39
-
40
- class String
41
- # Convenience method for converting strings to UTF-16LE for wide character
42
- # functions that require it.
43
- def wincode
44
- (self.tr(File::SEPARATOR, File::ALT_SEPARATOR) + 0.chr).encode('UTF-16LE')
45
- end
46
- end