ood_support 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ab9a0231c41e21de5992f0ae3bcf0be3ed19638
4
- data.tar.gz: fce3ab5ef3dc87c42cb8a8af09136aa80705ed7e
3
+ metadata.gz: 74117abef87ed8fce6977b36e54d99795db90ba2
4
+ data.tar.gz: f82d7ee819ed4bacece8ea084a3bb472e47105e9
5
5
  SHA512:
6
- metadata.gz: 0611200202ea6a276b89927499a45c130d54a06d5a6eb865bcf2c413ddefa85458ea3cb887f639e3dea7d8b1a8616d5cdf27dec54b05f8a3a834cf8a86f51878
7
- data.tar.gz: 98f312e86afbe33ea1b6d1b5d1e9830feb10a28e149d3cfdbe7692744a7280397b2d2911f55b946daea9cc17a6ebc5ba25cf0e27bf8c0f5843261cdb9d8ef38e
6
+ metadata.gz: 61abe8f3a20b7d6a9b6c3094d7b3490459dbf6629efd2559c420981ebb7257f7d617399177319fa84801ec48cd13dc44bb336ea1e50e5a35feeac42b43c13cee
7
+ data.tar.gz: 173d29d517fc5f9da03cefe598e32a195301eb3752d7f982edf74997f0e2cfbc1d0591f4907a0fe7f091bd2e79aadb597f6dbec20ba7ca2eae2c5f1696958644
@@ -0,0 +1,12 @@
1
+ ## Unreleased
2
+
3
+ ## 0.0.2 (2016-08-26)
4
+
5
+ Bugfixes:
6
+
7
+ - added aliases `User#home` and `User#member_of_group?` for backwards
8
+ compatibility
9
+
10
+ ## 0.0.1 (2016-07-01)
11
+
12
+ Initial release!
data/README.md CHANGED
@@ -156,6 +156,156 @@ OodSupport::Process.groups_changed?
156
156
  #=> false
