icfs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/icfs/users.rb ADDED
@@ -0,0 +1,80 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ require 'set'
13
+
14
+ module ICFS
15
+
16
+ ##########################################################################
17
+ # User, Role, Group, and Global Perms
18
+ #
19
+ #
20
+ # @abstract
21
+ #
22
+ class Users
23
+
24
+ ###############################################
25
+ # Validate a user
26
+ #
27
+ ValUser = {
28
+ method: :hash,
29
+ required: {
30
+ 'name' => Items::FieldUsergrp,
31
+ 'type' => {
32
+ method: :string,
33
+ allowed: Set[
34
+ 'user'.freeze,
35
+ 'role'.freeze,
36
+ 'group'.freeze,
37
+ ].freeze
38
+ }.freeze
39
+ }.freeze,
40
+ optional: {
41
+ 'roles' => {
42
+ method: :array,
43
+ check: Items::FieldUsergrp,
44
+ uniq: true
45
+ }.freeze,
46
+ 'groups' => {
47
+ method: :array,
48
+ check: Items::FieldUsergrp,
49
+ uniq: true
50
+ }.freeze,
51
+ 'perms' => {
52
+ method: :array,
53
+ check: Items::FieldPermGlobal,
54
+ uniq: true
55
+ }.freeze
56
+ }.freeze
57
+ }.freeze
58
+
59
+
60
+ ###############################################
61
+ # Read a user/role/group
62
+ #
63
+ # @param urg [String] User/Role/Group name
64
+ # @return [Hash] Will include :type and, if a user :roles, :groups, :perms
65
+ #
66
+ def read(urg); raise NotImplementedError; end
67
+
68
+
69
+ ###############################################
70
+ # Write a user/role/group
71
+ #
72
+ # @param obj [Hash] Will include :name, :type, and if a user
73
+ # :roles, :groups, :perms
74
+ #
75
+ def write(obj); raise NotImplementedError; end
76
+
77
+
78
+ end # class ICFS::Users
79
+
80
+ end # module ICFS
@@ -0,0 +1,166 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ require 'set'
13
+ require_relative 'elastic'
14
+
15
+ module ICFS
16
+
17
+ ##########################################################################
18
+ # Implements {ICFS::Users Users} using Elasticsearch to cache
19
+ # details from another {ICFS::Users Users} instance.
20
+ #
21
+ class UsersElastic < Users
22
+
23
+ include Elastic
24
+
25
+ private
26
+
27
+ ###############################################
28
+ # The ES mappings for the indexes
29
+ #
30
+ Maps = {
31
+ :users => '{
32
+ "mappings": { "_doc": { "properties": {
33
+ "name": { "type": "text" },
34
+ "type": { "type": "keyword" },
35
+ "roles": { "type": "keyword" },
36
+ "groups": { "type": "keyword" },
37
+ "perms": { "type": "keyword" },
38
+ "first": { "enabled": false },
39
+ "last": { "type": "date", "format": "epoch_second" },
40
+ "active": { "type": "boolean" }
41
+ }}}
42
+ }'.freeze,
43
+ }.freeze
44
+
45
+ public
46
+
47
+ ###############################################
48
+ # New instance
49
+ #
50
+ # @param map [Hash] Symbol to String of the indexes.
51
+ # Must provide :user
52
+ # @param es [Faraday] Faraday instance to the Elasticsearch cluster
53
+ # @param src [Users] Source of authoritative information
54
+ # @param exp [Integer] Maximum time to cache a response
55
+ #
56
+ def initialize(map, es, src, exp=3600)
57
+ @map = map
58
+ @es = es
59
+ @src = src
60
+ @exp = exp
61
+ end
62
+
63
+
64
+ ###############################################
65
+ # Validate a user
66
+ #
67
+ ValUserCache = {
68
+ method: :hash,
69
+ required: {
70
+ 'name' => Items::FieldUsergrp,
71
+ 'type' => {
72
+ method: :string,
73
+ allowed: Set[
74
+ 'user'.freeze,
75
+ 'role'.freeze,
76
+ 'group'.freeze,
77
+ ].freeze
78
+ }.freeze,
79
+ 'first' => Validate::IsIntPos,
80
+ 'last' => Validate::IsIntPos,
81
+ 'active' => Validate::IsBoolean,
82
+ }.freeze,
83
+ optional: {
84
+ 'roles' => {
85
+ method: :array,
86
+ check: Items::FieldUsergrp,
87
+ uniq: true
88
+ }.freeze,
89
+ 'groups' => {
90
+ method: :array,
91
+ check: Items::FieldUsergrp,
92
+ uniq: true
93
+ }.freeze,
94
+ 'perms' => {
95
+ method: :array,
96
+ check: Items::FieldPermGlobal,
97
+ uniq: true
98
+ }.freeze
99
+ }.freeze
100
+ }.freeze
101
+
102
+
103
+ ###############################################
104
+ # (see Users#read)
105
+ #
106
+ def read(urg)
107
+ json = _read(:users, urg)
108
+ now = Time.now.to_i
109
+
110
+ # not in the cache
111
+ if !json
112
+
113
+ # read from source
114
+ obj = @src.read(urg)
115
+ return nil if !obj
116
+
117
+ # first time
118
+ obj['first'] = now
119
+ obj['last'] = now
120
+ obj['active'] = true
121
+
122
+ # store in cache
123
+ json = Validate.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
124
+ _write(:users, urg, json)
125
+
126
+ # use cached version
127
+ else
128
+ obj = Validate.parse(json, 'User/Role/Group'.freeze, ValUserCache)
129
+ end
130
+
131
+ # expired
132
+ if (obj['last'] + @exp) < now
133
+
134
+ # read from source
135
+ obj2 = @src.read(urg)
136
+
137
+ # update
138
+ if obj2
139
+ obj['active'] = true
140
+ obj['roles'] = obj2['roles']
141
+ obj['groups'] = obj2['groups']
142
+ obj['perms'] = obj2['perms']
143
+ else
144
+ obj['active'] = false
145
+ end
146
+ obj['last'] = now
147
+
148
+ # and store in cache
149
+ json = Validate.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
150
+ _write(:users, urg, json)
151
+ end
152
+
153
+ # not active
154
+ return nil unless obj['active']
155
+
156
+ # clean out cached info
157
+ obj.delete('first'.freeze)
158
+ obj.delete('last'.freeze)
159
+ obj.delete('active'.freeze)
160
+
161
+ return obj
162
+ end # def read()
163
+
164
+ end # class ICFS::Users
165
+
166
+ end # module ICFS
@@ -0,0 +1,132 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ require 'tempfile'
13
+ require 'fileutils'
14
+ require 'set'
15
+
16
+ module ICFS
17
+
18
+ ##########################################################################
19
+ # Implements {ICFS::Users Users} from a file system
20
+ #
21
+ class UsersFs < Users
22
+
23
+ private
24
+
25
+ ###############################################
26
+ # read a raw file
27
+ def _read(fn)
28
+ json = File.read(File.join(@path, fn + '.json'.freeze))
29
+ obj = Validate.parse(json, 'User/Role/Group'.freeze, Users::ValUser)
30
+ if obj['name'] != fn
31
+ raise(Error::Values, 'UsersFs user %s name mismatch'.freeze % fn)
32
+ end
33
+ return obj
34
+ end # _read
35
+
36
+
37
+ public
38
+
39
+
40
+ ###############################################
41
+ # New instance
42
+ #
43
+ # @param path [String] Base directory
44
+ #
45
+ def initialize(path)
46
+ @path = path.dup
47
+ end
48
+
49
+
50
+ ###############################################
51
+ # (see Users#read)
52
+ #
53
+ def read(urg)
54
+ Validate.validate(urg, 'User/Role/Group'.freeze, Items::FieldUsergrp)
55
+
56
+ # get the base user
57
+ usr = _read(urg)
58
+ return usr if usr['type'] != 'user'.freeze
59
+
60
+ # assemble
61
+ type = usr['type']
62
+ done_s = Set.new.add(urg)
63
+ ary = []
64
+ roles_s = Set.new
65
+ if usr['roles']
66
+ ary.concat usr['roles']
67
+ roles_s.merge usr['roles']
68
+ end
69
+ grps_s = Set.new
70
+ if usr['groups']
71
+ ary.concat usr['groups']
72
+ grps_s.merge usr['groups']
73
+ end
74
+ perms_s = Set.new
75
+ if usr['perms']
76
+ perms_s.merge usr['perms']
77
+ end
78
+
79
+ # roles & groups
80
+ while itm = ary.shift
81
+ next if done_s.include?(itm)
82
+ done_s.add(itm)
83
+
84
+ usr = _read(itm)
85
+ if usr['roles']
86
+ ary.concat usr['roles']
87
+ roles_s.merge usr['roles']
88
+ end
89
+ if usr['groups']
90
+ ary.concat usr['groups']
91
+ grps_s.merge usr['groups']
92
+ end
93
+ if usr['perms']
94
+ perms_s.merge usr['perms']
95
+ end
96
+ end
97
+
98
+ # assemble final
99
+ return {
100
+ 'name' => urg.dup,
101
+ 'type' => type,
102
+ 'roles' => roles_s.to_a,
103
+ 'groups' => grps_s.to_a,
104
+ 'perms' => perms_s.to_a,
105
+ }
106
+
107
+ rescue Errno::ENOENT
108
+ return nil
109
+ end # def read()
110
+
111
+
112
+ ###############################################
113
+ # (see Users#write)
114
+ #
115
+ def write(obj)
116
+ Validate.validate(obj, 'User/Role/Group'.freeze, Users::ValUser)
117
+ json = JSON.pretty_generate(obj)
118
+
119
+ # write to temp file
120
+ tmp = Tempfile.new('_tmp'.freeze, @path, :encoding => 'ascii-8bit'.freeze)
121
+ tmp.write(json)
122
+ tmp.close
123
+
124
+ # move
125
+ FileUtils.mv(tmp.path, File.join(@path, obj['name'] + '.json'.freeze))
126
+ tmp.unlink
127
+ end # def write()
128
+
129
+
130
+ end # class ICFS::UsersFs
131
+
132
+ end # module ICFS
@@ -0,0 +1,479 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ require 'set'
13
+ require 'json'
14
+ require 'tempfile'
15
+
16
+ module ICFS
17
+
18
+ ##########################################################################
19
+ # Object validation
20
+ #
21
+ # @todo Move .parse and .generate to items or ICFS
22
+ # @todo Remove all use of Error module
23
+ #
24
+ module Validate
25
+
26
+
27
+ ###############################################
28
+ # Parse JSON string and validate
29
+ #
30
+ # @param json [String] the JSON to parse
31
+ # @param name [String] description of the item
32
+ # @param val [Hash] the check to use
33
+ # @return [Object] the item
34
+ #
35
+ # @raise [Error::NotFound] if json is nil
36
+ # @raise [Error::Value] if parsing or validation fails
37
+ #
38
+ def self.parse(json, name, val)
39
+ if json.nil?
40
+ raise(Error::NotFound, '%s not found'.freeze % name)
41
+ end
42
+ begin
43
+ itm = JSON.parse(json)
44
+ rescue
45
+ raise(Error::Value, 'JSON parsing failed'.freeze)
46
+ end
47
+ Validate.validate(itm, name, val)
48
+ return itm
49
+ end # def self.parse()
50
+
51
+
52
+ ###############################################
53
+ # Validate and generate JSON
54
+ #
55
+ # @param itm [Object] item to validate
56
+ # @param name [String] description of the item
57
+ # @param val [Hash] the check to use
58
+ # @return [String] JSON encoded item
59
+ #
60
+ # @raise [Error::Value] if validation fails
61
+ #
62
+ def self.generate(itm, name, val)
63
+ Validate.validate(itm, name, val)
64
+ return JSON.pretty_generate(itm)
65
+ end # def self.generate()
66
+
67
+
68
+ ###############################################
69
+ # Validate an object
70
+ #
71
+ # @param obj [Object] object to validate
72
+ # @param name [String] description of the object
73
+ # @param val [Hash] the check to use
74
+ #
75
+ # @raise [Error::Value] if validation fails
76
+ #
77
+ def self.validate(obj, name, val)
78
+ err = Validate.check(obj, val)
79
+ if err
80
+ raise(Error::Value, '%s has bad values: %s'.freeze %
81
+ [name, err.inspect])
82
+ end
83
+ end
84
+
85
+
86
+ ###############################################
87
+ # check an object
88
+ #
89
+ # @param obj [Object] object to validate
90
+ # @param val [Hash] the check to use
91
+ # @return [Object] error description
92
+ #
93
+ def self.check(obj, val)
94
+ if val.key?(:object)
95
+ err = val[:object].send(val[:method], obj, val)
96
+ else
97
+ err = Validate.send(val[:method], obj, val)
98
+ end
99
+ return err
100
+ end # def self.check()
101
+
102
+
103
+ ##############################################################
104
+ # Check Methods
105
+ ##############################################################
106
+
107
+
108
+ ###############################################
109
+ # check that any one validation is good
110
+ #
111
+ # @param obj [Object] object to validate
112
+ # @param val [Hash] options
113
+ # @option val [Array<Hash>] :check validations to check
114
+ # @return [Array,NilClass] error descriptions
115
+ #
116
+ def self.any(obj, val)
117
+ return nil unless val[:check].is_a?(Array)
118
+
119
+ errors = []
120
+
121
+ val[:check].each do |chk|
122
+ err = Validate.check(obj, chk)
123
+ return nil if err.nil?
124
+ errors << err
125
+ end
126
+
127
+ return errors
128
+ end # def self.any()
129
+
130
+
131
+ ###############################################
132
+ # check that all the validations are good
133
+ #
134
+ # @param obj [Object] object to validate
135
+ # @param val [Hash] options
136
+ # @option val [Array<Hash>] :check validations to check
137
+ # @option val [Boolean] :all Always check all the validations
138
+ # @return [Array, NilClass] error descriptions
139
+ #
140
+ def self.all(obj, val)
141
+ return nil unless val[:check].is_a?(Array)
142
+
143
+ errors = []
144
+ bad = false
145
+
146
+ val[:check].each do |check|
147
+ err = Validate.check(obj, check)
148
+ if err
149
+ errors << err
150
+ bad = true
151
+ break unless val[:all]
152
+ else
153
+ errors << nil
154
+ end
155
+ end
156
+
157
+ return bad ? errors : nil
158
+ end # def self.all()
159
+
160
+
161
+ ###############################################
162
+ # Check for an exact value
163
+ #
164
+ # @param obj [Object] object to validate
165
+ # @param val [Hash] options
166
+ # @option val [Integer] :check Value to compare
167
+ # @return [String,NilClass] error descriptions
168
+ #
169
+ def self.equals(obj, val)
170
+ if val[:check] == obj
171
+ return nil
172
+ else
173
+ return 'not equal'.freeze
174
+ end
175
+ end # def self.equals()
176
+
177
+
178
+ ###############################################
179
+ # check an integer
180
+ #
181
+ # @param obj [Object] object to validate
182
+ # @param val [Hash] options
183
+ # @option val [Integer] :min Minimum value
184
+ # @option val [Integer] :max Maximum value
185
+ # @return [String,NilClass] error descriptions
186
+ #
187
+ #
188
+ def self.integer(obj, val)
189
+ return 'not an Integer'.freeze unless obj.is_a?(Integer)
190
+
191
+ if val[:min] && obj < val[:min]
192
+ return 'too small: %d < %d'.freeze % [obj, val[:min]]
193
+ end
194
+
195
+ if val[:max] && obj > val[:max]
196
+ return 'too large: %d > %d '.freeze % [obj, val[:max]]
197
+ end
198
+
199
+ return nil
200
+ end # def self.integer()
201
+
202
+
203
+ ###############################################
204
+ # check a float
205
+ #
206
+ # @param obj [Object] object to validate
207
+ # @param val [Hash] options
208
+ # @option val [Float] :min Minimum value
209
+ # @option val [Float] :max Maximum value
210
+ # @return [String,NilClass] error descriptions
211
+ #
212
+ def self.float(obj, val)
213
+ return 'not a Float'.freeze unless obj.is_a?(Float)
214
+
215
+ if val[:min] && obj < val[:min]
216
+ return 'too small: %f < %f'.freeze % [obj, val[:min]]
217
+ end
218
+
219
+ if val[:max] && obj > val[:max]
220
+ return 'too large: %f > %f'.freeze % [obj, val[:max]]
221
+ end
222
+
223
+ return nil
224
+ end # def self.float()
225
+
226
+
227
+ ###############################################
228
+ # check for a type
229
+ #
230
+ # @param obj [Object] object to validate
231
+ # @param val [Hash] options
232
+ # @option val [Class,Array] :type The class or module to check
233
+ # @return [String,NilClass] error descriptions
234
+ #
235
+ def self.type(obj, val)
236
+ if val[:type]
237
+ if val[:type].is_a?(Array)
238
+ val[:type].each{|cl| return nil if obj.is_a?(cl) }
239
+ return 'not a listed type'.freeze
240
+ else
241
+ if !obj.is_a?(val[:type])
242
+ return 'not a %s'.freeze % val[:type].name
243
+ end
244
+ end
245
+ end
246
+ return nil
247
+ end # def self.type
248
+
249
+
250
+ ###############################################
251
+ # check a string
252
+ #
253
+ # @param obj [Object] object to validate
254
+ # @param val [Hash] options
255
+ # @option val [#include?] :allowed Value which is always okay
256
+ # @option val [#match] :valid check for okay value
257
+ # @option val [Boolean] :whitelist Must be valid or allowed
258
+ # @option val [#match] :invalid check for bad values
259
+ # @option val [Integer] :min Minimum length
260
+ # @option val [Integer] :max Maximum length
261
+ # @return [Hash,NilClass] error descriptions
262
+ #
263
+ def self.string(obj, val)
264
+
265
+ # type
266
+ return 'not a String'.freeze unless obj.is_a?(String)
267
+
268
+ errors = {}
269
+
270
+ # good values
271
+ if (val[:allowed] && val[:allowed].include?(obj)) ||
272
+ (val[:valid] && val[:valid].match(obj))
273
+ return nil
274
+ end
275
+
276
+ # if whitelisting
277
+ if val[:whitelist]
278
+ errors[:whitelist] = 'Value was not whitelisted'.freeze
279
+ end
280
+
281
+ # min length
282
+ if val[:min] && obj.size < val[:min]
283
+ errors[:min] = 'too short: %d < %d' % [obj.size, val[:min]]
284
+ end
285
+
286
+ # max length
287
+ if val[:max] && obj.size > val[:max]
288
+ errors[:max] = 'too long: %d > %d' % [obj.size, val[:max]]
289
+ end
290
+
291
+ # invalid
292
+ if val[:invalid] && val[:invalid].match(obj)
293
+ errors[:invalid] = true
294
+ end
295
+
296
+ return errors.empty? ? nil : errors
297
+ end # def self.string()
298
+
299
+
300
+ ###############################################
301
+ # check an array
302
+ #
303
+ # @param obj [Object] object to validate
304
+ # @param val [Hash] options
305
+ # @option val [Integer] :min Minimum length
306
+ # @option val [Integer] :max Maximum length
307
+ # @option val [TrueClass] :uniq Require all members to be unique
308
+ # @option val [Hash,Array] :check Validations for members of the array.
309
+ # If a Hash is provided, all members will be checked against it.
310
+ # If an Array is provided, they will be checked in order.
311
+ # @return [Hash,NilClass] error descriptions
312
+ #
313
+ def self.array(obj, val)
314
+
315
+ # type
316
+ return 'not an Array'.freeze unless obj.is_a?(Array)
317
+
318
+ errors = {}
319
+
320
+ # min size
321
+ if val[:min] && obj.size < val[:min]
322
+ errors[:min] = true
323
+ end
324
+
325
+ # max size
326
+ if val[:max] && obj.size > val[:max]
327
+ errors[:max] = true
328
+ end
329
+
330
+ # all members uniq
331
+ if val[:uniq] && obj.size != obj.uniq.size
332
+ errors[:uniq] = true
333
+ end
334
+
335
+ # single check, all items of the array
336
+ if val[:check].is_a?(Hash)
337
+ check = val[:check]
338
+
339
+ # each value
340
+ obj.each_index do |ix|
341
+ if val[ix]
342
+ err = Validate.check(obj[ix], val[ix])
343
+ else
344
+ err = Validate.check(obj[ix], check)
345
+ end
346
+ errors[ix] = err if err
347
+ end
348
+
349
+ # an array of checks
350
+ elsif val[:check].is_a?(Array)
351
+ cka = val[:check]
352
+ cs = cka.size
353
+
354
+ # each value
355
+ obj.each_index do |ix|
356
+ if val[ix]
357
+ err = Validate.check(obj[ix], val[ix])
358
+ else
359
+ err = Validate.check(obj[ix], cka[ix % cs])
360
+ end
361
+ errors[ix] = err if err
362
+ end
363
+ end
364
+
365
+ return errors.empty? ? nil : errors
366
+ end # def self.array()
367
+
368
+
369
+ ###############################################
370
+ # check a hash
371
+ #
372
+ # @param obj [Object] object to validate
373
+ # @param val [Hash] options
374
+ # @option val [Hash] :required Keys which must be present and their checks
375
+ # @option val [Hash] :optional Keys which may be present and their checks
376
+ # @option val [TrueClass] :others Allow other keys
377
+ # @return [Hash,NilClass] error descriptions
378
+ #
379
+ def self.hash(obj, val)
380
+
381
+ # type
382
+ return 'not a Hash'.freeze unless obj.is_a?(Hash)
383
+
384
+ ary = obj.to_a
385
+ chk = Array.new(ary.size)
386
+ errors = {}
387
+
388
+ # check all required keys
389
+ if val[:required]
390
+ val[:required].each do |key, check|
391
+
392
+ # find the index
393
+ ix = ary.index{|ok, ov| ok == key }
394
+
395
+ # missing required key
396
+ if ix.nil?
397
+ errors[key] = 'missing'.freeze
398
+ next
399
+ end
400
+
401
+ # check it
402
+ err = Validate.check(ary[ix][1], check)
403
+ errors[key] = err if err
404
+ chk[ix] = true
405
+ end
406
+ end
407
+
408
+ # check all optional keys
409
+ if val[:optional]
410
+ val[:optional].each do |key, check|
411
+
412
+ # find the index
413
+ ix = ary.index{|ok, ov| ok == key }
414
+ next if ix.nil?
415
+
416
+ # do the check
417
+ err = Validate.check(ary[ix][1], check)
418
+ errors[key] = err if err
419
+ chk[ix] = true
420
+ end
421
+ end
422
+
423
+ # make sure we have validated all keys
424
+ if !val[:others]
425
+ chk.each_index do |ix|
426
+ next if chk[ix]
427
+ errors[ary[ix][0]] = 'not allowed'.freeze
428
+ end
429
+ end
430
+
431
+ # do we have any errors?
432
+ return errors.empty? ? nil : errors
433
+
434
+ end # def self.hash()
435
+
436
+
437
+ ##############################################################
438
+ # Common checks
439
+ ##############################################################
440
+
441
+
442
+ # Boolean
443
+ IsBoolean = {
444
+ method: :type,
445
+ type: [ TrueClass, FalseClass ].freeze,
446
+ }.freeze
447
+
448
+
449
+ # Tempfile
450
+ IsTempfile = {
451
+ method: :type,
452
+ type: Tempfile,
453
+ }.freeze
454
+
455
+
456
+ # Float
457
+ IsFloat = {
458
+ method: :type,
459
+ type: [ Float ].freeze
460
+ }.freeze
461
+
462
+
463
+ # Positive Integer
464
+ IsIntPos = {
465
+ method: :integer,
466
+ min: 1
467
+ }.freeze
468
+
469
+
470
+ # Unsigned Integer
471
+ IsIntUns = {
472
+ method: :integer,
473
+ min: 0
474
+ }.freeze
475
+
476
+
477
+ end # module ICFS::Validate
478
+
479
+ end # module ICFS