icfs 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/icfs/store.rb CHANGED
@@ -220,8 +220,7 @@ class Store
220
220
  # Path for case
221
221
  #
222
222
  def _case(cid, lnum)
223
- [
224
- @base,
223
+ @base + [
225
224
  cid,
226
225
  'c'.freeze,
227
226
  '%d.json'.freeze % lnum
@@ -233,8 +232,7 @@ class Store
233
232
  # Path for log
234
233
  #
235
234
  def _log(cid, lnum)
236
- [
237
- @base,
235
+ @base + [
238
236
  cid,
239
237
  'l'.freeze,
240
238
  '%d.json'.freeze % lnum
@@ -246,8 +244,7 @@ class Store
246
244
  # Path for entry
247
245
  #
248
246
  def _entry(cid, enum, lnum)
249
- [
250
- @base,
247
+ @base + [
251
248
  cid,
252
249
  'e'.freeze,
253
250
  enum.to_s,
@@ -260,8 +257,7 @@ class Store
260
257
  # Path for file
261
258
  #
262
259
  def _file(cid, enum, lnum, fnum)
263
- [
264
- @base,
260
+ @base + [
265
261
  cid,
266
262
  'e'.freeze,
267
263
  enum.to_s,
@@ -274,8 +270,7 @@ class Store
274
270
  # Filename for action
275
271
  #
276
272
  def _action(cid, anum, lnum)
277
- [
278
- @base,
273
+ @base + [
279
274
  cid,
280
275
  'a'.freeze,
281
276
  anum.to_s,
@@ -288,8 +283,7 @@ class Store
288
283
  # Filename for index
289
284
  #
290
285
  def _index(cid, xnum, lnum)
291
- [
292
- @base,
286
+ @base + [
293
287
  cid,
294
288
  'i'.freeze,
295
289
  xnum.to_s,
data/lib/icfs/store_fs.rb CHANGED
@@ -29,7 +29,11 @@ class StoreFs < Store
29
29
  # @param base [String] the base directory
30
30
  #
31
31
  def initialize(base)
32
- @base = base.freeze
32
+ if base[-1] == '/'.freeze
33
+ @base = base.freeze
34
+ else
35
+ @base = (base + '/').freeze
36
+ end
33
37
  end
34
38
 
35
39
 
data/lib/icfs/users.rb CHANGED
@@ -16,11 +16,8 @@ module ICFS
16
16
  ##########################################################################
17
17
  # User, Role, Group, and Global Perms
18
18
  #
19
- #
20
19
  # @abstract
21
20
  #
22
- # @todo Add cache flush method
23
- #
24
21
  class Users
25
22
 
26
23
  ###############################################
data/lib/icfs/users_fs.rb CHANGED
@@ -11,7 +11,9 @@
11
11
 
12
12
  require 'tempfile'
13
13
  require 'fileutils'
14
- require 'set'
14
+ require 'json'
15
+
16
+ require_relative 'users'
15
17
 
16
18
  module ICFS
17
19
 
@@ -20,22 +22,6 @@ module ICFS
20
22
  #
21
23
  class UsersFs < Users
22
24
 
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 = Items.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
25
 
40
26
  ###############################################
41
27
  # New instance
@@ -47,63 +33,26 @@ class UsersFs < Users
47
33
  end
48
34
 
49
35
 
36
+ ###############################################
37
+ # Path to store the file
38
+ #
39
+ def _path(urg)
40
+ File.join(@path, urg + '.json'.freeze)
41
+ end
42
+ private :_path
43
+
44
+
50
45
  ###############################################
51
46
  # (see Users#read)
52
47
  #
53
48
  def read(urg)
54
- Items.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
49
+ Items.validate(urg, 'User/Role/Group name'.freeze, Items::FieldUsergrp)
50
+ json = File.read(_path(urg))
51
+ obj = Items.parse(json, 'User/Role/Group'.freeze, Users::ValUser)
52
+ if obj['name'] != urg
53
+ raise(Error::Values, 'UsersFs user %s name mismatch'.freeze % fn)
96
54
  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
-
55
+ return obj
107
56
  rescue Errno::ENOENT
108
57
  return nil
109
58
  end # def read()
@@ -122,7 +71,7 @@ class UsersFs < Users
122
71
  tmp.close
123
72
 
124
73
  # move
125
- FileUtils.mv(tmp.path, File.join(@path, obj['name'] + '.json'.freeze))
74
+ FileUtils.mv(tmp.path, _path(obj['name']))
126
75
  tmp.unlink
127
76
  end # def write()
128
77
 
@@ -0,0 +1,127 @@
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 'redis'
13
+
14
+ module ICFS
15
+
16
+ ##########################################################################
17
+ # Implement Users with a Redis cache
18
+ #
19
+ class UsersRedis < Users
20
+
21
+ ###############################################
22
+ # New instance
23
+ #
24
+ # @param redis [Redis] The redis client
25
+ # @param base [Users] The base Users store
26
+ # @param opts [Hash] Options
27
+ # @option opts [String] :prefix Prefix for Redis key
28
+ # @option opts [Integer] :expires Expiration time in seconds
29
+ #
30
+ def initialize(redis, base, opts={})
31
+ @redis = redis
32
+ @base = base
33
+ @pre = opts[:prefix] || ''.freeze
34
+ @exp = opts[:expires] || 1*60*60 # 1 hour default
35
+ end
36
+
37
+
38
+ ###############################################
39
+ # Where to store in Redis
40
+ #
41
+ def _key(urg)
42
+ @pre + urg
43
+ end
44
+ private :_key
45
+
46
+
47
+ ###############################################
48
+ # (see Users#read)
49
+ #
50
+ def read(urg)
51
+ Validate.check(urg, Items::FieldUsergrp) # FIXME
52
+ key = _key(urg)
53
+
54
+ # try cache
55
+ json = @redis.get(key)
56
+ return JSON.parse(json) if json
57
+
58
+ # get base object from base store
59
+ bse = @base.read(urg)
60
+ return nil if !bse
61
+
62
+ # assemble
63
+ seen = Set.new.add(urg)
64
+ ary = []
65
+ roles = Set.new
66
+ grps = Set.new
67
+ perms = Set.new
68
+ if bse['roles']
69
+ ary.concat bse['roles']
70
+ roles.merge bse['roles']
71
+ end
72
+ if bse['groups']
73
+ ary.concat bse['groups']
74
+ grps.merge bse['groups']
75
+ end
76
+ if bse['perms']
77
+ perms.merge bse['perms']
78
+ end
79
+
80
+ # call ourself recursively for any un-expanded memberships
81
+ while itm = ary.shift
82
+ next if seen.include?(itm)
83
+ seen.add(itm)
84
+ ikey = _key(itm)
85
+
86
+ # all included u/r/g have been seen & expanded
87
+ mem = self.read(itm)
88
+ next if !mem
89
+ if mem['roles']
90
+ roles.merge mem['roles']
91
+ seen.merge mem['roles']
92
+ end
93
+ if mem['groups']
94
+ grps.merge mem['groups']
95
+ seen.merge mem['groups']
96
+ end
97
+ if mem['perms']
98
+ perms.merge mem['perms']
99
+ end
100
+ end
101
+
102
+ # final result
103
+ bse['roles'] = roles.to_a unless roles.empty?
104
+ bse['groups'] = grps.to_a unless grps.empty?
105
+ bse['perms'] = perms.to_a unless perms.empty?
106
+ json = JSON.pretty_generate(bse)
107
+
108
+ # save to cache
109
+ @redis.set(key, json)
110
+ @redis.expire(key, @exp)
111
+ return bse
112
+ end # def read()
113
+
114
+
115
+ ###############################################
116
+ # (see Users#write)
117
+ #
118
+ def write(obj)
119
+ json = Items.generate(obj, 'User/Role/Group'.freeze, Users::ValUser)
120
+ key = _key(obj['name'])
121
+ @redis.del(key)
122
+ @base.write(obj)
123
+ end # def write()
124
+
125
+ end # class ICFS::UsersRedis
126
+
127
+ end # module ICFS
@@ -0,0 +1,68 @@
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 'aws-sdk-s3'
13
+
14
+ module ICFS
15
+
16
+ ##########################################################################
17
+ # Implements {ICFS::Users Users} from AWS S3
18
+ #
19
+ class UsersS3 < Users
20
+
21
+ ###############################################
22
+ # New instance
23
+ #
24
+ # @param s3 [Aws::S3::Client] the configured S3 client
25
+ # @param bucket [String] The bucket name
26
+ # @param prefix [String] Prefix to use for object keys
27
+ #
28
+ def initialize(s3, bucket, prefix=nil)
29
+ @s3 = s3
30
+ @bck = bucket
31
+ @pre = prefix || ''.freeze
32
+ end
33
+
34
+
35
+ ###############################################
36
+ # Where to store onfig
37
+ #
38
+ def _path(user)
39
+ @pre + user
40
+ end # def _path()
41
+ private :_path
42
+
43
+
44
+ ###############################################
45
+ # (see Users#read)
46
+ #
47
+ def read(urg)
48
+ Items.validate(urg, 'User/Role/Group name'.freeze, Items::FieldUsergrp)
49
+ json = @s3.get_object( bucket: @bck, key: _path(urg) ).body.read
50
+ return JSON.parse(json)
51
+ rescue
52
+ return nil
53
+ end # def read()
54
+
55
+
56
+ ###############################################
57
+ # (see Users#write)
58
+ #
59
+ def write(obj)
60
+ Items.validate(obj, 'User/Role/Group'.freeze, Users::ValUser)
61
+ json = JSON.pretty_generate(obj)
62
+ @s3.put_object( bucket: @bck, key: _path(obj['name']), body: json )
63
+ end # def write()
64
+
65
+
66
+ end # class ICFS::UsersS3
67
+
68
+ end # module ICFS
@@ -10,6 +10,7 @@
10
10
  # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
11
 
12
12
  require_relative '../store_fs'
13
+
13
14
  #
14
15
  module ICFS
15
16
 
@@ -20,6 +21,7 @@ module Utils
20
21
  #
21
22
  class Backup
22
23
 
24
+
23
25
  ###############################################
24
26
  # Instance
25
27
  #
@@ -37,7 +39,7 @@ class Backup
37
39
  ###############################################
38
40
  # Copy an item
39
41
  #
40
- def _item(dest, title, read, write, args, val=nil)
42
+ def _copy_item(dest, title, read, write, args, val=nil)
41
43
  @log.debug('ICFS copy: %s'.freeze % title)
42
44
 
43
45
  # read the item
@@ -65,7 +67,7 @@ class Backup
65
67
  @log.error('ICFS copy: %s bad JSON'.freeze % title)
66
68
  return nil
67
69
  end # def _item
68
- private :_item
70
+ private :_copy_item
69
71
 
70
72
 
71
73
  ###############################################
@@ -76,8 +78,16 @@ class Backup
76
78
  # @param lnum_max [Integer] The highest log
77
79
  # @param lnum_min [Integer] The lowest log
78
80
  #
79
- def copy(cid, dest, lnum_max, lnum_min=1)
81
+ def copy(cid, dest, lnum_min=1, lnum_max=0)
80
82
  @log.info('ICFS copy: %s %d-%d'.freeze % [cid, lnum_min, lnum_max])
83
+
84
+ # if no max specified, pull from current
85
+ if lnum_max == 0
86
+ json = @cache.current_read(cid)
87
+ cur = Items.parse(json, 'current'.freeze, Items::ItemCurrent)
88
+ lnum_max = cur['log']
89
+ end
90
+
81
91
  if lnum_min > lnum_max
82
92
  raise ArgumentError, 'ICFS copy, log num min is larger than max'.freeze
83
93
  end
@@ -105,7 +115,7 @@ class Backup
105
115
  'entry %d-%d'.freeze % [enum, lnum],
106
116
  :entry_read,
107
117
  :entry_write,
108
- [cid, lnum]
118
+ [cid, enum, lnum]
109
119
  )
110
120
 
111
121
  # index
@@ -121,7 +131,7 @@ class Backup
121
131
 
122
132
  # action
123
133
  if log['action']
124
- anum = log['action']['lnum']
134
+ anum = log['action']['num']
125
135
  _copy_item(dest,
126
136
  'action %d-%d'.freeze % [anum, lnum],
127
137
  :action_read,
@@ -133,7 +143,7 @@ class Backup
133
143
  # case
134
144
  if log['case_hash']
135
145
  _copy_item(dest,
136
- 'case %d'.freeze, % lnum,
146
+ 'case %d'.freeze % lnum,
137
147
  :case_read,
138
148
  :case_write,
139
149
  [cid, lnum]
@@ -142,7 +152,8 @@ class Backup
142
152
 
143
153
  # files
144
154
  if log['files_hash']
145
- log['files_hash'].each_index do |fnum|
155
+ log['files_hash'].each_index do |fraw|
156
+ fnum = fraw + 1
146
157
 
147
158
  @log.debug('ICFS copy: file %d-%d-%d'.freeze % [enum, lnum, fnum])
148
159
 
@@ -170,6 +181,200 @@ class Backup
170
181
  end # def copy()
171
182
 
172
183
 
184
+ ###############################################
185
+ # Restore an item
186
+ #
187
+ def _restore_item(src, title, read, write, args_st, args_ca, val=nil)
188
+ @log.debug('ICFS restore: %s'.freeze % title)
189
+
190
+ # read the item
191
+ json = src.send(read, *args_st)
192
+ if !json
193
+ @log.warn('ICFS restore: %s is missing'.freeze % title)
194
+ return nil
195
+ end
196
+
197
+ # parse item if requested
198
+ if val
199
+ obj = JSON.parse(json)
200
+ err = Validate.check(obj, val)
201
+ if err
202
+ @log.error('ICFS restore: %s bad format'.freeze % title)
203
+ return nil
204
+ end
205
+ end
206
+
207
+ # write the item to store & cache
208
+ @store.send(write, *args_st, json)
209
+ @cache.send(write, *args_ca, json)
210
+ return [obj, json]
211
+
212
+ end # def _restore_item()
213
+ private :_restore_item
214
+
215
+
216
+ ###############################################
217
+ # Restore a backup into a case
218
+ #
219
+ # @param cid [String] The case ID
220
+ # @param src [Store] Source store
221
+ # @param lnum_max [Integer] The highest log
222
+ # @param lnum_min [Integer] The lowest log
223
+ #
224
+ def restore(cid, src, lnum_min=0, lnum_max=0)
225
+ @log.info('ICFS restore: %s %d-%d'.freeze % [cid, lnum_min, lnum_max])
226
+
227
+ # take lock
228
+ @cache.lock_take(cid)
229
+ begin
230
+
231
+ # read current
232
+ json = @cache.current_read(cid)
233
+ if json
234
+ cur = Items.parse(json, 'current'.freeze, Items::ItemCurrent)
235
+ else
236
+ cur = {
237
+ 'icfs' => 1,
238
+ 'caseid' => cid,
239
+ 'log' => 1,
240
+ 'entry' => 1,
241
+ 'action' => 0,
242
+ 'index' => 0
243
+ }
244
+ end
245
+
246
+ # if no min specified, pull from current or default to 1
247
+ lnum_min = cur['log'] if lnum_min == 0
248
+
249
+ # sanity check min & max
250
+ if (lnum_min > lnum_max) && (lnum_max != 0)
251
+ raise ArgumentError: 'ICFS restore, log min is larger than max'.freeze
252
+ end
253
+
254
+ # max entry, action, index
255
+ emax = cur['entry']
256
+ amax = cur['action']
257
+ imax = cur['index']
258
+
259
+ # each log
260
+ lnum = lnum_min
261
+ llast = nil
262
+ while lnum != lnum_max
263
+
264
+ # copy the log
265
+ log, litem = _restore_item(src,
266
+ 'log %d'.freeze % lnum,
267
+ :log_read,
268
+ :log_write,
269
+ [cid, lnum],
270
+ [cid, lnum],
271
+ Items::ItemLog
272
+ )
273
+
274
+ # no log - all done
275
+ if !log
276
+ break
277
+ else
278
+ llast = litem
279
+ end
280
+
281
+ # entry
282
+ enum = log['entry']['num']
283
+ _restore_item(src,
284
+ 'entry %d-%d'.freeze % [enum, lnum],
285
+ :entry_read,
286
+ :entry_write,
287
+ [cid, enum, lnum],
288
+ [cid, enum]
289
+ )
290
+ emax = enum if enum > emax
291
+
292
+ # index
293
+ if log['index']
294
+ xnum = log['index']['num']
295
+ _restore_item(src,
296
+ 'index %d-%d'.freeze % [xnum, lnum],
297
+ :index_read,
298
+ :index_write,
299
+ [cid, xnum, lnum],
300
+ [cid, xnum]
301
+ )
302
+ imax = xnum if xnum > imax
303
+ end
304
+
305
+ # action
306
+ if log['action']
307
+ anum = log['action']['num']
308
+ _restore_item(src,
309
+ 'action %d-%d'.freeze % [anum, lnum],
310
+ :action_read,
311
+ :action_write,
312
+ [cid, anum, lnum],
313
+ [cid, anum]
314
+ )
315
+ amax = anum if anum > amax
316
+ end
317
+
318
+ # case
319
+ if log['case_hash']
320
+ _restore_item(src,
321
+ 'case %d'.freeze % lnum,
322
+ :case_read,
323
+ :case_write,
324
+ [cid, lnum],
325
+ [cid]
326
+ )
327
+ end
328
+
329
+ # files
330
+ if log['files_hash']
331
+ log['files_hash'].each_index do |fraw|
332
+ fnum = fraw + 1
333
+
334
+ @log.debug('ICFS restore: file %d-%d-%d'.freeze % [enum, lnum, fnum])
335
+
336
+ # read
337
+ fi = src.file_read(cid, enum, lnum, fnum)
338
+ if !fi
339
+ @log.warn('ICFS restore: file %d-%d-%d missing'.freeze %
340
+ [enum, lnum, fnum])
341
+ next
342
+ end
343
+
344
+ # copy
345
+ tmp = @store.tempfile
346
+ IO.copy_stream(fi, tmp)
347
+ src.close(fi)
348
+
349
+ # write
350
+ @store.file_write(cid, enum, lnum, fnum, tmp)
351
+ end
352
+ end
353
+
354
+ lnum += 1
355
+ end
356
+
357
+ # write current
358
+ cur = {
359
+ 'icfs' => 1,
360
+ 'caseid' => cid,
361
+ 'log' => lnum-1,
362
+ 'entry' => emax,
363
+ 'action' => amax,
364
+ 'index' => imax,
365
+ 'hash' => ICFS.hash(llast)
366
+ }
367
+ nitem = Items.generate(cur, 'current'.freeze, Items::ItemCurrent)
368
+ @cache.current_write(cid, nitem)
369
+
370
+ ensure
371
+ # release lock
372
+ @cache.lock_release(cid)
373
+ end
374
+
375
+ end # def restore()
376
+
377
+
173
378
  end # class ICFS::Utils::Backup
174
379
 
175
380
  end # module ICFS::Utils
@@ -16,16 +16,12 @@ module ICFS
16
16
  ##########################################################################
17
17
  # Low level utilities for working directly with ICFS systems.
18
18
  #
19
- # @todo Archive/move case tool written
20
- #
21
19
  module Utils
22
20
 
23
21
 
24
22
  ##########################################################################
25
23
  # Check a case for errors
26
24
  #
27
- # @todo Make a function to auto find the last log
28
- #
29
25
  class Check
30
26
 
31
27
 
@@ -180,7 +176,7 @@ class Check
180
176
  ['index'.freeze, 1].freeze,
181
177
  ['log'.freeze, 2].freeze
182
178
  ]
183
- )
179
+ )
184
180
  idx_cur.add(xnum)
185
181
  end
186
182