157
157
  ```
158
158
 
159
+ ### ACLs
160
+
161
+ #### NFSv4 File ACLs
162
+
163
+ Allows reading and writing of NFSv4 file ACL permissions.
164
+
165
+ To access a file's ACL:
166
+
167
+ ```ruby
168
+ # Get file ACL
169
+ acl = OodSupport::ACLs::Nfs4ACL.get_facl(path: "/path/to/file")
170
+
171
+ # Check if user has read access to file
172
+ acl.allow?(principle: OodSupport::User.new("user1"), permission: :r)
173
+ #=> true
174
+
175
+ # Check if group has write access to file
176
+ # NB: A user of this group may *actually* have access to write to this file
177
+ acl.allow?(principle: OodSupport::Group.new("group1"), permission: :w)
178
+ #=> false
179
+ ```
180
+
181
+ To add an ACL permission to a file:
182
+
183
+ ```ruby
184
+ # Create a new ACL entry
185
+ entry = OodSupport::ACLs::Nfs4Entry.new(type: :A, flags: [], principle: "user2", domain: "osc.edu", permissions: [:r, :w])
186
+
187
+ # or you can pass it a properly formatted string...
188
+ entry = OodSupport::ACLs::Nfs4Entry.parse("A::user2@osc.edu:rw")
189
+
190
+ # Add this entry to the file ACLs
191
+ acl = OodSupport::ACLs::Nfs4ACL.add_facl(path: "/path/to/file", entry: entry)
192
+
193
+ # Check that this added entry changes access
194
+ acl.allow?(principle: OodSupport::User.new("user2"), permission: :r)
195
+ #=> true
196
+ ```
197
+
198
+ To remove an ACL permission from a file:
199
+
200
+ ```ruby
201
+ # Get file ACL
202
+ acl = OodSupport::ACLs::Nfs4ACL.get_facl(path: "/path/to/file")
203
+
204
+ # Choose the entry we want to remove from the array of entries
205
+ entry = acl.entries.first
206
+
207
+ # Remove this entry from the file
208
+ new_acl = OodSupport::ACLs::Nfs4ACL.rem_facl(path: "/path/to/file", entry: entry)
209
+
210
+ # Check that this entry removal changes access
211
+ new_acl.allow?(principle: OodSupport::User.new("user2"), permission: :r)
212
+ #=> false
213
+ ```
214
+
215
+ ##### File ACL Methods
216
+
217
+ List of class methods on the `Nfs4ACL` object used to access/modify a file's
218
+ ACL. For all class methods an `Nfs4ACL` object is created and returned.
219
+
220
+ ```ruby
221
+ # Get the file/directory ACLs for a given path
222
+ Nfs4ACL::get_facl(path: p)
223
+
224
+ # Add an ACL entry to the given file/directory ACLs
225
+ Nfs4ACL::add_facl(path: p, entry: e)
226
+
227
+ # Remove an ACL entry from the given file/directory ACLs
228
+ Nfs4ACL::rem_facl(path: p, entry: e)
229
+
230
+ # Modify in-place an ACL entry from the given file/directory ACLs
231
+ Nfs4ACL::mod_facl(path: p, old_entry: e1, new_entry: e2)
232
+
233
+ # Set the whole ACL (overwrites original) for a given file/directory
234
+ Nfs4ACL::set_facl(path: p, acl: a)
235
+ ```
236
+
237
+ #### Posix File ACLs
238
+
239
+ Allows reading and writing of Posix file ACL permissions.
240
+
241
+ To access a file's ACL:
242
+
243
+ ```ruby
244
+ # Get file ACL
245
+ acl = OodSupport::ACLs::PosixACL.get_facl path: "/path/to/file"
246
+
247
+ # Check if user has read access to file
248
+ acl.allow?(principle: OodSupport::User.new("user1"), permission: :r)
249
+ #=> true
250
+
251
+ # Check if group has write access to file
252
+ # NB: A user of this group may *actually* have access to write to this file
253
+ acl.allow?(principle: OodSupport::Group.new("group1"), permission: :w)
254
+ #=> false
255
+ ```
256
+
257
+ To add an ACL permission to a file:
258
+
259
+ ```ruby
260
+ # Create a new ACL entry
261
+ entry = OodSupport::ACLs::PosixEntry.new(flag: :user, principle: "user2", permissions: [:r, :w, :-])
262
+
263
+ # or you can pass it a properly formatted string...
264
+ entry = OodSupport::ACLs::PosixEntry.parse("user:user2:rw-")
265
+
266
+ # Add this entry to the file ACLs
267
+ acl = OodSupport::ACLs::PosixACL.add_facl(path: "/path/to/file", entry: entry)
268
+
269
+ # Check that this added entry changes access
270
+ acl.allow?(principle: OodSupport::User.new("user2"), permission: :r)
271
+ #=> true
272
+ ```
273
+
274
+ To remove an ACL permission from a file:
275
+
276
+ ```ruby
277
+ # Get file ACL
278
+ acl = OodSupport::ACLs::PosixACL.get_facl(path: "/path/to/file")
279
+
280
+ # Choose the entry we want to remove from the array of entries
281
+ entry = acl.entries.detect {|e| e.user_entry? && e.principle == "user2"}
282
+
283
+ # Remove this entry from the file
284
+ new_acl = OodSupport::ACLs::PosixACL.rem_facl(path: "/path/to/file", entry: entry)
285
+
286
+ # Check that this entry removal changes access
287
+ new_acl.allow?(principle: OodSupport::User.new("user2"), permission: :r)
288
+ #=> false
289
+ ```
290
+
291
+ ##### File ACL Methods
292
+
293
+ List of class methods on the `PosixACL` object used to access/modify a file's
294
+ ACL. For all class methods an `PosixACL` object is created and returned.
295
+
296
+ ```ruby
297
+ # Get the file/directory ACLs for a given path
298
+ PosixACL::get_facl(path: p)
299
+
300
+ # Add an ACL entry to the given file/directory ACLs
301
+ PosixACL::add_facl(path: p, entry: e)
302
+
303
+ # Remove an ACL entry from the given file/directory ACLs
304
+ PosixACL::rem_facl(path: p, entry: e)
305
+
306
+ # Clear all extended ACLs for the given file/directory
307
+ PosixACL::clear_facl(path: p)
308
+ ```
159
309
  ## Contributing
160
310
 
161
311
  1. Fork it ( https://github.com/[my-github-username]/ood_support/fork )
@@ -1,9 +1,16 @@
1
1
  require 'ood_support/version'
2
+ require 'ood_support/errors'
2
3
  require 'ood_support/user'
3
4
  require 'ood_support/group'
4
5
  require 'ood_support/process'
6
+ require 'ood_support/acl'
7
+ require 'ood_support/acl_entry'
5
8
 
6
9
  # The main namespace for ood_support
7
10
  module OodSupport
8
- # Your code goes here...
11
+ # A namespace to hold all subclasses of {ACL} and {ACLEntry}
12
+ module ACLs
13
+ require 'ood_support/acls/nfs4'
14
+ require 'ood_support/acls/posix'
15
+ end
9
16
  end
@@ -0,0 +1,89 @@
1
+ module OodSupport
2
+ # A helper object that describes an access control list (ACL) with entries
3
+ class ACL
4
+ # The entries of this ACL
5
+ # @return [Array<ACLEntry>] list of entries
6
+ attr_reader :entries
7
+
8
+ # Whether this ACL defaults to allow, otherwise default deny
9
+ # @return [Boolean] whether default allow
10
+ attr_reader :default
11
+
12
+ # Generate an ACL by parsing a string along with options
13
+ # @param acl [#to_s] string describing acl
14
+ # @param kwargs [Hash] extra arguments defining acl
15
+ # @return [ACL] acl generated by string and options
16
+ def self.parse(acl, **kwargs)
17
+ entries = []
18
+ acl.to_s.strip.split(/\n|,/).grep(/^[^#]/).each do |entry|
19
+ entries << entry_class.parse(entry)
20
+ end
21
+ new(entries: entries, **kwargs)
22
+ end
23
+
24
+ # @param entries [#to_s] list of entries
25
+ # @param default [Boolean] default allow, otherwise deny
26
+ def initialize(entries:, default: false)
27
+ @entries = entries
28
+ @default = default
29
+ end
30
+
31
+ # Check if queried principle has access to resource
32
+ # @param principle [String] principle to check against
33
+ # @return [Boolean] does principle have access?
34
+ def allow?(principle:)
35
+ # Check in array order
36
+ ordered_check(principle: principle)
37
+ end
38
+
39
+ # Convert object to string
40
+ # @return [String] the string describing this object
41
+ def to_s
42
+ entries.join("\n")
43
+ end
44
+
45
+ # Convert object to hash
46
+ # @return [Hash] the hash describing this object
47
+ def to_h
48
+ { entries: entries, default: default }
49
+ end
50
+
51
+ # The comparison operator
52
+ # @param other [#to_h] entry to compare against
53
+ # @return [Boolean] how acls compare
54
+ def ==(other)
55
+ to_h == other.to_h
56
+ end
57
+
58
+ # Checks whether two ACL objects are completely identical to each other
59
+ # @param other [ACL] entry to compare against
60
+ # @return [Boolean] whether same objects
61
+ def eql?(other)
62
+ self.class == other.class && self == other
63
+ end
64
+
65
+ # Generates a hash value for this object
66
+ # @return [Fixnum] hash value of object
67
+ def hash
68
+ [self.class, to_h].hash
69
+ end
70
+
71
+ private
72
+ # Class used to generate an entry
73
+ def self.entry_class
74
+ ACLEntry
75
+ end
76
+
77
+ # Check each entry in order from array
78
+ def ordered_check(**kwargs)
79
+ entries.each do |entry|
80
+ if entry.match(**kwargs)
81
+ # Check if its an allow or deny acl entry (may not be both)
82
+ return true if entry.is_allow?
83
+ return false if entry.is_deny?
84
+ end
85
+ end
86
+ return default # default allow or default deny
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,76 @@
1
+ module OodSupport
2
+ # A helper object that defines a generic ACL entry
3
+ class ACLEntry
4
+ include Comparable
5
+
6
+ # The principle of this entry
7
+ # @return [String] principle of entry
8
+ attr_reader :principle
9
+
10
+ # Generate an entry by parsing a string
11
+ # @param entry [#to_s] string describing entry
12
+ # @param kwargs [Hash] extra arguments
13
+ # @raise [InvalidACLEntry] unable to parse entry string
14
+ # @return [ACLEntry] entry generated by string
15
+ def self.parse(entry, **kwargs)
16
+ new(parse_entry(entry).merge(kwargs))
17
+ end
18
+
19
+ # @param principle [#to_s] principle for this ACL entry
20
+ def initialize(principle:)
21
+ @principle = principle.to_s
22
+ end
23
+
24
+ # Is this an "allow" ACL entry
25
+ # @return [Boolean] is this an allow entry
26
+ def is_allow?
27
+ true
28
+ end
29
+
30
+ # Is this a "deny" ACL entry
31
+ # @return [Boolean] is this a deny entry
32
+ def is_deny?
33
+ !is_allow?
34
+ end
35
+
36
+ # Do the requested args match this ACL entry?
37
+ # @params principle [String] requested principle
38
+ # @return [Boolean] does this match this entry
39
+ def match(principle:)
40
+ self.principle == principle
41
+ end
42
+
43
+ # Convert object to string
44
+ # @return [String] the string describing this object
45
+ def to_s
46
+ principle
47
+ end
48
+
49
+ # The comparison operator
50
+ # @param other [#to_s] entry to compare against
51
+ # @return [Boolean] how entries compare
52
+ def <=>(other)
53
+ to_s <=> other
54
+ end
55
+
56
+ # Checks whether two ACLEntry objects are completely identical to each
57
+ # other
58
+ # @param other [ACLEntry] entry to compare against
59
+ # @return [Boolean] whether same objects
60
+ def eql?(other)
61
+ self.class == other.class && self == other
62
+ end
63
+
64
+ # Generates a hash value for this object
65
+ # @return [Fixnum] hash value of object
66
+ def hash
67
+ [self.class, to_s].hash
68
+ end
69
+
70
+ private
71
+ # Parse an entry string into input parameters
72
+ def self.parse_entry(entry)
73
+ { principle: entry.to_s.strip }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,262 @@
1
+ require 'open3'
2
+
3
+ module OodSupport
4
+ module ACLs
5
+ # Object describing an NFSv4 ACL
6
+ class Nfs4ACL < ACL
7
+ # The binary used to get the file ACLs
8
+ GET_FACL_BIN = 'nfs4_getfacl'
9
+
10
+ # The binary used to set the file ACLs
11
+ SET_FACL_BIN = 'nfs4_setfacl'
12
+
13
+ # Name of owner for this ACL
14
+ # @return [String] owner name
15
+ attr_reader :owner
16
+
17
+ # Name of owning group for this ACL
18
+ # @return [String] group name
19
+ attr_reader :group
20
+
21
+ # Get ACL from file path
22
+ # @param path [String] path to file or directory
23
+ # @raise [InvalidPath] file path doesn't exist
24
+ # @raise [BadExitCode] the command line called exited with non-zero status
25
+ # @return [Nfs4ACL] acl generated from path
26
+ def self.get_facl(path:)
27
+ path = Pathname.new path
28
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
29
+ stat = path.stat
30
+ acl, err, s = Open3.capture3(GET_FACL_BIN, path.to_s)
31
+ raise BadExitCode, err unless s.success?
32
+ parse(acl, owner: User.new(stat.uid), group: Group.new(stat.gid))
33
+ end
34
+
35
+ # Add ACL to file path
36
+ # @param path [String] path to file or directory
37
+ # @param entry [Nfs4Entry] entry to add to file
38
+ # @raise [InvalidPath] file path doesn't exist
39
+ # @raise [BadExitCode] the command line called exited with non-zero status
40
+ # @return [Nfs4ACL] new acl of path
41
+ def self.add_facl(path:, entry:)
42
+ path = Pathname.new path
43
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
44
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-a', entry.to_s, path.to_s)
45
+ raise BadExitCode, err unless s.success?
46
+ get_facl(path: path)
47
+ end
48
+
49
+ # Remove ACL from file path
50
+ # @param path [String] path to file or directory
51
+ # @param entry [Nfs4Entry] entry to remove from file
52
+ # @raise [InvalidPath] file path doesn't exist
53
+ # @raise [BadExitCode] the command line called exited with non-zero status
54
+ # @return [Nfs4ACL] new acl of path
55
+ def self.rem_facl(path:, entry:)
56
+ path = Pathname.new path
57
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
58
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-x', entry.to_s, path.to_s)
59
+ raise BadExitCode, err unless s.success?
60
+ get_facl(path: path)
61
+ end
62
+
63
+ # Modify in-place an entry for file path
64
+ # @param path [String] path to file or directory
65
+ # @param old_entry [Nfs4Entry] old entry to modify in-place in file
66
+ # @param new_entry [Nfs4Entry] new entry to be replaced with
67
+ # @raise [InvalidPath] file path doesn't exist
68
+ # @raise [BadExitCode] the command line called exited with non-zero status
69
+ # @return [Nfs4ACL] new acl of path
70
+ def self.mod_facl(path:, old_entry:, new_entry:)
71
+ path = Pathname.new path
72
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
73
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-m', old_entry.to_s, new_entry.to_s, path.to_s)
74
+ raise BadExitCode, err unless s.success?
75
+ get_facl(path: path)
76
+ end
77
+
78
+ # Set ACL (overwrites original) for file path
79
+ # @param path [String] path to file or directory
80
+ # @param acl [Nfs4ACL] ACL to overwrite original with
81
+ # @raise [InvalidPath] file path doesn't exist
82
+ # @raise [BadExitCode] the command line called exited with non-zero status
83
+ # @return [Nfs4ACL] new acl of path
84
+ def self.set_facl(path:, acl:)
85
+ path = Pathname.new path
86
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
87
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-s', acl.to_s, path.to_s)
88
+ raise BadExitCode, err unless s.success?
89
+ get_facl(path: path)
90
+ end
91
+
92
+ # @param owner [#to_s] name of owner
93
+ # @param group [#to_s] name of group
94
+ # @see ACL#initialize
95
+ def initialize(owner:, group:, **kwargs)
96
+ super(kwargs.merge(default: false))
97
+ @owner = owner.to_s
98
+ @group = group.to_s
99
+ end
100
+
101
+ # Check if queried principle has access to resource
102
+ # @param principle [User, Group] principle to check against
103
+ # @param permission [Symbol] permission to check against
104
+ # @return [Boolean] does principle have access?
105
+ def allow?(principle:, permission:)
106
+ # Check in array order
107
+ ordered_check(principle: principle, permission: permission, owner: owner, group: group)
108
+ end
109
+
110
+ # Convert object to hash
111
+ # @return [Hash] the hash describing this object
112
+ def to_h
113
+ super.merge owner: owner, group: group
114
+ end
115
+
116
+ private
117
+ # Use Nfs4Entry for entry objects
118
+ def self.entry_class
119
+ Nfs4Entry
120
+ end
121
+ end
122
+
123
+ # Object describing single NFSv4 ACL entry
124
+ class Nfs4Entry < ACLEntry
125
+ # Valid types for an ACL entry
126
+ VALID_TYPE = %i[ A U D L ]
127
+
128
+ # Valid flags for an ACL entry
129
+ VALID_FLAG = %i[ f d p i S F g ]
130
+
131
+ # Valid permissions for an ACL entry
132
+ VALID_PERMISSION = %i[ r w a x d D t T n N c C o y ]
133
+
134
+ # Regular expression used when parsing ACL entry string
135
+ REGEX_PATTERN = %r[^(?<type>[#{VALID_TYPE.join}]):(?<flags>[#{VALID_FLAG.join}]*):(?<principle>\w+)@(?<domain>[\w\.\-]*):(?<permissions>[#{VALID_PERMISSION.join}]+)$]
136
+
137
+ # Type of ACL entry
138
+ # @return [Symbol] type of acl entry
139
+ attr_reader :type
140
+
141
+ # Flags set on ACL entry
142
+ # @return [Array<Symbol>] flags on acl entry
143
+ attr_reader :flags
144
+
145
+ # Domain of ACL entry
146
+ # @return [String] domain of acl entry
147
+ attr_reader :domain
148
+
149
+ # Permissions of ACL entry
150
+ # @return [Array<Symbol>] permissions of acl entry
151
+ attr_reader :permissions
152
+
153
+ # @param type [#to_sym] type of acl entry
154
+ # @param flags [Array<#to_sym>] list of flags for entry
155
+ # @param domain [#to_s] domain of principle
156
+ # @param permissions [Array<#to_sym>] list of permissions for entry
157
+ # @see ACLEntry#initialize
158
+ def initialize(type:, flags:, domain:, permissions:, **kwargs)
159
+ @type = type.to_sym
160
+ @flags = flags.map(&:to_sym)
161
+ @domain = domain.to_s
162
+ @permissions = permissions.map(&:to_sym)
163
+ super(kwargs)
164
+ end
165
+
166
+ # Is this an "allow" ACL entry
167
+ # @return [Boolean] is this an allow entry
168
+ def is_allow?
169
+ type == :A
170
+ end
171
+
172
+ # Is this a "deny" ACL entry
173
+ # @return [Boolean] is this a deny entry
174
+ def is_deny?
175
+ type == :D
176
+ end
177
+
178
+ # Do the requested args match this ACL entry?
179
+ # @param principle [User, Group, #to_s] requested principle
180
+ # @param permission [#to_sym] requested permission
181
+ # @param owner [String] owner of corresponding ACL
182
+ # @param group [String] owning group of corresponding ACL
183
+ # @raise [ArgumentError] principle isn't {User} or {Group} object
184
+ # @return [Boolean] does this match this entry
185
+ def match(principle:, permission:, owner:, group:)
186
+ principle = User.new(principle) if (!principle.is_a?(User) && !principle.is_a?(Group))
187
+ return false unless has_permission?(permission: permission)
188
+ # Ignore domain, I don't want or care to check for domain matches
189
+ p = self.principle
190
+ p = owner if user_owner_entry?
191
+ p = group if group_owner_entry?
192
+ if (principle.is_a?(User) && group_entry?)
193
+ principle.groups.include?(p)
194
+ elsif (principle.is_a?(User) && user_entry?) || (principle.is_a?(Group) && group_entry?)
195
+ principle == p
196
+ elsif other_entry?
197
+ true
198
+ else
199
+ false
200
+ end
201
+ end
202
+
203
+ # Is this a user-specific ACL entry
204
+ # @return [Boolean] is this a user entry
205
+ def user_entry?
206
+ !group_entry? && !other_entry?
207
+ end
208
+
209
+ # Is this a group-specific ACL entry
210
+ # @return [Boolean] is this a group entry
211
+ def group_entry?
212
+ flags.include? :g
213
+ end
214
+
215
+ # Is this an other-specific ACL entry
216
+ # @return [Boolean] is this an other entry
217
+ def other_entry?
218
+ principle == "EVERYONE"
219
+ end
220
+
221
+ # Is this the owner ACL entry
222
+ # @return [Boolean] is this the owner entry
223
+ def user_owner_entry?
224
+ user_entry? && principle == "OWNER"
225
+ end
226
+
227
+ # Is this the owning group ACL entry
228
+ # @return [Boolean] is this the owning group entry
229
+ def group_owner_entry?
230
+ group_entry? && principle == "GROUP"
231
+ end
232
+
233
+ # Does this entry have the requested permission
234
+ # @param permission [#to_sym] the requested permission
235
+ # @return [Boolean] found this permission
236
+ def has_permission?(permission:)
237
+ permissions.include? permission.to_sym
238
+ end
239
+
240
+ # Convert object to string
241
+ # @return [String] the string describing this object
242
+ def to_s
243
+ "#{type}:#{flags.join}:#{principle}@#{domain}:#{permissions.join}"
244
+ end
245
+
246
+ private
247
+ # Parse an entry string into input parameters
248
+ def self.parse_entry(entry)
249
+ e = REGEX_PATTERN.match(entry.to_s.strip) do |m|
250
+ {
251
+ type: m[:type],
252
+ flags: m[:flags].chars,
253
+ principle: m[:principle],
254
+ domain: m[:domain],
255
+ permissions: m[:permissions].chars
256
+ }
257
+ end
258
+ e ? e : raise(InvalidACLEntry, "invalid entry: #{entry}")
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,274 @@
1
+ require 'open3'
2
+
3
+ module OodSupport
4
+ module ACLs
5
+ # Object describing a Posix ACL
6
+ class PosixACL < ACL
7
+ # The binary used to get the file ACLs
8
+ GET_FACL_BIN = 'getfacl'
9
+
10
+ # The binary used to set the file ACLs
11
+ SET_FACL_BIN = 'setfacl'
12
+
13
+ # Name of owner for this ACL
14
+ # @return [String] owner name
15
+ attr_reader :owner
16
+
17
+ # Name of owning group for this ACL
18
+ # @return [String] group name
19
+ attr_reader :group
20
+
21
+ # Mask set for this ACL
22
+ # @return [Array<Symbol>] mask for this acl
23
+ attr_reader :mask
24
+
25
+ # Get ACL from file path
26
+ # @param path [String] path to file or directory
27
+ # @raise [InvalidPath] file path doesn't exist
28
+ # @raise [BadExitCode] the command line called exited with non-zero status
29
+ # @return [PosixACL] acl generated from path
30
+ def self.get_facl(path:)
31
+ path = Pathname.new path
32
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
33
+ stat = path.stat
34
+ acl, err, s = Open3.capture3(GET_FACL_BIN, path.to_s)
35
+ raise BadExitCode, err unless s.success?
36
+ parse(acl, owner: User.new(stat.uid), group: Group.new(stat.gid))
37
+ end
38
+
39
+ # Add ACL to file path
40
+ # @param path [String] path to file or directory
41
+ # @param entry [PosixEntry] entry to add to file
42
+ # @raise [InvalidPath] file path doesn't exist
43
+ # @raise [BadExitCode] the command line called exited with non-zero status
44
+ # @return [PosixACL] new acl of path
45
+ def self.add_facl(path:, entry:)
46
+ path = Pathname.new path
47
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
48
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-m', entry.to_s, path.to_s)
49
+ raise BadExitCode, err unless s.success?
50
+ get_facl(path: path)
51
+ end
52
+
53
+ # Remove ACL from file path
54
+ # @param path [String] path to file or directory
55
+ # @param entry [PosixEntry] entry to remove from file
56
+ # @raise [InvalidPath] file path doesn't exist
57
+ # @raise [BadExitCode] the command line called exited with non-zero status
58
+ # @return [PosixACL] new acl of path
59
+ def self.rem_facl(path:, entry:)
60
+ path = Pathname.new path
61
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
62
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-x', entry.to_s(w_perms: false), path.to_s)
63
+ raise BadExitCode, err unless s.success?
64
+ get_facl(path: path)
65
+ end
66
+
67
+ # Clear all extended ACLs from file path
68
+ # @param path [String] path to file or directory
69
+ # @return [PosixACL] new acl of path
70
+ def self.clear_facl(path:)
71
+ path = Pathname.new path
72
+ raise InvalidPath, "invalid path: #{path}" unless path.exist?
73
+ _, err, s = Open3.capture3(SET_FACL_BIN, '-b', path.to_s)
74
+ raise BadExitCode, err unless s.success?
75
+ get_facl(path: path)
76
+ end
77
+
78
+ # Generate an ACL by parsing a string along with options
79
+ # @param acl [#to_s] string describing acl
80
+ # @param kwargs [Hash] extra arguments defining acl
81
+ # @return [PosixACL] acl generated by string and options
82
+ def self.parse(acl, **kwargs)
83
+ entries = []
84
+ acl.to_s.strip.split(/\n|,/).grep(/^[^#]/).each do |entry|
85
+ entries << entry_class.parse(entry)
86
+ end
87
+ mask = entries.detect {|e| e.flag == :mask}
88
+ new(entries: entries - [mask], mask: mask, **kwargs)
89
+ end
90
+
91
+ # @param owner [#to_s] name of owner
92
+ # @param group [#to_s] name of group
93
+ # @param mask [PosixACL] mask permissions
94
+ # @see ACL#initialize
95
+ def initialize(owner:, group:, mask:, **kwargs)
96
+ super(kwargs.merge(default: false))
97
+ @owner = owner.to_s
98
+ @group = group.to_s
99
+ @mask = mask
100
+ end
101
+
102
+ # Check if queried principle has access to resource
103
+ # @param principle [User, Group] principle to check against
104
+ # @param permission [Symbol] permission to check against
105
+ # @return [Boolean] does principle have access?
106
+ def allow?(principle:, permission:)
107
+ # First check owner entry then check rest of user entries (order
108
+ # matters). If match, then this entry determines access.
109
+ entries.select(&:user_entry?).sort_by {|e| e.user_owner_entry? ? 0 : 1}.each do |entry|
110
+ return entry.has_permission?(permission: permission, mask: mask) if entry.match(principle: principle, owner: owner, group: group)
111
+ end
112
+
113
+ # Then check groups (order independent). Entry only determines access
114
+ # if it contains requested permission.
115
+ groups = entries.select {|e| e.group_entry? && e.match(principle: principle, owner: owner, group: group)}.map do |entry|
116
+ entry.has_permission?(permission: permission, mask: mask)
117
+ end
118
+
119
+ unless groups.empty?
120
+ # Found matching groups so check if any give access
121
+ groups.any?
122
+ else
123
+ # Failed to find any matching groups so check "other" entry
124
+ entries.detect(&:other_entry?).has_permission?(permission: permission, mask: mask)
125
+ end
126
+ end
127
+
128
+ # Convert object to string
129
+ # @return [String] the string describing the object
130
+ def to_s
131
+ (entries + [mask]).join(",")
132
+ end
133
+
134
+ # Convert object to hash
135
+ # @return [Hash] the hash describing this object
136
+ def to_h
137
+ super.merge owner: owner, group: group, mask: mask
138
+ end
139
+
140
+ private
141
+ # Use PosixEntry for entry objects
142
+ def self.entry_class
143
+ PosixEntry
144
+ end
145
+ end
146
+
147
+ # Object describing single Posix ACL entry
148
+ class PosixEntry < ACLEntry
149
+ # Valid flags for an ACL entry
150
+ VALID_FLAG = %i[ user group mask other ]
151
+
152
+ # Valid permissions for an ACL entry
153
+ VALID_PERMISSION = %i[ r w x ]
154
+
155
+ # Regular expression used when parsing ACL entry string
156
+ REGEX_PATTERN = %r[^(?<default>default:)?(?<flag>#{VALID_FLAG.join('|')}):(?<principle>\w*):(?<permissions>[#{VALID_PERMISSION.join}\-]{3})]
157
+
158
+ # Is this a default ACL entry
159
+ # @return [Boolean] whether default acl entry
160
+ attr_reader :default
161
+
162
+ # Flag set on ACL entry
163
+ # @return [Symbol] flag on acl entry
164
+ attr_reader :flag
165
+
166
+ # Permissions of ACL entry
167
+ # @return [Array<Symbol>] permissions of acl entry
168
+ attr_reader :permissions
169
+
170
+ # @param default [Boolean] whether default acl entry
171
+ # @param flag [#to_sym] flag for entry
172
+ # @param permissions [Array<#to_sym>] list of permissions for entry
173
+ # @see ACLEntry#initialize
174
+ def initialize(default: false, flag:, permissions:, **kwargs)
175
+ @default = default
176
+ @flag = flag.to_sym
177
+ @permissions = permissions.map(&:to_sym)
178
+ super(kwargs)
179
+ end
180
+
181
+ # Do the requested args match this ACL entry?
182
+ # @param principle [User, Group, #to_s] requested principle
183
+ # @param owner [String] owner of corresponding ACL
184
+ # @param group [String] owning group of corresponding ACL
185
+ # @raise [ArgumentError] principle isn't {User} or {Group} object
186
+ # @return [Boolean] does this match this entry
187
+ def match(principle:, owner:, group:)
188
+ principle = User.new(principle) if (!principle.is_a?(User) && !principle.is_a?(Group))
189
+ return false if default_entry?
190
+ p = self.principle
191
+ p = owner if user_owner_entry?
192
+ p = group if group_owner_entry?
193
+ if (principle.is_a?(User) && group_entry?)
194
+ principle.groups.include?(p)
195
+ elsif (principle.is_a?(User) && user_entry?) || (principle.is_a?(Group) && group_entry?)
196
+ principle == p
197
+ elsif other_entry?
198
+ true
199
+ else
200
+ false
201
+ end
202
+ end
203
+
204
+ # Is this a default ACL entry
205
+ # @return [Boolean] is this a default entry
206
+ def default_entry?
207
+ default
208
+ end
209
+
210
+ # Is this a user-specific ACL entry
211
+ # @return [Boolean] is this a user entry
212
+ def user_entry?
213
+ !default_entry? && flag == :user
214
+ end
215
+
216
+ # Is this a group-specific ACL entry
217
+ # @return [Boolean] is this a group entry
218
+ def group_entry?
219
+ !default_entry? && flag == :group
220
+ end
221
+
222
+ # Is this an other-specific ACL entry
223
+ # @return [Boolean] is this an other entry
224
+ def other_entry?
225
+ !default_entry? && flag == :other
226
+ end
227
+
228
+ # Is this the owner ACL entry
229
+ # @return [Boolean] is this the owner entry
230
+ def user_owner_entry?
231
+ user_entry? && principle.empty?
232
+ end
233
+
234
+ # Is this the owning group ACL entry
235
+ # @return [Boolean] is this the owning group entry
236
+ def group_owner_entry?
237
+ group_entry? && principle.empty?
238
+ end
239
+
240
+ # Does this entry have the requested permission
241
+ # @param permission [#to_sym] the requested permission
242
+ # @param mask [PosixEntry] the permissions of the mask entry
243
+ # @return [Boolean] found this permission
244
+ def has_permission?(permission:, mask:)
245
+ if user_owner_entry? || other_entry?
246
+ permissions.include? permission.to_sym
247
+ else
248
+ (mask ? permissions & mask.permissions : permissions).include? permission.to_sym
249
+ end
250
+ end
251
+
252
+ # Convert object to string
253
+ # @param w_perms [Boolean] whether display permissions
254
+ # @return [String] the string describing this object
255
+ def to_s(w_perms: true)
256
+ %[#{"default:" if default_entry?}#{flag}:#{principle}#{":#{permissions.join}" if w_perms}]
257
+ end
258
+
259
+ private
260
+ # Parse an entry string into input parameters
261
+ def self.parse_entry(entry)
262
+ e = REGEX_PATTERN.match(entry.to_s.strip) do |m|
263
+ {
264
+ default: m[:default] ? true : false,
265
+ flag: m[:flag],
266
+ principle: m[:principle],
267
+ permissions: m[:permissions].chars
268
+ }
269
+ end
270
+ e ? e : raise(InvalidACLEntry, "invalid entry: #{entry}")
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,16 @@
1
+ module OodSupport
2
+ # The root exception class that all OodSupport-specific exceptions inherit
3
+ # from
4
+ class Error < StandardError; end
5
+
6
+ # An exception raised when attempting to access a path that doesn't exist on
7
+ # local file system
8
+ class InvalidPath < Error; end
9
+
10
+ # An exception raised when attempting to run a command that exits with an
11
+ # exit code other than 0
12
+ class BadExitCode < Error; end
13
+
14
+ # An exception raised when attempting to parse an ACL entry from a string
15
+ class InvalidACLEntry < Error; end
16
+ end
@@ -25,7 +25,7 @@ module OodSupport
25
25
  end
26
26
 
27
27
  # The comparison operator for sorting values
28
- # @param other [Group] group to compare against
28
+ # @param other [#to_s] group to compare against
29
29
  # @return [Fixnum] how groups compare
30
30
  def <=>(other)
31
31
  name <=> other
@@ -36,13 +36,13 @@ module OodSupport
36
36
  # @param other [Group] group to compare against
37
37
  # @return [Boolean] whether same objects
38
38
  def eql?(other)
39
- other.is_a?(Group) && id == other.id
39
+ self.class == other.class && self == other
40
40
  end
41
41
 
42
42
  # Generates a hash value for this object
43
43
  # @return [Fixnum] hash value of object
44
44
  def hash
45
- id.hash
45
+ [self.class, name].hash
46
46
  end
47
47
 
48
48
  # Convert object to string using group name as string value
@@ -28,6 +28,8 @@ module OodSupport
28
28
 
29
29
  alias_method :id, :uid
30
30
 
31
+ alias_method :home, :dir
32
+
31
33
  # @param user [Fixnum, #to_s] user id or name
32
34
  def initialize(user = Process.user)
33
35
  @passwd = user.is_a?(Fixnum) ? Etc.getpwuid(user) : Etc.getpwnam(user.to_s)
@@ -40,6 +42,8 @@ module OodSupport
40
42
  groups.include? Group.new(group)
41
43
  end
42
44
 
45
+ alias_method :member_of_group?, :in_group?
46
+
43
47
  # Provide primary group of user
44
48
  # @return [Group] primary group of user
45
49
  def group
@@ -53,7 +57,7 @@ module OodSupport
53
57
  end
54
58
 
55
59
  # The comparison operator for sorting values
56
- # @param other [User] user to compare against
60
+ # @param other [#to_s] user to compare against
57
61
  # @return [Fixnum] how users compare
58
62
  def <=>(other)
59
63
  name <=> other
@@ -64,13 +68,13 @@ module OodSupport
64
68
  # @param other [User] user to compare against
65
69
  # @return [Boolean] whether same objects
66
70
  def eql?(other)
67
- other.is_a?(User) && id == other.id
71
+ self.class == other.class && self == other
68
72
  end
69
73
 
70
74
  # Generates a hash value for this object
71
75
  # @return [Fixnum] hash value of object
72
76
  def hash
73
- id.hash
77
+ [self.class, name].hash
74
78
  end
75
79
 
76
80
  # Convert object to string using user name as string value
@@ -1,4 +1,4 @@
1
1
  module OodSupport
2
2
  # The current version of {OodSupport}
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ood_support
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Nicklas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-01 00:00:00.000000000 Z
11
+ date: 2016-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -47,11 +47,17 @@ extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
49
  - ".gitignore"
50
+ - CHANGELOG.md
50
51
  - Gemfile
51
52
  - LICENSE.txt
52
53
  - README.md
53
54
  - Rakefile
54
55
  - lib/ood_support.rb
56
+ - lib/ood_support/acl.rb
57
+ - lib/ood_support/acl_entry.rb
58
+ - lib/ood_support/acls/nfs4.rb
59
+ - lib/ood_support/acls/posix.rb
60
+ - lib/ood_support/errors.rb
55
61
  - lib/ood_support/group.rb
56
62
  - lib/ood_support/process.rb
57
63
  - lib/ood_support/user.rb