icfs 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/icfs_demo_check.rb +29 -0
- data/data/docker/build-web.sh +27 -0
- data/data/docker/compose-demo.yml +41 -0
- data/data/docker/compose-init.yml +32 -0
- data/data/docker/icfs-app.rb +50 -0
- data/data/docker/icfs-cfg.yml +88 -0
- data/data/docker/icfs-init.rb +75 -0
- data/data/docker/icfs-ruby/Dockerfile +22 -0
- data/data/docker/icfs-ruby/build.sh +14 -0
- data/data/docker/nginx.conf +68 -0
- data/lib/icfs.rb +2 -3
- data/lib/icfs/api.rb +41 -41
- data/lib/icfs/cache_elastic.rb +6 -4
- data/lib/icfs/items.rb +62 -1
- data/lib/icfs/store.rb +25 -0
- data/lib/icfs/store_fs.rb +10 -0
- data/lib/icfs/store_s3.rb +11 -0
- data/lib/icfs/users.rb +2 -0
- data/lib/icfs/users_elastic.rb +5 -3
- data/lib/icfs/users_fs.rb +3 -3
- data/lib/icfs/utils/backup.rb +177 -0
- data/lib/icfs/utils/check.rb +290 -0
- data/lib/icfs/validate.rb +0 -63
- data/lib/icfs/web/auth_ssl.rb +10 -2
- data/lib/icfs/web/client.rb +2 -2
- metadata +14 -2
data/lib/icfs.rb
CHANGED
@@ -11,12 +11,12 @@
|
|
11
11
|
|
12
12
|
require 'digest/sha2'
|
13
13
|
|
14
|
+
require_relative 'icfs/validate'
|
15
|
+
|
14
16
|
##########################################################################
|
15
17
|
# Investigative Case File System
|
16
18
|
#
|
17
19
|
# @todo Delete Items and move into this module
|
18
|
-
# @todo Verification tool written
|
19
|
-
# @todo Archive/move case tool written
|
20
20
|
#
|
21
21
|
module ICFS
|
22
22
|
|
@@ -101,7 +101,6 @@ end # module ICFS::Error
|
|
101
101
|
|
102
102
|
end # module ICFS
|
103
103
|
|
104
|
-
require_relative 'icfs/validate'
|
105
104
|
require_relative 'icfs/cache'
|
106
105
|
require_relative 'icfs/store'
|
107
106
|
require_relative 'icfs/items'
|
data/lib/icfs/api.rb
CHANGED
@@ -265,12 +265,12 @@ class Api
|
|
265
265
|
def case_read(cid, lnum=0)
|
266
266
|
if lnum != 0
|
267
267
|
json = @store.case_read(cid, lnum)
|
268
|
-
return
|
268
|
+
return Items.parse(json, 'case'.freeze, Items::ItemCase)
|
269
269
|
end
|
270
270
|
|
271
271
|
if !@cases.key?(cid)
|
272
272
|
json = @cache.case_read(cid)
|
273
|
-
cur =
|
273
|
+
cur = Items.parse(json, 'case'.freeze, Items::ItemCase)
|
274
274
|
@cases[cid] = cur
|
275
275
|
end
|
276
276
|
return @cases[cid]
|
@@ -295,7 +295,7 @@ class Api
|
|
295
295
|
|
296
296
|
# read
|
297
297
|
json = @cache.log_read(cid, lnum)
|
298
|
-
return
|
298
|
+
return Items.parse(json, 'log'.freeze, Items::ItemLog)
|
299
299
|
end # def log_read()
|
300
300
|
|
301
301
|
|
@@ -313,7 +313,7 @@ class Api
|
|
313
313
|
# get access list and current entry
|
314
314
|
al = access_list(cid)
|
315
315
|
json = @cache.entry_read(cid, enum)
|
316
|
-
ec =
|
316
|
+
ec = Items.parse(json, 'entry'.freeze, Items::ItemEntry)
|
317
317
|
|
318
318
|
# see if we can read the entry
|
319
319
|
need = Set.new
|
@@ -330,7 +330,7 @@ class Api
|
|
330
330
|
return ec
|
331
331
|
else
|
332
332
|
json = @store.entry_read(cid, enum, lnum)
|
333
|
-
return
|
333
|
+
return Items.parse(json, 'entry'.freeze, Items::ItemEntry)
|
334
334
|
end
|
335
335
|
end # def entry_read()
|
336
336
|
|
@@ -362,7 +362,7 @@ class Api
|
|
362
362
|
id = '%s.%d'.freeze % [cid, anum]
|
363
363
|
unless @actions.key?(id)
|
364
364
|
json = @cache.action_read(cid, anum)
|
365
|
-
act =
|
365
|
+
act = Items.parse(json, 'action'.freeze, Items::ItemAction)
|
366
366
|
@actions[id] = act
|
367
367
|
end
|
368
368
|
return @actions[id]
|
@@ -395,7 +395,7 @@ class Api
|
|
395
395
|
return ac
|
396
396
|
else
|
397
397
|
json = @store.action_read( cid, anum, lnum)
|
398
|
-
return
|
398
|
+
return Items.parse(json, 'action'.freeze, Items::ItemAction)
|
399
399
|
end
|
400
400
|
end # def action_read()
|
401
401
|
|
@@ -419,14 +419,14 @@ class Api
|
|
419
419
|
|
420
420
|
# read curent index
|
421
421
|
json = @cache.index_read(cid, xnum)
|
422
|
-
xc =
|
422
|
+
xc = Items.parse(json, 'index'.freeze, Items::ItemIndex)
|
423
423
|
|
424
424
|
# return the requested version
|
425
425
|
if( lnum == 0 || xc['log'] == lnum )
|
426
426
|
return xc
|
427
427
|
else
|
428
428
|
json = @store.index_read(cid, xnum, lnum)
|
429
|
-
return
|
429
|
+
return Items.parse(json, 'index'.freeze, Items::ItemIndex)
|
430
430
|
end
|
431
431
|
end # def index_read()
|
432
432
|
|
@@ -444,7 +444,7 @@ class Api
|
|
444
444
|
end
|
445
445
|
|
446
446
|
json = @cache.current_read(cid)
|
447
|
-
return
|
447
|
+
return Items.parse(json, 'current'.current, Items::ItemCurrent)
|
448
448
|
end # end def current_read()
|
449
449
|
|
450
450
|
|
@@ -476,7 +476,7 @@ class Api
|
|
476
476
|
# @param query [Hash] a query
|
477
477
|
#
|
478
478
|
def case_search(query)
|
479
|
-
|
479
|
+
Items.validate(query, 'Case Search'.freeze, ValCaseSearch)
|
480
480
|
@cache.case_search(query)
|
481
481
|
end
|
482
482
|
|
@@ -513,7 +513,7 @@ class Api
|
|
513
513
|
# @param query [Hash] a query
|
514
514
|
#
|
515
515
|
def log_search(query)
|
516
|
-
|
516
|
+
Items.validate(query, 'Log Search'.freeze, ValLogSearch)
|
517
517
|
@cache.log_search(query)
|
518
518
|
end
|
519
519
|
|
@@ -552,7 +552,7 @@ class Api
|
|
552
552
|
# Search for entries
|
553
553
|
#
|
554
554
|
def entry_search(query)
|
555
|
-
|
555
|
+
Items.validate(query, 'Entry Search'.freeze, ValEntrySearch)
|
556
556
|
|
557
557
|
# check permissions
|
558
558
|
# - have global search permissions / read access to the case
|
@@ -640,7 +640,7 @@ class Api
|
|
640
640
|
# Search for actions
|
641
641
|
#
|
642
642
|
def action_search(query)
|
643
|
-
|
643
|
+
Items.validate(query, 'Action Search'.freeze, ValActionSearch)
|
644
644
|
|
645
645
|
# permissions check
|
646
646
|
# - have global search permissions / read access to the case
|
@@ -686,7 +686,7 @@ class Api
|
|
686
686
|
# Search for indexes
|
687
687
|
#
|
688
688
|
def index_search(query)
|
689
|
-
|
689
|
+
Items.validate(query, 'Index Search'.freeze, ValIndexSearch)
|
690
690
|
|
691
691
|
# permissions check
|
692
692
|
# - have global search permissions / read access to the case
|
@@ -701,7 +701,7 @@ class Api
|
|
701
701
|
res[:list].each do |se|
|
702
702
|
idx = se[:object]
|
703
703
|
|
704
|
-
unless access_list(idx[:caseid].include?(ICFS::PermRead)
|
704
|
+
unless access_list(idx[:caseid]).include?(ICFS::PermRead)
|
705
705
|
idx[:title] = nil
|
706
706
|
idx[:tags] = nil
|
707
707
|
end
|
@@ -728,7 +728,7 @@ class Api
|
|
728
728
|
# Analyze stats
|
729
729
|
#
|
730
730
|
def stats(query)
|
731
|
-
|
731
|
+
Items.validate(query, 'Stats Search'.freeze, ValStatsSearch)
|
732
732
|
|
733
733
|
# permissions check
|
734
734
|
# - have global search permissions / read access to the case
|
@@ -757,7 +757,7 @@ class Api
|
|
757
757
|
# Get case tags
|
758
758
|
#
|
759
759
|
def case_tags(query)
|
760
|
-
|
760
|
+
Items.validate(query, 'Case Tags Search'.freeze, ValCaseTags)
|
761
761
|
return @cache.case_tags(query)
|
762
762
|
end # def case_tags()
|
763
763
|
|
@@ -778,7 +778,7 @@ class Api
|
|
778
778
|
# Get entry tags
|
779
779
|
#
|
780
780
|
def entry_tags(query)
|
781
|
-
|
781
|
+
Items.validate(query, 'Entry Tags Search'.freeze, ValEntryTags)
|
782
782
|
|
783
783
|
# permissions
|
784
784
|
# - read access to case
|
@@ -819,7 +819,7 @@ class Api
|
|
819
819
|
# Get action tags
|
820
820
|
#
|
821
821
|
def action_tags(query)
|
822
|
-
|
822
|
+
Items.validate(query, 'Task Tags Search'.freeze, ValActionTags)
|
823
823
|
|
824
824
|
# only allow searches for user/roles you have
|
825
825
|
unless @ur.include?(query[:assigned]) ||
|
@@ -849,7 +849,7 @@ class Api
|
|
849
849
|
# Get index tags
|
850
850
|
#
|
851
851
|
def index_tags(query)
|
852
|
-
|
852
|
+
Items.validate(query, 'Index Tags'.freeze, ValIndexTags)
|
853
853
|
unless access_list(query[:caseid]).include?(ICFS::PermRead)
|
854
854
|
raise(Error::Perms, 'missing perms: %s'.freeze % ICFS::PermRead)
|
855
855
|
end
|
@@ -875,8 +875,8 @@ class Api
|
|
875
875
|
# Sanity checks
|
876
876
|
|
877
877
|
# form & values
|
878
|
-
|
879
|
-
|
878
|
+
Items.validate(ent, 'entry'.freeze, Items::ItemEntryNew)
|
879
|
+
Items.validate(cse, 'case'.freeze, Items::ItemCaseEdit)
|
880
880
|
|
881
881
|
# access users/roles/groups are valid
|
882
882
|
cse["access"].each do |acc|
|
@@ -923,7 +923,7 @@ class Api
|
|
923
923
|
cse['caseid'] = cid
|
924
924
|
cse['log'] = 1
|
925
925
|
cse['tags'] ||= [ ICFS::TagNone ]
|
926
|
-
citem =
|
926
|
+
citem = Items.generate(cse, 'case'.freeze, Items::ItemCase)
|
927
927
|
|
928
928
|
# entry
|
929
929
|
ent['icfs'] = 1
|
@@ -973,12 +973,12 @@ class Api
|
|
973
973
|
# finish items
|
974
974
|
ent['time'] ||= now
|
975
975
|
ent['files'].each{|fi| fi['log'] ||= 1 } if ent['files']
|
976
|
-
eitem =
|
976
|
+
eitem = Items.generate(ent, 'entry'.freeze, Items::ItemEntry)
|
977
977
|
log['time'] = now
|
978
978
|
log['entry']['hash'] = ICFS.hash(eitem)
|
979
|
-
litem =
|
979
|
+
litem = Items.generate(log, 'log'.freeze, Items::ItemLog)
|
980
980
|
cur['hash'] = ICFS.hash(litem)
|
981
|
-
nitem =
|
981
|
+
nitem = Items.generate(cur, 'current'.freeze, Items::ItemCurrent)
|
982
982
|
|
983
983
|
# write to cache
|
984
984
|
@cache.entry_write(cid, 1, eitem)
|
@@ -1017,13 +1017,13 @@ class Api
|
|
1017
1017
|
|
1018
1018
|
# form & content
|
1019
1019
|
if idx || cse
|
1020
|
-
|
1020
|
+
Items.validate(ent, 'New Entry'.freeze, Items::ItemEntryNew)
|
1021
1021
|
else
|
1022
|
-
|
1022
|
+
Items.validate(ent, 'Editable Entry'.freeze, Items::ItemEntryEdit)
|
1023
1023
|
end
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1024
|
+
Items.validate(act, 'action'.freeze, Items::ItemActionEdit) if act
|
1025
|
+
Items.validate(idx, 'index'.freeze, Items::ItemIndexEdit) if idx
|
1026
|
+
Items.validate(cse, 'case'.freeze, Items::ItemCaseEdit) if cse
|
1027
1027
|
|
1028
1028
|
# edit index OR case, not both
|
1029
1029
|
if idx && cse
|
@@ -1133,13 +1133,13 @@ class Api
|
|
1133
1133
|
|
1134
1134
|
# current
|
1135
1135
|
json = @cache.current_read(cid)
|
1136
|
-
cur =
|
1136
|
+
cur = Items.parse(json, 'current'.freeze, Items::ItemCurrent)
|
1137
1137
|
|
1138
1138
|
# entry
|
1139
1139
|
if ent['entry']
|
1140
1140
|
enum = ent['entry']
|
1141
1141
|
json = @cache.entry_read(cid, enum)
|
1142
|
-
ent_pri =
|
1142
|
+
ent_pri = Items.parse(json, 'entry'.freeze, Items::ItemEntry)
|
1143
1143
|
nxt['entry'] = cur['entry']
|
1144
1144
|
else
|
1145
1145
|
enum = cur['entry'] + 1
|
@@ -1156,7 +1156,7 @@ class Api
|
|
1156
1156
|
end
|
1157
1157
|
if anum
|
1158
1158
|
json = @cache.action_read(cid, anum)
|
1159
|
-
act_pri =
|
1159
|
+
act_pri = Items.parse(json, 'action'.freeze, Items::ItemAction)
|
1160
1160
|
nxt['action'] = cur['action']
|
1161
1161
|
elsif act
|
1162
1162
|
anum = cur['action'] + 1
|
@@ -1318,7 +1318,7 @@ class Api
|
|
1318
1318
|
end
|
1319
1319
|
end
|
1320
1320
|
ent['files'].each{|fi| fi['log'] ||= lnum } if ent['files']
|
1321
|
-
eitem =
|
1321
|
+
eitem = Items.generate(ent, 'entry'.freeze, Items::ItemEntry)
|
1322
1322
|
log['entry'] = {
|
1323
1323
|
'num' => enum,
|
1324
1324
|
'hash' => ICFS.hash(eitem)
|
@@ -1328,7 +1328,7 @@ class Api
|
|
1328
1328
|
if act
|
1329
1329
|
act['action'] = anum
|
1330
1330
|
act['log'] = lnum
|
1331
|
-
aitem =
|
1331
|
+
aitem = Items.generate(act, 'action'.freeze, Items::ItemAction)
|
1332
1332
|
log['action'] = {
|
1333
1333
|
'num' => anum,
|
1334
1334
|
'hash' => ICFS.hash(aitem)
|
@@ -1339,7 +1339,7 @@ class Api
|
|
1339
1339
|
if idx
|
1340
1340
|
idx['index'] = xnum
|
1341
1341
|
idx['log'] = lnum
|
1342
|
-
xitem =
|
1342
|
+
xitem = Items.generate(idx, 'index'.freeze, Items::ItemIndex)
|
1343
1343
|
log['index'] = {
|
1344
1344
|
'num' => xnum,
|
1345
1345
|
'hash' => ICFS.hash(xitem)
|
@@ -1349,7 +1349,7 @@ class Api
|
|
1349
1349
|
# case
|
1350
1350
|
if cse
|
1351
1351
|
cse['log'] = lnum
|
1352
|
-
citem =
|
1352
|
+
citem = Items.generate(cse, 'case'.freeze, Items::ItemCase)
|
1353
1353
|
log['case_hash'] = ICFS.hash(citem)
|
1354
1354
|
end
|
1355
1355
|
|
@@ -1357,11 +1357,11 @@ class Api
|
|
1357
1357
|
log['log'] = lnum
|
1358
1358
|
log['prev'] = cur['hash']
|
1359
1359
|
log['time'] = now
|
1360
|
-
litem =
|
1360
|
+
litem = Items.generate(log, 'log'.freeze, Items::ItemLog)
|
1361
1361
|
nxt['hash'] = ICFS.hash(litem)
|
1362
1362
|
|
1363
1363
|
# next
|
1364
|
-
nitem =
|
1364
|
+
nitem = Items.generate(nxt, 'current'.freeze, Items::ItemCurrent)
|
1365
1365
|
|
1366
1366
|
|
1367
1367
|
####################
|
data/lib/icfs/cache_elastic.rb
CHANGED
@@ -9,6 +9,8 @@
|
|
9
9
|
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
10
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
11
|
|
12
|
+
require 'json'
|
13
|
+
require 'socket'
|
12
14
|
require_relative 'elastic'
|
13
15
|
|
14
16
|
|
@@ -153,6 +155,8 @@ class CacheElastic < Cache
|
|
153
155
|
def initialize(map, es)
|
154
156
|
@map = map
|
155
157
|
@es = es
|
158
|
+
@name = '%s:%d' % [Socket.gethostname, Process.pid]
|
159
|
+
@name.freeze
|
156
160
|
end
|
157
161
|
|
158
162
|
|
@@ -165,11 +169,9 @@ class CacheElastic < Cache
|
|
165
169
|
###############################################
|
166
170
|
# (see Cache#lock_take)
|
167
171
|
#
|
168
|
-
# @todo Include client info to help with debugging
|
169
|
-
#
|
170
172
|
def lock_take(cid)
|
171
173
|
|
172
|
-
json = '{"client":"
|
174
|
+
json = '{"client":"%s"}'.freeze % @name
|
173
175
|
url = '%s/_doc/%s/_create'.freeze % [@map[:lock], CGI.escape(cid)]
|
174
176
|
head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
|
175
177
|
|
@@ -656,7 +658,7 @@ class CacheElastic < Cache
|
|
656
658
|
filter = [
|
657
659
|
_query_term('caseid'.freeze, query[:caseid]),
|
658
660
|
_query_term('tags'.freeze, query[:tags]),
|
659
|
-
_query_prefix('title'.freeze, query[:prefix]),
|
661
|
+
_query_prefix('title.raw'.freeze, query[:prefix]),
|
660
662
|
].compact
|
661
663
|
req = { 'query' => _query_bool(must, filter, nil, nil) }
|
662
664
|
|
data/lib/icfs/items.rb
CHANGED
@@ -9,7 +9,9 @@
|
|
9
9
|
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
10
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
11
|
|
12
|
-
|
12
|
+
require 'json'
|
13
|
+
|
14
|
+
|
13
15
|
module ICFS
|
14
16
|
|
15
17
|
|
@@ -18,6 +20,65 @@ module ICFS
|
|
18
20
|
#
|
19
21
|
module Items
|
20
22
|
|
23
|
+
|
24
|
+
###############################################
|
25
|
+
# Parse JSON string and validate
|
26
|
+
#
|
27
|
+
# @param json [String] the JSON to parse
|
28
|
+
# @param name [String] description of the item
|
29
|
+
# @param val [Hash] the check to use
|
30
|
+
# @return [Object] the item
|
31
|
+
#
|
32
|
+
# @raise [Error::NotFound] if json is nil
|
33
|
+
# @raise [Error::Value] if parsing or validation fails
|
34
|
+
#
|
35
|
+
def self.parse(json, name, val)
|
36
|
+
if json.nil?
|
37
|
+
raise(Error::NotFound, '%s not found'.freeze % name)
|
38
|
+
end
|
39
|
+
begin
|
40
|
+
itm = JSON.parse(json)
|
41
|
+
rescue
|
42
|
+
raise(Error::Value, 'JSON parsing failed'.freeze)
|
43
|
+
end
|
44
|
+
Items.validate(itm, name, val)
|
45
|
+
return itm
|
46
|
+
end # def self.parse()
|
47
|
+
|
48
|
+
|
49
|
+
###############################################
|
50
|
+
# Validate and generate JSON
|
51
|
+
#
|
52
|
+
# @param itm [Object] item to validate
|
53
|
+
# @param name [String] description of the item
|
54
|
+
# @param val [Hash] the check to use
|
55
|
+
# @return [String] JSON encoded item
|
56
|
+
#
|
57
|
+
# @raise [Error::Value] if validation fails
|
58
|
+
#
|
59
|
+
def self.generate(itm, name, val)
|
60
|
+
Items.validate(itm, name, val)
|
61
|
+
return JSON.pretty_generate(itm)
|
62
|
+
end # def self.generate()
|
63
|
+
|
64
|
+
|
65
|
+
###############################################
|
66
|
+
# Validate an object
|
67
|
+
#
|
68
|
+
# @param obj [Object] object to validate
|
69
|
+
# @param name [String] description of the object
|
70
|
+
# @param val [Hash] the check to use
|
71
|
+
#
|
72
|
+
# @raise [Error::Value] if validation fails
|
73
|
+
#
|
74
|
+
def self.validate(obj, name, val)
|
75
|
+
err = Validate.check(obj, val)
|
76
|
+
if err
|
77
|
+
raise(Error::Value, '%s has bad values: %s'.freeze %
|
78
|
+
[name, err.inspect])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
21
82
|
##############################################################
|
22
83
|
# Base fields
|
23
84
|
##############################################################
|
data/lib/icfs/store.rb
CHANGED
@@ -115,6 +115,18 @@ class Store
|
|
115
115
|
def file_write(cid, enum, lnum, fnum, tmpf); raise NotImplementedError; end
|
116
116
|
|
117
117
|
|
118
|
+
###############################################
|
119
|
+
# Get a file size
|
120
|
+
#
|
121
|
+
# @param cid [String] caseid
|
122
|
+
# @param enum [Integer] Entry number
|
123
|
+
# @param lnum [Integer] Log number
|
124
|
+
# @param fnum [Integer] File number
|
125
|
+
# @return [Integer] The size of the file
|
126
|
+
#
|
127
|
+
def file_size(cid, enum, lnum, fnum); raise NotImplementedError; end
|
128
|
+
|
129
|
+
|
118
130
|
###############################################
|
119
131
|
# Read an action
|
120
132
|
#
|
@@ -176,6 +188,19 @@ class Store
|
|
176
188
|
def tempfile; raise NotImplementedError; end
|
177
189
|
|
178
190
|
|
191
|
+
###############################################
|
192
|
+
# Close the file returned by file_read()
|
193
|
+
#
|
194
|
+
# @param fi [File] The file to close
|
195
|
+
#
|
196
|
+
def close(fi)
|
197
|
+
if fi.respond_to?( :close! )
|
198
|
+
fi.close!
|
199
|
+
else
|
200
|
+
fi.close
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
179
204
|
private
|
180
205
|
|
181
206
|
|