win32-file-security 1.0.0
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 +2 -0
- data/MANIFEST +12 -0
- data/README +45 -0
- data/Rakefile +43 -0
- data/lib/win32/file/security.rb +470 -0
- data/lib/win32/file/windows/constants.rb +125 -0
- data/lib/win32/file/windows/functions.rb +46 -0
- data/lib/win32/file/windows/structs.rb +42 -0
- data/test/test_win32_file_security_encryption.rb +90 -0
- data/test/test_win32_file_security_permissions.rb +88 -0
- data/test/test_win32_file_security_version.rb +13 -0
- data/win32-file-security.gemspec +26 -0
- metadata +114 -0
data/CHANGES
ADDED
data/MANIFEST
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
* CHANGES
|
2
|
+
* MANIFEST
|
3
|
+
* Rakefile
|
4
|
+
* README
|
5
|
+
* win32-file-security.gemspec
|
6
|
+
* lib/win32/file/security.rb
|
7
|
+
* lib/win32/file/windows/constants.rb
|
8
|
+
* lib/win32/file/windows/functions.rb
|
9
|
+
* lib/win32/file/windows/structs.rb
|
10
|
+
* test/test_win32_file_security_encryption.rb
|
11
|
+
* test/test_win32_file_security_permissions.rb
|
12
|
+
* test/test_win32_file_security_version.rb
|
data/README
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
== Description
|
2
|
+
Additional methods for the File class that relate to file security on
|
3
|
+
the Microsoft Windows operating system.
|
4
|
+
|
5
|
+
== Prerequisites
|
6
|
+
* ffi
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
gem install win32-file-security
|
10
|
+
|
11
|
+
== Synopsis
|
12
|
+
|
13
|
+
require 'win32/file/security
|
14
|
+
|
15
|
+
p File.get_permissions('file.txt')
|
16
|
+
|
17
|
+
== Notes
|
18
|
+
If you have the win32-file gem already installed then you do not need this
|
19
|
+
gem. This library was split out from the win32-file gem in order to ease
|
20
|
+
testing and maintenance.
|
21
|
+
|
22
|
+
Otherwise, the only difference is that this library uses FFI instead
|
23
|
+
of win32-api. This also means that it works with JRuby.
|
24
|
+
|
25
|
+
== Known issues or bugs
|
26
|
+
None that I'm aware of.
|
27
|
+
|
28
|
+
Please report any issues you find on the github page at:
|
29
|
+
|
30
|
+
https://github.com/djberg96/win32-file-security/issues
|
31
|
+
|
32
|
+
== License
|
33
|
+
Artistic 2.0
|
34
|
+
|
35
|
+
== Copyright
|
36
|
+
(C) 2003-2012, Daniel J. Berger, All Rights Reserved
|
37
|
+
|
38
|
+
== Warranty
|
39
|
+
This package is provided "as is" and without any express or
|
40
|
+
implied warranties, including, without limitation, the implied
|
41
|
+
warranties of merchantability and fitness for a particular purpose.
|
42
|
+
|
43
|
+
== Authors
|
44
|
+
* Daniel J. Berger
|
45
|
+
* Park Heesob
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
CLEAN.include('**/*.gem', '**/*.rbc')
|
6
|
+
|
7
|
+
namespace :gem do
|
8
|
+
desc 'Build the win32-file-security gem'
|
9
|
+
task :create => [:clean] do
|
10
|
+
spec = eval(IO.read('win32-file-security.gemspec'))
|
11
|
+
Gem::Builder.new(spec).build
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Install the win32-file-security gem"
|
15
|
+
task :install => [:create] do
|
16
|
+
file = Dir["*.gem"].first
|
17
|
+
sh "gem install #{file}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace 'test' do
|
22
|
+
Rake::TestTask.new('all') do |t|
|
23
|
+
task :test => :clean
|
24
|
+
t.warning = true
|
25
|
+
t.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
Rake::TestTask.new('encryption') do |t|
|
29
|
+
task :test => :clean
|
30
|
+
t.warning = true
|
31
|
+
t.verbose = true
|
32
|
+
t.test_files = FileList['test/test_win32_file_security_encryption']
|
33
|
+
end
|
34
|
+
|
35
|
+
Rake::TestTask.new('permissions') do |t|
|
36
|
+
task :test => :clean
|
37
|
+
t.warning = true
|
38
|
+
t.verbose = true
|
39
|
+
t.test_files = FileList['test/test_win32_file_security_permissions']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => 'test:all'
|
@@ -0,0 +1,470 @@
|
|
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')
|
4
|
+
|
5
|
+
class File
|
6
|
+
include Windows::File::Constants
|
7
|
+
include Windows::File::Functions
|
8
|
+
extend Windows::File::Constants
|
9
|
+
extend Windows::File::Structs
|
10
|
+
extend Windows::File::Functions
|
11
|
+
|
12
|
+
# The version of the win32-file library
|
13
|
+
WIN32_FILE_SECURITY_VERSION = '1.0.0'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Returns the encryption status of a file as a string. Possible return
|
18
|
+
# values are:
|
19
|
+
#
|
20
|
+
# * encryptable
|
21
|
+
# * encrypted
|
22
|
+
# * readonly
|
23
|
+
# * root directory (i.e. not encryptable)
|
24
|
+
# * system fiel (i.e. not encryptable)
|
25
|
+
# * unsupported
|
26
|
+
# * unknown
|
27
|
+
#
|
28
|
+
def encryption_status(file)
|
29
|
+
wide_file = file.wincode
|
30
|
+
status_ptr = FFI::MemoryPointer.new(:ulong)
|
31
|
+
|
32
|
+
unless FileEncryptionStatusW(wide_file, status_ptr)
|
33
|
+
raise SystemCallError.new("FileEncryptionStatus", FFI.errno)
|
34
|
+
end
|
35
|
+
|
36
|
+
status = status_ptr.read_ulong
|
37
|
+
|
38
|
+
rvalue = case status
|
39
|
+
when FILE_ENCRYPTABLE
|
40
|
+
"encryptable"
|
41
|
+
when FILE_IS_ENCRYPTED
|
42
|
+
"encrypted"
|
43
|
+
when FILE_READ_ONLY
|
44
|
+
"readonly"
|
45
|
+
when FILE_ROOT_DIR
|
46
|
+
"root directory"
|
47
|
+
when FILE_SYSTEM_ATTR
|
48
|
+
"system file"
|
49
|
+
when FILE_SYSTEM_NOT_SUPPORTED
|
50
|
+
"unsupported"
|
51
|
+
else
|
52
|
+
"unknown"
|
53
|
+
end
|
54
|
+
|
55
|
+
rvalue
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns whether or not the root path of the specified file is
|
59
|
+
# encryptable. If a relative path is specified, it will check against
|
60
|
+
# the root of the current directory.
|
61
|
+
#
|
62
|
+
# Be sure to include a trailing slash if specifying a root path.
|
63
|
+
#
|
64
|
+
# Examples:
|
65
|
+
#
|
66
|
+
# p File.encryptable?
|
67
|
+
# p File.encryptable?("D:\\")
|
68
|
+
# p File.encryptable?("C:/foo/bar.txt") # Same as 'C:\'
|
69
|
+
#
|
70
|
+
def encryptable?(file = nil)
|
71
|
+
bool = false
|
72
|
+
flags_ptr = FFI::MemoryPointer.new(:ulong)
|
73
|
+
|
74
|
+
if file
|
75
|
+
file = File.expand_path(file)
|
76
|
+
wide_file = file.wincode
|
77
|
+
|
78
|
+
if !PathIsRootW(wide_file)
|
79
|
+
unless PathStripToRootW(wide_file)
|
80
|
+
raise SystemCallError.new("PathStripToRoot", FFI.errno)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
wide_file = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
unless GetVolumeInformationW(wide_file, nil, 0, nil, nil, flags_ptr, nil, 0)
|
88
|
+
raise SystemCallError.new("GetVolumeInformation", FFI.errno)
|
89
|
+
end
|
90
|
+
|
91
|
+
flags = flags_ptr.read_ulong
|
92
|
+
|
93
|
+
if flags & FILE_SUPPORTS_ENCRYPTION > 0
|
94
|
+
bool = true
|
95
|
+
end
|
96
|
+
|
97
|
+
bool
|
98
|
+
end
|
99
|
+
|
100
|
+
# Encrypts a file or directory. All data streams in a file are encrypted.
|
101
|
+
# All new files created in an encrypted directory are encrypted.
|
102
|
+
#
|
103
|
+
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
104
|
+
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
105
|
+
# rights.
|
106
|
+
#
|
107
|
+
# Requires exclusive access to the file being encrypted, and will fail if
|
108
|
+
# another process is using the file or the file is marked read-only. If the
|
109
|
+
# file is compressed the file will be decompressed before encrypting it.
|
110
|
+
#
|
111
|
+
def encrypt(file)
|
112
|
+
unless EncryptFileW(file.wincode)
|
113
|
+
raise SystemCallError.new("EncryptFile", FFI.errno)
|
114
|
+
end
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Decrypts an encrypted file or directory.
|
119
|
+
#
|
120
|
+
# The caller must have the FILE_READ_DATA, FILE_WRITE_DATA,
|
121
|
+
# FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, and SYNCHRONIZE access
|
122
|
+
# rights.
|
123
|
+
#
|
124
|
+
# Requires exclusive access to the file being decrypted, and will fail if
|
125
|
+
# another process is using the file. If the file is not encrypted an error
|
126
|
+
# is NOT raised, it's simply a no-op.
|
127
|
+
#
|
128
|
+
def decrypt(file)
|
129
|
+
unless DecryptFileW(file.wincode, 0)
|
130
|
+
raise SystemCallError.new("DecryptFile", FFI.errno)
|
131
|
+
end
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns a hash describing the current file permissions for the given
|
136
|
+
# file. The account name is the key, and the value is an integer mask
|
137
|
+
# that corresponds to the security permissions for that file.
|
138
|
+
#
|
139
|
+
# To get a human readable version of the permissions, pass the value to
|
140
|
+
# the +File.securities+ method.
|
141
|
+
#
|
142
|
+
# You may optionally specify a host as the second argument. If no host is
|
143
|
+
# specified then the current host is used.
|
144
|
+
#
|
145
|
+
# Examples:
|
146
|
+
#
|
147
|
+
# hash = File.get_permissions('test.txt')
|
148
|
+
#
|
149
|
+
# p hash # => {"NT AUTHORITY\\SYSTEM"=>2032127, "BUILTIN\\Administrators"=>2032127, ...}
|
150
|
+
#
|
151
|
+
# hash.each{ |name, mask|
|
152
|
+
# p name
|
153
|
+
# p File.securities(mask)
|
154
|
+
# }
|
155
|
+
#
|
156
|
+
def get_permissions(file, host=nil)
|
157
|
+
size_needed_ptr = FFI::MemoryPointer.new(:ulong)
|
158
|
+
security_ptr = FFI::MemoryPointer.new(:ulong)
|
159
|
+
|
160
|
+
wide_file = file.wincode
|
161
|
+
wide_host = host ? host.wincode : nil
|
162
|
+
|
163
|
+
# First pass, get the size needed
|
164
|
+
bool = GetFileSecurityW(
|
165
|
+
wide_file,
|
166
|
+
DACL_SECURITY_INFORMATION,
|
167
|
+
security_ptr,
|
168
|
+
security_ptr.size,
|
169
|
+
size_needed_ptr
|
170
|
+
)
|
171
|
+
|
172
|
+
errno = FFI.errno
|
173
|
+
|
174
|
+
if !bool && errno != ERROR_INSUFFICIENT_BUFFER
|
175
|
+
raise SystemCallError.new("GetFileSecurity", errno)
|
176
|
+
end
|
177
|
+
|
178
|
+
size_needed = size_needed_ptr.read_ulong
|
179
|
+
|
180
|
+
security_ptr = FFI::MemoryPointer.new(size_needed)
|
181
|
+
|
182
|
+
# Second pass, this time with the appropriately sized security pointer
|
183
|
+
bool = GetFileSecurityW(
|
184
|
+
wide_file,
|
185
|
+
DACL_SECURITY_INFORMATION,
|
186
|
+
security_ptr,
|
187
|
+
security_ptr.size,
|
188
|
+
size_needed_ptr
|
189
|
+
)
|
190
|
+
|
191
|
+
unless bool
|
192
|
+
raise SystemCallError.new("GetFileSecurity", FFI.errno)
|
193
|
+
end
|
194
|
+
|
195
|
+
control_ptr = FFI::MemoryPointer.new(:ulong)
|
196
|
+
revision_ptr = FFI::MemoryPointer.new(:ulong)
|
197
|
+
|
198
|
+
unless GetSecurityDescriptorControl(security_ptr, control_ptr, revision_ptr)
|
199
|
+
raise SystemCallError.new("GetSecurityDescriptorControl", FFI.errno)
|
200
|
+
end
|
201
|
+
|
202
|
+
control = control_ptr.read_ulong
|
203
|
+
|
204
|
+
if control & SE_DACL_PRESENT == 0
|
205
|
+
raise ArgumentError, "No DACL present: explicit deny all"
|
206
|
+
end
|
207
|
+
|
208
|
+
dacl_pptr = FFI::MemoryPointer.new(:pointer)
|
209
|
+
dacl_present_ptr = FFI::MemoryPointer.new(:bool)
|
210
|
+
dacl_defaulted_ptr = FFI::MemoryPointer.new(:ulong)
|
211
|
+
|
212
|
+
val = GetSecurityDescriptorDacl(
|
213
|
+
security_ptr,
|
214
|
+
dacl_present_ptr,
|
215
|
+
dacl_pptr,
|
216
|
+
dacl_defaulted_ptr
|
217
|
+
)
|
218
|
+
|
219
|
+
if val == 0
|
220
|
+
raise SystemCallError.new("GetSecurityDescriptorDacl", FFI.errno)
|
221
|
+
end
|
222
|
+
|
223
|
+
acl = ACL.new(dacl_pptr.read_pointer)
|
224
|
+
|
225
|
+
if acl[:AclRevision] == 0
|
226
|
+
raise ArgumentError, "DACL is NULL: implicit access grant"
|
227
|
+
end
|
228
|
+
|
229
|
+
ace_count = acl[:AceCount]
|
230
|
+
perms_hash = {}
|
231
|
+
|
232
|
+
0.upto(ace_count - 1){ |i|
|
233
|
+
ace_pptr = FFI::MemoryPointer.new(:pointer)
|
234
|
+
next unless GetAce(acl, i, ace_pptr)
|
235
|
+
|
236
|
+
access = ACCESS_ALLOWED_ACE.new(ace_pptr.read_pointer)
|
237
|
+
|
238
|
+
if access[:Header][:AceType] == ACCESS_ALLOWED_ACE_TYPE
|
239
|
+
name = FFI::MemoryPointer.new(:uchar, 260)
|
240
|
+
name_size = FFI::MemoryPointer.new(:ulong)
|
241
|
+
name_size.write_ulong(name.size)
|
242
|
+
|
243
|
+
domain = FFI::MemoryPointer.new(:uchar, 260)
|
244
|
+
domain_size = FFI::MemoryPointer.new(:ulong)
|
245
|
+
domain_size.write_ulong(domain.size)
|
246
|
+
|
247
|
+
use_ptr = FFI::MemoryPointer.new(:pointer)
|
248
|
+
|
249
|
+
val = LookupAccountSidW(
|
250
|
+
wide_host,
|
251
|
+
ace_pptr.read_pointer + 8,
|
252
|
+
name,
|
253
|
+
name_size,
|
254
|
+
domain,
|
255
|
+
domain_size,
|
256
|
+
use_ptr
|
257
|
+
)
|
258
|
+
|
259
|
+
if val == 0
|
260
|
+
raise SystemCallError.new("LookupAccountSid", FFI.errno)
|
261
|
+
end
|
262
|
+
|
263
|
+
# The x2 multiplier is necessary due to wide char strings.
|
264
|
+
name = name.read_string(name_size.read_ulong * 2).delete(0.chr)
|
265
|
+
domain = domain.read_string(domain_size.read_ulong * 2).delete(0.chr)
|
266
|
+
|
267
|
+
unless domain.empty?
|
268
|
+
name = domain + '\\' + name
|
269
|
+
end
|
270
|
+
|
271
|
+
perms_hash[name] = access[:Mask]
|
272
|
+
end
|
273
|
+
}
|
274
|
+
|
275
|
+
perms_hash
|
276
|
+
end
|
277
|
+
|
278
|
+
# Sets the file permissions for the given file name. The 'permissions'
|
279
|
+
# argument is a hash with an account name as the key, and the various
|
280
|
+
# permission constants as possible values. The possible constant values
|
281
|
+
# are:
|
282
|
+
#
|
283
|
+
# * FILE_READ_DATA
|
284
|
+
# * FILE_WRITE_DATA
|
285
|
+
# * FILE_APPEND_DATA
|
286
|
+
# * FILE_READ_EA
|
287
|
+
# * FILE_WRITE_EA
|
288
|
+
# * FILE_EXECUTE
|
289
|
+
# * FILE_DELETE_CHILD
|
290
|
+
# * FILE_READ_ATTRIBUTES
|
291
|
+
# * FILE_WRITE_ATTRIBUTES
|
292
|
+
# * STANDARD_RIGHTS_ALL
|
293
|
+
# * FULL
|
294
|
+
# * READ
|
295
|
+
# * ADD
|
296
|
+
# * CHANGE
|
297
|
+
# * DELETE
|
298
|
+
# * READ_CONTROL
|
299
|
+
# * WRITE_DAC
|
300
|
+
# * WRITE_OWNER
|
301
|
+
# * SYNCHRONIZE
|
302
|
+
# * STANDARD_RIGHTS_REQUIRED
|
303
|
+
# * STANDARD_RIGHTS_READ
|
304
|
+
# * STANDARD_RIGHTS_WRITE
|
305
|
+
# * STANDARD_RIGHTS_EXECUTE
|
306
|
+
# * STANDARD_RIGHTS_ALL
|
307
|
+
# * SPECIFIC_RIGHTS_ALL
|
308
|
+
# * ACCESS_SYSTEM_SECURITY
|
309
|
+
# * MAXIMUM_ALLOWED
|
310
|
+
# * GENERIC_READ
|
311
|
+
# * GENERIC_WRITE
|
312
|
+
# * GENERIC_EXECUTE
|
313
|
+
# * GENERIC_ALL
|
314
|
+
#
|
315
|
+
# Example:
|
316
|
+
#
|
317
|
+
# # Set locally
|
318
|
+
# File.set_permissions(file, "userid" => File::GENERIC_ALL)
|
319
|
+
#
|
320
|
+
# # Set a remote system
|
321
|
+
# File.set_permissions(file, "host\\userid" => File::GENERIC_ALL)
|
322
|
+
#
|
323
|
+
def set_permissions(file, perms)
|
324
|
+
raise TypeError unless file.is_a?(String)
|
325
|
+
raise TypeError unless perms.kind_of?(Hash)
|
326
|
+
|
327
|
+
wide_file = file.wincode
|
328
|
+
|
329
|
+
account_rights = 0
|
330
|
+
sec_desc = FFI::MemoryPointer.new(:pointer, SECURITY_DESCRIPTOR_MIN_LENGTH)
|
331
|
+
|
332
|
+
unless InitializeSecurityDescriptor(sec_desc, 1)
|
333
|
+
raise SystemCallError.new("InitializeSecurityDescriptor", FFI.errno)
|
334
|
+
end
|
335
|
+
|
336
|
+
acl_new = FFI::MemoryPointer.new(ACL, 100)
|
337
|
+
|
338
|
+
unless InitializeAcl(acl_new, acl_new.size, ACL_REVISION2)
|
339
|
+
raise SystemCallError.new("InitializeAcl", FFI.errno)
|
340
|
+
end
|
341
|
+
|
342
|
+
perms.each{ |account, mask|
|
343
|
+
next if mask.nil?
|
344
|
+
|
345
|
+
server, account = account.split("\\")
|
346
|
+
|
347
|
+
if ['BUILTIN', 'NT AUTHORITY'].include?(server.upcase)
|
348
|
+
wide_server = nil
|
349
|
+
else
|
350
|
+
wide_server = server.wincode
|
351
|
+
end
|
352
|
+
|
353
|
+
wide_account = account.wincode
|
354
|
+
|
355
|
+
sid = FFI::MemoryPointer.new(:uchar, 1024)
|
356
|
+
sid_size = FFI::MemoryPointer.new(:ulong)
|
357
|
+
sid_size.write_ulong(sid.size)
|
358
|
+
|
359
|
+
domain = FFI::MemoryPointer.new(:uchar, 260)
|
360
|
+
domain_size = FFI::MemoryPointer.new(:ulong)
|
361
|
+
domain_size.write_ulong(domain.size)
|
362
|
+
|
363
|
+
use_ptr = FFI::MemoryPointer.new(:ulong)
|
364
|
+
|
365
|
+
val = LookupAccountNameW(
|
366
|
+
wide_server,
|
367
|
+
wide_account,
|
368
|
+
sid,
|
369
|
+
sid_size,
|
370
|
+
domain,
|
371
|
+
domain_size,
|
372
|
+
use_ptr
|
373
|
+
)
|
374
|
+
|
375
|
+
raise SystemCallError.new("LookupAccountName", FFI.errno) unless val
|
376
|
+
|
377
|
+
all_ace = ACCESS_ALLOWED_ACE2.new
|
378
|
+
|
379
|
+
val = CopySid(
|
380
|
+
ALLOW_ACE_LENGTH - ACCESS_ALLOWED_ACE.size,
|
381
|
+
all_ace.to_ptr+8,
|
382
|
+
sid
|
383
|
+
)
|
384
|
+
|
385
|
+
raise SystemCallError.new("CopySid", FFI.errno) unless val
|
386
|
+
|
387
|
+
if (GENERIC_ALL & mask).nonzero?
|
388
|
+
account_rights = GENERIC_ALL & mask
|
389
|
+
elsif (GENERIC_RIGHTS_CHK & mask).nonzero?
|
390
|
+
account_rights = GENERIC_RIGHTS_MASK & mask
|
391
|
+
else
|
392
|
+
# Do nothing, leave it set to zero.
|
393
|
+
end
|
394
|
+
|
395
|
+
all_ace[:Header][:AceFlags] = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE
|
396
|
+
|
397
|
+
2.times{
|
398
|
+
if account_rights != 0
|
399
|
+
all_ace[:Header][:AceSize] = 8 + GetLengthSid(sid)
|
400
|
+
all_ace[:Mask] = account_rights
|
401
|
+
|
402
|
+
val = AddAce(
|
403
|
+
acl_new,
|
404
|
+
ACL_REVISION2,
|
405
|
+
MAXDWORD,
|
406
|
+
all_ace,
|
407
|
+
all_ace[:Header][:AceSize]
|
408
|
+
)
|
409
|
+
|
410
|
+
raise SystemCallError.new("AddAce", FFI.errno) unless val
|
411
|
+
|
412
|
+
all_ace[:Header][:AceFlags] = CONTAINER_INHERIT_ACE
|
413
|
+
else
|
414
|
+
all_ace[:Header][:AceFlags] = 0
|
415
|
+
end
|
416
|
+
|
417
|
+
account_rights = REST_RIGHTS_MASK & mask
|
418
|
+
}
|
419
|
+
}
|
420
|
+
|
421
|
+
unless SetSecurityDescriptorDacl(sec_desc, true, acl_new, false)
|
422
|
+
raise SystemCallError.new("SetSecurityDescriptorDacl", FFI.errno)
|
423
|
+
end
|
424
|
+
|
425
|
+
unless SetFileSecurityW(wide_file, DACL_SECURITY_INFORMATION, sec_desc)
|
426
|
+
raise SystemCallError.new("SetFileSecurity", FFI.errno)
|
427
|
+
end
|
428
|
+
|
429
|
+
self
|
430
|
+
end
|
431
|
+
|
432
|
+
# Returns an array of human-readable strings that correspond to the
|
433
|
+
# permission flags.
|
434
|
+
#
|
435
|
+
# Example:
|
436
|
+
#
|
437
|
+
# File.get_permissions('test.txt').each{ |name, mask|
|
438
|
+
# puts name
|
439
|
+
# p File.securities(mask)
|
440
|
+
# }
|
441
|
+
#
|
442
|
+
def securities(mask)
|
443
|
+
sec_array = []
|
444
|
+
|
445
|
+
security_rights = {
|
446
|
+
'FULL' => FULL,
|
447
|
+
'DELETE' => DELETE,
|
448
|
+
'READ' => READ,
|
449
|
+
'CHANGE' => CHANGE,
|
450
|
+
'ADD' => ADD
|
451
|
+
}
|
452
|
+
|
453
|
+
if mask == 0
|
454
|
+
sec_array.push('NONE')
|
455
|
+
else
|
456
|
+
if (mask & FULL) ^ FULL == 0
|
457
|
+
sec_array.push('FULL')
|
458
|
+
else
|
459
|
+
security_rights.each{ |string, numeric|
|
460
|
+
if (numeric & mask) ^ numeric == 0
|
461
|
+
sec_array.push(string)
|
462
|
+
end
|
463
|
+
}
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
sec_array
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Windows
|
2
|
+
module File
|
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
|
14
|
+
SECURITY_DESCRIPTOR_MIN_LENGTH = 20
|
15
|
+
|
16
|
+
## Security Rights
|
17
|
+
|
18
|
+
SYNCHRONIZE = 0x100000
|
19
|
+
STANDARD_RIGHTS_REQUIRED = 0xf0000
|
20
|
+
STANDARD_RIGHTS_READ = 0x20000
|
21
|
+
STANDARD_RIGHTS_WRITE = 0x20000
|
22
|
+
STANDARD_RIGHTS_EXECUTE = 0x20000
|
23
|
+
STANDARD_RIGHTS_ALL = 0x1F0000
|
24
|
+
SPECIFIC_RIGHTS_ALL = 0xFFFF
|
25
|
+
ACCESS_SYSTEM_SECURITY = 0x1000000
|
26
|
+
MAXIMUM_ALLOWED = 0x2000000
|
27
|
+
GENERIC_READ = 0x80000000
|
28
|
+
GENERIC_WRITE = 0x40000000
|
29
|
+
GENERIC_EXECUTE = 0x20000000
|
30
|
+
GENERIC_ALL = 0x10000000
|
31
|
+
GENERIC_RIGHTS_CHK = 0xF0000000
|
32
|
+
REST_RIGHTS_MASK = 0x001FFFFF
|
33
|
+
|
34
|
+
FILE_READ_DATA = 1
|
35
|
+
FILE_LIST_DIRECTORY = 1
|
36
|
+
FILE_WRITE_DATA = 2
|
37
|
+
FILE_ADD_FILE = 2
|
38
|
+
FILE_APPEND_DATA = 4
|
39
|
+
FILE_ADD_SUBDIRECTORY = 4
|
40
|
+
FILE_CREATE_PIPE_INSTANCE = 4
|
41
|
+
FILE_READ_EA = 8
|
42
|
+
FILE_READ_PROPERTIES = 8
|
43
|
+
FILE_WRITE_EA = 16
|
44
|
+
FILE_WRITE_PROPERTIES = 16
|
45
|
+
FILE_EXECUTE = 32
|
46
|
+
FILE_TRAVERSE = 32
|
47
|
+
FILE_DELETE_CHILD = 64
|
48
|
+
FILE_READ_ATTRIBUTES = 128
|
49
|
+
FILE_WRITE_ATTRIBUTES = 256
|
50
|
+
|
51
|
+
FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
|
52
|
+
|
53
|
+
FILE_GENERIC_READ =
|
54
|
+
STANDARD_RIGHTS_READ |
|
55
|
+
FILE_READ_DATA |
|
56
|
+
FILE_READ_ATTRIBUTES |
|
57
|
+
FILE_READ_EA |
|
58
|
+
SYNCHRONIZE
|
59
|
+
|
60
|
+
FILE_GENERIC_WRITE =
|
61
|
+
STANDARD_RIGHTS_WRITE |
|
62
|
+
FILE_WRITE_DATA |
|
63
|
+
FILE_WRITE_ATTRIBUTES |
|
64
|
+
FILE_WRITE_EA |
|
65
|
+
FILE_APPEND_DATA |
|
66
|
+
SYNCHRONIZE
|
67
|
+
|
68
|
+
FILE_GENERIC_EXECUTE =
|
69
|
+
STANDARD_RIGHTS_EXECUTE |
|
70
|
+
FILE_READ_ATTRIBUTES |
|
71
|
+
FILE_EXECUTE |
|
72
|
+
SYNCHRONIZE
|
73
|
+
|
74
|
+
FILE_SHARE_READ = 1
|
75
|
+
FILE_SHARE_WRITE = 2
|
76
|
+
FILE_SHARE_DELETE = 4
|
77
|
+
FILE_NOTIFY_CHANGE_FILE_NAME = 1
|
78
|
+
FILE_NOTIFY_CHANGE_DIR_NAME = 2
|
79
|
+
FILE_NOTIFY_CHANGE_ATTRIBUTES = 4
|
80
|
+
FILE_NOTIFY_CHANGE_SIZE = 8
|
81
|
+
FILE_NOTIFY_CHANGE_LAST_WRITE = 16
|
82
|
+
FILE_NOTIFY_CHANGE_LAST_ACCESS = 32
|
83
|
+
FILE_NOTIFY_CHANGE_CREATION = 64
|
84
|
+
FILE_NOTIFY_CHANGE_SECURITY = 256
|
85
|
+
FILE_CASE_SENSITIVE_SEARCH = 1
|
86
|
+
FILE_CASE_PRESERVED_NAMES = 2
|
87
|
+
FILE_UNICODE_ON_DISK = 4
|
88
|
+
FILE_PERSISTENT_ACLS = 8
|
89
|
+
FILE_FILE_COMPRESSION = 16
|
90
|
+
FILE_VOLUME_QUOTAS = 32
|
91
|
+
FILE_SUPPORTS_SPARSE_FILES = 64
|
92
|
+
FILE_SUPPORTS_REPARSE_POINTS = 128
|
93
|
+
FILE_SUPPORTS_REMOTE_STORAGE = 256
|
94
|
+
FILE_VOLUME_IS_COMPRESSED = 0x8000
|
95
|
+
FILE_SUPPORTS_OBJECT_IDS = 0x10000
|
96
|
+
FILE_SUPPORTS_ENCRYPTION = 0x20000
|
97
|
+
|
98
|
+
FILE_ENCRYPTABLE = 0
|
99
|
+
FILE_IS_ENCRYPTED = 1
|
100
|
+
FILE_ROOT_DIR = 3
|
101
|
+
FILE_SYSTEM_ATTR = 2
|
102
|
+
FILE_SYSTEM_DIR = 4
|
103
|
+
FILE_UNKNOWN = 5
|
104
|
+
FILE_SYSTEM_NOT_SUPPORT = 6
|
105
|
+
FILE_READ_ONLY = 8
|
106
|
+
|
107
|
+
# Read and execute privileges
|
108
|
+
READ = FILE_GENERIC_READ | FILE_EXECUTE
|
109
|
+
|
110
|
+
# Add privileges
|
111
|
+
ADD = 0x001201bf
|
112
|
+
|
113
|
+
# Delete privileges
|
114
|
+
DELETE = 0x00010000
|
115
|
+
|
116
|
+
# Generic write, generic read, execute and delete privileges
|
117
|
+
CHANGE = FILE_GENERIC_WRITE | FILE_GENERIC_READ | FILE_EXECUTE | DELETE
|
118
|
+
|
119
|
+
# Full security rights - read, write, append, execute, and delete.
|
120
|
+
FULL = STANDARD_RIGHTS_ALL | FILE_READ_DATA | FILE_WRITE_DATA |
|
121
|
+
FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_EXECUTE |
|
122
|
+
FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,46 @@
|
|
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
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Windows
|
4
|
+
module File
|
5
|
+
module Structs
|
6
|
+
class ACE_HEADER < FFI::Struct
|
7
|
+
layout(
|
8
|
+
:AceType, :uchar,
|
9
|
+
:AceFlags, :uchar,
|
10
|
+
:AceSize, :ushort
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
class ACCESS_ALLOWED_ACE < FFI::Struct
|
15
|
+
layout(
|
16
|
+
:Header, ACE_HEADER,
|
17
|
+
:Mask, :ulong,
|
18
|
+
:SidStart, :ulong
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
class ACCESS_ALLOWED_ACE2 < FFI::Struct
|
23
|
+
layout(
|
24
|
+
:Header, ACE_HEADER,
|
25
|
+
:Mask, :ulong,
|
26
|
+
:SidStart, :ulong,
|
27
|
+
:dummy, [:uchar, 40]
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
class ACL < FFI::Struct
|
32
|
+
layout(
|
33
|
+
:AclRevision, :uchar,
|
34
|
+
:Sbz1, :uchar,
|
35
|
+
:AclSize, :ushort,
|
36
|
+
:AceCount, :ushort,
|
37
|
+
:Sbz2, :ushort
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# test_win32_file_encryption.rb
|
3
|
+
#
|
4
|
+
# Test case for the encryption related methods of win32-file. You should
|
5
|
+
# run this test via the 'rake test' or 'rake test_encryption' task.
|
6
|
+
#
|
7
|
+
# Note: These tests may fail based on the security setup of your system.
|
8
|
+
#############################################################################
|
9
|
+
require 'test-unit'
|
10
|
+
require 'win32/security'
|
11
|
+
require 'win32/file/security'
|
12
|
+
require 'socket'
|
13
|
+
|
14
|
+
class TC_Win32_File_Security_Encryption < Test::Unit::TestCase
|
15
|
+
def self.startup
|
16
|
+
Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
|
17
|
+
@@file = File.join(Dir.pwd, 'encryption_test.txt')
|
18
|
+
File.open(@@file, 'w'){ |fh| fh.puts "This is an encryption test." }
|
19
|
+
@@host = Socket.gethostname
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup
|
23
|
+
@msg = '=> Ignore. May not work due to security setup of your system.'
|
24
|
+
@elevated = Win32::Security.elevated_security?
|
25
|
+
@statuses = ['encrypted', 'encryptable', 'unknown']
|
26
|
+
end
|
27
|
+
|
28
|
+
test "encrypt method basic functionality" do
|
29
|
+
omit_unless(@elevated)
|
30
|
+
assert_respond_to(File, :encrypt)
|
31
|
+
assert_nothing_raised(@msg){ File.encrypt(@@file) }
|
32
|
+
end
|
33
|
+
|
34
|
+
test "encrypt requires one argument" do
|
35
|
+
omit_unless(@elevated)
|
36
|
+
assert_raise(ArgumentError){ File.encrypt }
|
37
|
+
assert_raise(ArgumentError){ File.encrypt(@@file, @@file) }
|
38
|
+
end
|
39
|
+
|
40
|
+
test "encrypt requires a string argument" do
|
41
|
+
omit_unless(@elevated)
|
42
|
+
assert_raise(TypeError, NoMethodError){ File.encrypt(1) }
|
43
|
+
end
|
44
|
+
|
45
|
+
test "decrypt method basic functionality" do
|
46
|
+
omit_unless(@elevated)
|
47
|
+
assert_respond_to(File, :decrypt)
|
48
|
+
assert_nothing_raised(@msg){ File.decrypt(@@file) }
|
49
|
+
end
|
50
|
+
|
51
|
+
test "decrypt accepts a single argument only" do
|
52
|
+
omit_unless(@elevated)
|
53
|
+
assert_raise(ArgumentError){ File.decrypt }
|
54
|
+
end
|
55
|
+
|
56
|
+
test "decrypt requires a string argument" do
|
57
|
+
omit_unless(@elevated)
|
58
|
+
assert_raise(TypeError, NoMethodError){ File.decrypt(1) }
|
59
|
+
end
|
60
|
+
|
61
|
+
test "encryptable? basic functionality" do
|
62
|
+
assert_respond_to(File, :encryptable?)
|
63
|
+
end
|
64
|
+
|
65
|
+
test "encryptable? returns a boolean value" do
|
66
|
+
assert_boolean(File.encryptable?("C:\\"))
|
67
|
+
end
|
68
|
+
|
69
|
+
test "encryption_status basic functionality" do
|
70
|
+
assert_respond_to(File, :encryption_status)
|
71
|
+
end
|
72
|
+
|
73
|
+
test "encryption_status returns the expected result" do
|
74
|
+
status = File.encryption_status(@@file)
|
75
|
+
assert_kind_of(String, status)
|
76
|
+
assert_true(@statuses.include?(status))
|
77
|
+
end
|
78
|
+
|
79
|
+
def teardown
|
80
|
+
@msg = nil
|
81
|
+
@statuses = nil
|
82
|
+
@elevated = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.shutdown
|
86
|
+
File.delete(@@file) if File.exists?(@@file)
|
87
|
+
@@file = nil
|
88
|
+
@@host = nil
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
##############################################################################
|
2
|
+
# test_win32_file_permissions.rb
|
3
|
+
#
|
4
|
+
# Test case for permission related methods of win32-file-security. You should
|
5
|
+
# use the 'rake test' or 'rake test:perms' task to run this.
|
6
|
+
##############################################################################
|
7
|
+
require 'test-unit'
|
8
|
+
require 'test/unit'
|
9
|
+
require 'win32/file/security'
|
10
|
+
require 'socket'
|
11
|
+
require 'etc'
|
12
|
+
|
13
|
+
class TC_Win32_File_Security_Permissions < Test::Unit::TestCase
|
14
|
+
def self.startup
|
15
|
+
@@user = Etc.getlogin
|
16
|
+
@@host = Socket.gethostname
|
17
|
+
@@file = File.join(Dir.pwd, 'security_test.txt')
|
18
|
+
|
19
|
+
Dir.chdir(File.dirname(File.expand_path(File.basename(__FILE__))))
|
20
|
+
File.open(@@file, 'w'){ |fh| fh.puts "This is a security test." }
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup
|
24
|
+
@perms = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
test "get_permissions basic functionality" do
|
28
|
+
assert_respond_to(File, :get_permissions)
|
29
|
+
assert_nothing_raised{ File.get_permissions(@@file) }
|
30
|
+
end
|
31
|
+
|
32
|
+
test "get_permissions returns a hash" do
|
33
|
+
assert_kind_of(Hash, File.get_permissions(@@file))
|
34
|
+
end
|
35
|
+
|
36
|
+
test "get_permissions accepts an optional hostname argument" do
|
37
|
+
assert_nothing_raised{ File.get_permissions(@@file, @@host) }
|
38
|
+
end
|
39
|
+
|
40
|
+
test "get_permissions requires at least one argument" do
|
41
|
+
assert_raise(ArgumentError){ File.get_permissions }
|
42
|
+
end
|
43
|
+
|
44
|
+
test "set_permissions basic functionality" do
|
45
|
+
assert_respond_to(File, :set_permissions)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "set_permissions works as expected" do
|
49
|
+
assert_nothing_raised{ @perms = File.get_permissions(@@file) }
|
50
|
+
assert_nothing_raised{ File.set_permissions(@@file, @perms) }
|
51
|
+
assert_equal(@perms, File.get_permissions(@@file))
|
52
|
+
end
|
53
|
+
|
54
|
+
test "set_permissions works if host is specified" do
|
55
|
+
@perms = {"#{@@host}\\#{@@user}" => File::GENERIC_ALL}
|
56
|
+
assert_nothing_raised{ File.set_permissions(@@file, @perms) }
|
57
|
+
assert_equal(@perms, File.get_permissions(@@file))
|
58
|
+
end
|
59
|
+
|
60
|
+
test "securities method basic functionality" do
|
61
|
+
assert_respond_to(File, :securities)
|
62
|
+
end
|
63
|
+
|
64
|
+
test "securities method works as expected" do
|
65
|
+
@perms = File.get_permissions(@@file)
|
66
|
+
|
67
|
+
@perms.each{ |acct, mask|
|
68
|
+
assert_nothing_raised{ File.securities(mask) }
|
69
|
+
assert_kind_of(Array, File.securities(mask))
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
test "securities method accepts a single argument only" do
|
74
|
+
assert_raise(ArgumentError){ File.securities }
|
75
|
+
assert_raise(ArgumentError){ File.securities({}, {}) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def teardown
|
79
|
+
@perms = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.shutdown
|
83
|
+
File.delete(@@file) if File.exists?(@@file)
|
84
|
+
@@file = nil
|
85
|
+
@@host = nil
|
86
|
+
@@user = nil
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#############################################################################
|
2
|
+
# test_win32_file_security_version.rb
|
3
|
+
#
|
4
|
+
# Just a test for the version of the library.
|
5
|
+
#############################################################################
|
6
|
+
require 'test-unit'
|
7
|
+
require 'win32/file/security'
|
8
|
+
|
9
|
+
class TC_Win32_File_Security_Version < Test::Unit::TestCase
|
10
|
+
test "version is set to expected value" do
|
11
|
+
assert_equal('1.0.0', File::WIN32_FILE_SECURITY_VERSION)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'win32-file-security'
|
5
|
+
spec.version = '1.0.0'
|
6
|
+
spec.authors = ['Daniel J. Berger', 'Park Heesob']
|
7
|
+
spec.license = 'Artistic 2.0'
|
8
|
+
spec.email = 'djberg96@gmail.com'
|
9
|
+
spec.homepage = 'http://github.com/djberg96/win32-file-security'
|
10
|
+
spec.summary = 'File security methods for the File class on MS Windows'
|
11
|
+
spec.test_files = Dir['test/test*']
|
12
|
+
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
13
|
+
|
14
|
+
spec.rubyforge_project = 'win32utils'
|
15
|
+
spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
|
16
|
+
|
17
|
+
spec.add_dependency('ffi')
|
18
|
+
spec.add_development_dependency('test-unit')
|
19
|
+
spec.add_development_dependency('win32-security')
|
20
|
+
|
21
|
+
spec.description = <<-EOF
|
22
|
+
The win32-file-security library adds security related methods to the
|
23
|
+
core File class for MS Windows. This includes the ability to get or
|
24
|
+
set permissions, as well as encrypt or decrypt files.
|
25
|
+
EOF
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: win32-file-security
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel J. Berger
|
9
|
+
- Park Heesob
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-12-19 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: test-unit
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: win32-security
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
description: ! " The win32-file-security library adds security related methods
|
64
|
+
to the\n core File class for MS Windows. This includes the ability to get or\n
|
65
|
+
\ set permissions, as well as encrypt or decrypt files.\n"
|
66
|
+
email: djberg96@gmail.com
|
67
|
+
executables: []
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files:
|
70
|
+
- README
|
71
|
+
- CHANGES
|
72
|
+
- MANIFEST
|
73
|
+
files:
|
74
|
+
- CHANGES
|
75
|
+
- 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
|
+
- MANIFEST
|
80
|
+
- Rakefile
|
81
|
+
- README
|
82
|
+
- test/test_win32_file_security_encryption.rb
|
83
|
+
- test/test_win32_file_security_permissions.rb
|
84
|
+
- test/test_win32_file_security_version.rb
|
85
|
+
- win32-file-security.gemspec
|
86
|
+
homepage: http://github.com/djberg96/win32-file-security
|
87
|
+
licenses:
|
88
|
+
- Artistic 2.0
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project: win32utils
|
107
|
+
rubygems_version: 1.8.24
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: File security methods for the File class on MS Windows
|
111
|
+
test_files:
|
112
|
+
- test/test_win32_file_security_encryption.rb
|
113
|
+
- test/test_win32_file_security_permissions.rb
|
114
|
+
- test/test_win32_file_security_version.rb
|