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