icfs 0.1.0

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.
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