win32-file-security 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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