osx-acl 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +7 -0
- data/bin/acl_tool +145 -0
- data/lib/acl/assignment.rb +16 -0
- data/lib/acl/entry.rb +53 -0
- data/lib/acl/version.rb +5 -0
- data/lib/acl.rb +137 -0
- data/osx-acl.gemspec +27 -0
- data/spec/acl/assignment_spec.rb +43 -0
- data/spec/acl/entry_spec.rb +28 -0
- data/spec/acl_spec.rb +126 -0
- data/spec/spec_helper.rb +2 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 125c0e75690ae256728513e5372e43e98b51fc20
|
4
|
+
data.tar.gz: 853389a6f082a954d68e17f748f31b736b1652f5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 95bb8e5dcb64bdc878f842112685d09ed66734b8bccce8a0738d9e0bb16e037c97f2171c488dfcfb33a7856f036dbcf13cb5d58e0ef6fa9c19595f13d7c2b9b9
|
7
|
+
data.tar.gz: 585d3768458692b8c54637fa3e116b63807f773bf778fd870ec5b18387b74540c19ab19b848a92a8d3f41256b4abf18253d356d1f87aedaf534cbc5c934e8972
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Kyle Crawford
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# osx-acl
|
2
|
+
|
3
|
+
Tool and Ruby Library for managing ACLs on OS X
|
4
|
+
|
5
|
+
The acl_tool can be used to report on and remove orphaned or user-level entries.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'osx-acl'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install osx-acl
|
22
|
+
|
23
|
+
## Building
|
24
|
+
|
25
|
+
`
|
26
|
+
rake build
|
27
|
+
fpm -s gem -t osxpkg --osxpkg-identifier-prefix org.rubygems.kcrawford
|
28
|
+
pkg/osx-acl-1.x.gem`
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
From the acl_tool
|
33
|
+
|
34
|
+
```
|
35
|
+
Usage: /usr/bin/acl_tool [OPTIONS] path
|
36
|
+
|
37
|
+
Options
|
38
|
+
--dry-run outputs what would be done without modifying ACLs
|
39
|
+
--exclude x,y,z users to exclude from --remove-user-entries
|
40
|
+
--remove-orphans removes orphaned acl entries
|
41
|
+
--remove-user-entries removes user acl entries
|
42
|
+
--report report existing ACLs
|
43
|
+
--version outputs version information for this tool
|
44
|
+
--help outputs help information for this tool
|
45
|
+
```
|
46
|
+
|
47
|
+
From ruby
|
48
|
+
```
|
49
|
+
>> require 'acl'
|
50
|
+
=> true
|
51
|
+
>> acl = OSX::ACL.of("tmp")
|
52
|
+
=> #<OSX::ACL:0x007f92eaabc578 @path="tmp">
|
53
|
+
>> acl.entries
|
54
|
+
=> [#<OSX::ACL::Entry:0x007f92eaaf7510 @components=["user", "FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000046", "_www", "70", "allow", "read"]>]
|
55
|
+
>> ace = acl.entries.first
|
56
|
+
=> #<OSX::ACL::Entry:0x007f92eaaf7510 @components=["user", "FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000046", "_www", "70", "allow", "read"]>
|
57
|
+
>> ace.assignment
|
58
|
+
=> #<OSX::ACL::Assignment:0x007f92ea2a0060 @type="user", @uuid="FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000046", @name="_www", @id="70">
|
59
|
+
>> ace.assignment.type
|
60
|
+
=> "user"
|
61
|
+
>> ace.assignment.name
|
62
|
+
=> "_www"
|
63
|
+
>> ace.rules
|
64
|
+
=> ["allow"]
|
65
|
+
>> ace.permissions
|
66
|
+
=> ["read"]
|
67
|
+
>> acl.remove_entry_at_index(0)
|
68
|
+
chmod -a# 0 tmp # user:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000046:_www:70:allow:read
|
69
|
+
=> true
|
70
|
+
```
|
71
|
+
|
72
|
+
## Contributing
|
73
|
+
|
74
|
+
1. Fork it ( https://github.com/[my-github-username]/osx-acl/fork )
|
75
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
76
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
77
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
78
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/acl_tool
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'acl'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
def process_recursive(entry, &block)
|
8
|
+
yield(entry)
|
9
|
+
process_directory(entry) {|next_entry| process_recursive(next_entry,&block) } if File.directory?(entry)
|
10
|
+
end
|
11
|
+
|
12
|
+
def process_directory(entry)
|
13
|
+
Dir.entries(entry).each do |sub_entry|
|
14
|
+
next if [".",".."].include?(sub_entry)
|
15
|
+
yield(File.join(entry, sub_entry))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def main
|
20
|
+
options = {:exclude => []}
|
21
|
+
|
22
|
+
opt_parser = OptionParser.new do |opt|
|
23
|
+
opt.banner = "Usage: #{$0} [OPTIONS] path"
|
24
|
+
|
25
|
+
opt.separator ""
|
26
|
+
opt.separator "Options"
|
27
|
+
|
28
|
+
opt.on("--dry-run", "outputs what would be done without modifying ACLs") do
|
29
|
+
ENV['OSX_ACL_NOOP'] = "yes"
|
30
|
+
end
|
31
|
+
|
32
|
+
opt.on("--exclude x,y,z", Array, "users to exclude from --remove-user-entries") do |exclusions|
|
33
|
+
options[:exclude] = exclusions.map(&:downcase)
|
34
|
+
end
|
35
|
+
|
36
|
+
opt.on("--remove-orphans", "removes orphaned acl entries") do
|
37
|
+
options[:action] = "remove_orphans"
|
38
|
+
end
|
39
|
+
|
40
|
+
opt.on("--remove-user-entries", "removes user acl entries") do
|
41
|
+
options[:action] = "remove_user_entries"
|
42
|
+
end
|
43
|
+
|
44
|
+
opt.on("--report", "report existing ACLs") do
|
45
|
+
options[:action] = "report"
|
46
|
+
end
|
47
|
+
|
48
|
+
opt.on("--version", "outputs version information for this tool") do
|
49
|
+
options[:action] = "version"
|
50
|
+
end
|
51
|
+
|
52
|
+
opt.on("--help", "outputs help information for this tool") do
|
53
|
+
options[:action] = "help"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
opt_parser.parse!
|
59
|
+
|
60
|
+
if ARGV[0] && !File.exist?(ARGV[0])
|
61
|
+
puts opt_parser
|
62
|
+
exit 0
|
63
|
+
end
|
64
|
+
case options[:action]
|
65
|
+
when "version"
|
66
|
+
puts OSX::ACL::VERSION
|
67
|
+
when "help"
|
68
|
+
puts opt_parser
|
69
|
+
when "remove_orphans"
|
70
|
+
process_recursive(ARGV[0]) do |entry|
|
71
|
+
number_of_aces_removed = OSX::ACL.of(entry).remove_orphans!
|
72
|
+
if number_of_aces_removed > 0
|
73
|
+
puts "-#{number_of_aces_removed}: #{File.absolute_path(entry)}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
when "remove_user_entries"
|
77
|
+
process_recursive(ARGV[0]) do |entry|
|
78
|
+
begin
|
79
|
+
OSX::ACL.of(entry).entries.remove_if do |ace|
|
80
|
+
assignment = ace.assignment
|
81
|
+
assignment.type == "user" && !options[:exclude].include?(assignment.name.downcase)
|
82
|
+
end
|
83
|
+
rescue
|
84
|
+
$stderr.puts "### Failed to remove ace for #{entry}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
when "report"
|
88
|
+
path_count = 0
|
89
|
+
ace_count = 0
|
90
|
+
orphan_count = 0
|
91
|
+
explicit_paths = Set.new
|
92
|
+
user_acl_paths = Set.new
|
93
|
+
no_acl_paths = Set.new
|
94
|
+
process_recursive(ARGV[0]) do |path|
|
95
|
+
path_count += 1
|
96
|
+
acl = OSX::ACL.of(path)
|
97
|
+
entries = acl.entries
|
98
|
+
ace_count += entries.length
|
99
|
+
orphan_count += acl.orphans.length
|
100
|
+
if acl.entries.empty?
|
101
|
+
no_acl_paths << path
|
102
|
+
else
|
103
|
+
entries.each do |entry|
|
104
|
+
if entry.assignment.type == 'user'
|
105
|
+
user_acl_paths << path
|
106
|
+
end
|
107
|
+
if !entry.inherited?
|
108
|
+
explicit_paths << path
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
# It should output stats:
|
115
|
+
# total files
|
116
|
+
# total aces
|
117
|
+
# total orphans
|
118
|
+
# total explicit
|
119
|
+
# total without acl
|
120
|
+
# list of explicit
|
121
|
+
# list of paths without an acl
|
122
|
+
|
123
|
+
output = %{
|
124
|
+
Total paths: #{path_count}
|
125
|
+
Total aces: #{ace_count}
|
126
|
+
Total orphans: #{orphan_count}
|
127
|
+
Total explicit: #{explicit_paths.length}
|
128
|
+
Total without acl: #{no_acl_paths.length}
|
129
|
+
Total with user level: #{user_acl_paths.length}
|
130
|
+
List of explicit:
|
131
|
+
#{explicit_paths.to_a.join("\n")}
|
132
|
+
List of paths without an acl:
|
133
|
+
#{no_acl_paths.to_a.join("\n")}
|
134
|
+
List of user aces:
|
135
|
+
#{user_acl_paths.to_a.join("\n")}
|
136
|
+
}
|
137
|
+
puts output
|
138
|
+
|
139
|
+
else
|
140
|
+
puts opt_parser
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
main()
|
145
|
+
|
data/lib/acl/entry.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'acl/assignment'
|
2
|
+
module OSX
|
3
|
+
class ACL
|
4
|
+
class Entry
|
5
|
+
include Comparable
|
6
|
+
attr_accessor :components
|
7
|
+
|
8
|
+
def self.from_text(text)
|
9
|
+
new.tap {|entry| entry.components = text.split(":") }
|
10
|
+
end
|
11
|
+
|
12
|
+
def <=>(other)
|
13
|
+
components <=> other.components
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
components.join(":")
|
18
|
+
end
|
19
|
+
|
20
|
+
def inherited=(should_inherit)
|
21
|
+
if should_inherit && !inherited?
|
22
|
+
rules << "inherited"
|
23
|
+
elsif inherited? && !should_inherit
|
24
|
+
rules.delete("inherited")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def inherited?
|
29
|
+
rules.include? "inherited"
|
30
|
+
end
|
31
|
+
|
32
|
+
def orphaned?
|
33
|
+
assignment.orphan?
|
34
|
+
end
|
35
|
+
|
36
|
+
def assignment
|
37
|
+
ACL::Assignment.new(assignment_components)
|
38
|
+
end
|
39
|
+
|
40
|
+
def assignment_components
|
41
|
+
components[0..-3]
|
42
|
+
end
|
43
|
+
|
44
|
+
def permissions
|
45
|
+
components.last.split(",")
|
46
|
+
end
|
47
|
+
|
48
|
+
def rules
|
49
|
+
components[-2].split(",")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/acl/version.rb
ADDED
data/lib/acl.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require "acl/version"
|
2
|
+
require "acl/entry"
|
3
|
+
require 'open3'
|
4
|
+
require 'delegate'
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
module OSX
|
8
|
+
|
9
|
+
require 'ffi'
|
10
|
+
module API
|
11
|
+
extend FFI::Library
|
12
|
+
ffi_lib FFI::Library::LIBC
|
13
|
+
attach_function :acl_get_fd, [:int], :pointer
|
14
|
+
attach_function :acl_to_text, [:pointer, :pointer], :pointer
|
15
|
+
attach_function :acl_valid, [:pointer], :int
|
16
|
+
attach_function :acl_free, [:pointer], :int
|
17
|
+
end
|
18
|
+
|
19
|
+
class ACL
|
20
|
+
|
21
|
+
attr_accessor :path, :entries
|
22
|
+
|
23
|
+
def self.of(path)
|
24
|
+
new.tap {|acl| acl.path = path }
|
25
|
+
end
|
26
|
+
|
27
|
+
def entries
|
28
|
+
@entries ||= make_entries
|
29
|
+
end
|
30
|
+
|
31
|
+
def make_entries
|
32
|
+
Entries.new(self, entry_lines.map {|line| ACL::Entry.from_text(line) })
|
33
|
+
end
|
34
|
+
|
35
|
+
class Entries < SimpleDelegator
|
36
|
+
attr_reader :acl
|
37
|
+
def initialize(acl, entries)
|
38
|
+
@acl = acl
|
39
|
+
super(entries)
|
40
|
+
end
|
41
|
+
def as_inherited
|
42
|
+
map {|entry| entry.inherited = true }
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_if
|
46
|
+
removal_count = 0
|
47
|
+
# we reverse the order of the entries so we can remove entries without affecting the index of other entries
|
48
|
+
reverse.each_with_index do |entry,index|
|
49
|
+
if yield(entry)
|
50
|
+
# since entries are reversed, we calculate the actual index
|
51
|
+
actual_index = (length - 1) - index
|
52
|
+
if acl.remove_entry_at_index(actual_index)
|
53
|
+
removal_count += 1
|
54
|
+
else
|
55
|
+
raise "Failed to remove #{entry} from #{acl.path}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
removal_count
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def entry_lines
|
64
|
+
file_descriptor, acl_text_ptr, acl_ptr = nil
|
65
|
+
begin
|
66
|
+
file_descriptor = File.open(path, "r")
|
67
|
+
rescue Errno::ELOOP
|
68
|
+
return []
|
69
|
+
rescue Errno::EOPNOTSUPP
|
70
|
+
return []
|
71
|
+
rescue Errno::ENOENT
|
72
|
+
return []
|
73
|
+
end
|
74
|
+
acl_ptr = api.acl_get_fd(file_descriptor.fileno)
|
75
|
+
acl_text_ptr = api.acl_to_text(acl_ptr, nil)
|
76
|
+
return [] if acl_text_ptr.null?
|
77
|
+
ace_lines = acl_text_ptr.read_string.split("\n")[1..-1]
|
78
|
+
ace_lines
|
79
|
+
ensure
|
80
|
+
api.acl_free(acl_text_ptr)
|
81
|
+
api.acl_free(acl_ptr)
|
82
|
+
file_descriptor.close if file_descriptor
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove_orphans!
|
86
|
+
entries.remove_if {|entry| entry.orphaned? }
|
87
|
+
end
|
88
|
+
|
89
|
+
def orphans
|
90
|
+
entries.select {|e| e.orphaned? }
|
91
|
+
end
|
92
|
+
|
93
|
+
def file_flags
|
94
|
+
flags = ""
|
95
|
+
Open3.popen3("stat", "-f", "%f", path) do |stdin,stdout,stderr,thread|
|
96
|
+
flags = stdout.read
|
97
|
+
end
|
98
|
+
flags.to_i.to_s(8)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Wraps a file action
|
102
|
+
# first removes file flags that would cause the action to fail
|
103
|
+
# then yields to the block to perform the action
|
104
|
+
# then restores the flags
|
105
|
+
def preserving_flags
|
106
|
+
original_file_flags = file_flags
|
107
|
+
if original_file_flags == "0"
|
108
|
+
yield
|
109
|
+
else
|
110
|
+
begin
|
111
|
+
system("chflags", "0", path)
|
112
|
+
yield
|
113
|
+
ensure
|
114
|
+
system("chflags", original_file_flags, path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def remove_entry_at_index(index)
|
120
|
+
args = ["chmod", "-a#", index.to_s, path]
|
121
|
+
puts "#{args[0]} #{args[1]} #{args[2]} #{Shellwords.escape(args[3])} # #{entries[index].to_s}"
|
122
|
+
if ENV['OSX_ACL_NOOP'] == "yes"
|
123
|
+
true
|
124
|
+
else
|
125
|
+
preserving_flags do
|
126
|
+
system(*args)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def api
|
132
|
+
API
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/osx-acl.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'acl/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "osx-acl"
|
8
|
+
spec.version = OSX::ACL::VERSION
|
9
|
+
spec.authors = ["Kyle Crawford"]
|
10
|
+
spec.email = ["kcrwfrd@gmail.com"]
|
11
|
+
spec.summary = %q{OS X ACL reading and manipulation}
|
12
|
+
spec.description = %q{OS X ACL reading and munipluation using ffi and C acl API}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "ffi"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
include OSX
|
4
|
+
require 'acl/assignment'
|
5
|
+
|
6
|
+
def sample_assignment
|
7
|
+
ACL::Assignment.new(["group", "ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000046", "_www", "70"])
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ACL::Assignment do
|
11
|
+
describe '.new' do
|
12
|
+
it "instantiates with array of string components" do
|
13
|
+
sample_assignment
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a type" do
|
18
|
+
expect(sample_assignment.type).to eq("group")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has a uuid" do
|
22
|
+
expect(sample_assignment.uuid).to eq("ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000046")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has a name" do
|
26
|
+
expect(sample_assignment.name).to eq("_www")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "has an id" do
|
30
|
+
expect(sample_assignment.id).to eq("70")
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#orphan?" do
|
34
|
+
it "is true when name is blank" do
|
35
|
+
orphaned_assignment = sample_assignment
|
36
|
+
orphaned_assignment.name = ""
|
37
|
+
expect(orphaned_assignment.orphan?).to eq(true)
|
38
|
+
end
|
39
|
+
it "is false when name is not blank" do
|
40
|
+
expect(sample_assignment.orphan?).to eq(false)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
include OSX
|
4
|
+
require 'acl/entry'
|
5
|
+
|
6
|
+
def valid_acl
|
7
|
+
ACL::Entry.from_text("group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000046:_www:70:allow,inherited:read,write")
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ACL::Entry do
|
11
|
+
describe '.from_text' do
|
12
|
+
it "instantiates with text" do
|
13
|
+
expect(ACL::Entry.from_text("doh")).to be_kind_of(ACL::Entry)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has an assignment" do
|
18
|
+
expect(valid_acl).to respond_to(:assignment)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "has rules" do
|
22
|
+
expect(valid_acl.rules).to eq(["allow", "inherited"])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has permissions" do
|
26
|
+
expect(valid_acl.permissions).to eq(["read","write"])
|
27
|
+
end
|
28
|
+
end
|
data/spec/acl_spec.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include OSX
|
4
|
+
DIR_WITH_VALID_ACES = 'tmp/dir_with_two_aces'
|
5
|
+
DIR_WITH_ORPHAN_ACES = 'tmp/dir_with_orphan_aces'
|
6
|
+
LOCKED_DIR_WITH_ORPHANS = '/tmp/locked_dir_with_orphans'
|
7
|
+
|
8
|
+
def make_dir_with_two_aces
|
9
|
+
puts "creating dir with two aces..."
|
10
|
+
system(
|
11
|
+
"
|
12
|
+
rm -Rf 'tmp/dir_with_two_aces';
|
13
|
+
mkdir -p 'tmp/dir_with_two_aces';
|
14
|
+
chmod +a 'group:staff allow read' 'tmp/dir_with_two_aces';
|
15
|
+
chmod +a 'group:_www allow read' 'tmp/dir_with_two_aces';
|
16
|
+
"
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def make_locked_dir_with_orphans
|
21
|
+
create_acl_with_orphans
|
22
|
+
puts "creating locked dir with orphans..."
|
23
|
+
system(
|
24
|
+
"
|
25
|
+
chflags -R 0 '#{LOCKED_DIR_WITH_ORPHANS}';
|
26
|
+
rm -Rf '#{LOCKED_DIR_WITH_ORPHANS}';
|
27
|
+
mv '#{DIR_WITH_ORPHAN_ACES}' '#{LOCKED_DIR_WITH_ORPHANS}';
|
28
|
+
chflags uchg '#{LOCKED_DIR_WITH_ORPHANS}';
|
29
|
+
"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_acl_with_orphans
|
34
|
+
make_dir_with_two_aces
|
35
|
+
puts "creating invalid aces..."
|
36
|
+
system(
|
37
|
+
"
|
38
|
+
rm -Rf '#{DIR_WITH_ORPHAN_ACES}';
|
39
|
+
mv 'tmp/dir_with_two_aces' 'tmp/dir_with_orphan_aces';
|
40
|
+
sudo dscl . -create /Users/_acl_orphan_user;
|
41
|
+
sudo dscl . -create /Users/_acl_orphan_user PrimaryGroupID 20;
|
42
|
+
sudo dscl . -create /Users/_acl_orphan_user UserShell /bin/false;
|
43
|
+
sudo dscl . -create /Users/_acl_orphan_user NFSHomeDirectory /dev/null;
|
44
|
+
sudo dscl . -create /Users/_acl_orphan_user RealName acl_orphan_user;
|
45
|
+
sudo dscl . -create /Users/_acl_orphan_user UniqueID 1020;
|
46
|
+
chmod +a 'user:_acl_orphan_user allow read' 'tmp/dir_with_orphan_aces';
|
47
|
+
chmod +a# 3 'user:_acl_orphan_user deny write' 'tmp/dir_with_orphan_aces';
|
48
|
+
sudo dscl . -delete /Users/_acl_orphan_user;
|
49
|
+
"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe ACL do
|
54
|
+
it 'has a version number' do
|
55
|
+
expect(ACL::VERSION).not_to be nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can be intantiated from a path' do
|
59
|
+
expect(ACL.of("/")).to be_kind_of(ACL)
|
60
|
+
expect(ACL.of("/tmp").path).to eq("/tmp")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "can read lines of acl entries" do
|
64
|
+
expect(ACL.of("/").entry_lines).to respond_to(:each)
|
65
|
+
make_dir_with_two_aces
|
66
|
+
expect(ACL.of("tmp/dir_with_two_aces").entry_lines.length).to eq(2)
|
67
|
+
expect(ACL.of("tmp/dir_with_two_aces").entry_lines.first).to eq("group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000046:_www:70:allow:read")
|
68
|
+
expect(ACL.of("tmp/dir_with_two_aces").entry_lines.last).to eq("group:ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000014:staff:20:allow:read")
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "its entries" do
|
72
|
+
it 'is an array' do
|
73
|
+
expect(ACL.of("/").entries).to respond_to(:each)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns entry instances' do
|
77
|
+
make_dir_with_two_aces
|
78
|
+
entries = ACL.of("tmp/dir_with_two_aces").entries
|
79
|
+
expect(entries.length).to eq(2)
|
80
|
+
expect(entries.first).to be_kind_of(ACL::Entry)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "handles symlinks" do
|
84
|
+
# create a symlink that points nowhere so File.open fails
|
85
|
+
system("ln", "-sf", "/tmp/.would_not_exist_ever_ever", "tmp/symlink")
|
86
|
+
expect(ACL.of("tmp/symlink").entries.length).to eq(0)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#remove_orphans!" do
|
91
|
+
it "returns number of orphans removed" do
|
92
|
+
create_acl_with_orphans
|
93
|
+
expect(ACL.of(DIR_WITH_ORPHAN_ACES).remove_orphans!).to eq(2)
|
94
|
+
make_dir_with_two_aces
|
95
|
+
expect(ACL.of(DIR_WITH_VALID_ACES).remove_orphans!).to eq(0)
|
96
|
+
end
|
97
|
+
it "only removes orphans" do
|
98
|
+
create_acl_with_orphans
|
99
|
+
ACL.of(DIR_WITH_ORPHAN_ACES).remove_orphans!
|
100
|
+
expect(ACL.of(DIR_WITH_ORPHAN_ACES).entries.length).to eq(2)
|
101
|
+
expect(ACL.of(DIR_WITH_ORPHAN_ACES).orphans.length).to eq(0)
|
102
|
+
end
|
103
|
+
it "preserves the order of remaining aces" do
|
104
|
+
create_acl_with_orphans
|
105
|
+
ACL.of(DIR_WITH_ORPHAN_ACES).remove_orphans!
|
106
|
+
expect(ACL.of(DIR_WITH_ORPHAN_ACES).entries.first.assignment.name).to eq("_www")
|
107
|
+
expect(ACL.of(DIR_WITH_ORPHAN_ACES).entries.last.assignment.name).to eq("staff")
|
108
|
+
end
|
109
|
+
it "does nothing if environment variable is set to NOOP" do
|
110
|
+
create_acl_with_orphans
|
111
|
+
ENV['OSX_ACL_NOOP'] = 'yes'
|
112
|
+
acl_with_orphans = ACL.of(DIR_WITH_ORPHAN_ACES)
|
113
|
+
acl_with_orphans.remove_orphans!
|
114
|
+
expect(acl_with_orphans.remove_orphans!).to eq(2)
|
115
|
+
ENV['OSX_ACL_NOOP'] = nil
|
116
|
+
end
|
117
|
+
it "handles locked files" do
|
118
|
+
make_locked_dir_with_orphans
|
119
|
+
expect(`stat -f %f #{LOCKED_DIR_WITH_ORPHANS}`.chomp).to eq("2")
|
120
|
+
acl = ACL.of(LOCKED_DIR_WITH_ORPHANS)
|
121
|
+
expect(acl.remove_orphans!).to eq(2)
|
122
|
+
expect(`stat -f %f #{LOCKED_DIR_WITH_ORPHANS}`.chomp).to eq("2")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: osx-acl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kyle Crawford
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: OS X ACL reading and munipluation using ffi and C acl API
|
84
|
+
email:
|
85
|
+
- kcrwfrd@gmail.com
|
86
|
+
executables:
|
87
|
+
- acl_tool
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- .rspec
|
93
|
+
- .travis.yml
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/acl_tool
|
99
|
+
- lib/acl.rb
|
100
|
+
- lib/acl/assignment.rb
|
101
|
+
- lib/acl/entry.rb
|
102
|
+
- lib/acl/version.rb
|
103
|
+
- osx-acl.gemspec
|
104
|
+
- spec/acl/assignment_spec.rb
|
105
|
+
- spec/acl/entry_spec.rb
|
106
|
+
- spec/acl_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
homepage: ''
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.0.14
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: OS X ACL reading and manipulation
|
132
|
+
test_files:
|
133
|
+
- spec/acl/assignment_spec.rb
|
134
|
+
- spec/acl/entry_spec.rb
|
135
|
+
- spec/acl_spec.rb
|
136
|
+
- spec/spec_helper.rb
|