icfs 0.1.0 → 0.1.1
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.
- 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
|
|