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