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.
- checksums.yaml +7 -0
- data/LICENSE.txt +674 -0
- data/bin/icfs_demo_create.rb +89 -0
- data/bin/icfs_demo_fcgi.rb +51 -0
- data/bin/icfs_demo_ssl_gen.rb +84 -0
- data/bin/icfs_demo_web.rb +50 -0
- data/bin/icfs_dev_todo.rb +20 -0
- data/data/demo_config.yml +94 -0
- data/data/icfs.css +475 -0
- data/data/icfs.js +458 -0
- data/lib/icfs.rb +109 -0
- data/lib/icfs/api.rb +1436 -0
- data/lib/icfs/cache.rb +254 -0
- data/lib/icfs/cache_elastic.rb +1154 -0
- data/lib/icfs/demo/auth.rb +74 -0
- data/lib/icfs/demo/static.rb +59 -0
- data/lib/icfs/demo/timezone.rb +38 -0
- data/lib/icfs/elastic.rb +83 -0
- data/lib/icfs/items.rb +653 -0
- data/lib/icfs/store.rb +278 -0
- data/lib/icfs/store_fs.rb +98 -0
- data/lib/icfs/store_s3.rb +97 -0
- data/lib/icfs/users.rb +80 -0
- data/lib/icfs/users_elastic.rb +166 -0
- data/lib/icfs/users_fs.rb +132 -0
- data/lib/icfs/validate.rb +479 -0
- data/lib/icfs/web/auth_ssl.rb +73 -0
- data/lib/icfs/web/client.rb +4498 -0
- metadata +77 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# See LICENSE.txt for licensing information.
|
7
|
+
#
|
8
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
9
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
|
11
|
+
require 'rack'
|
12
|
+
|
13
|
+
module ICFS
|
14
|
+
module Demo
|
15
|
+
|
16
|
+
##########################################################################
|
17
|
+
# Test authentication - Rack middleware
|
18
|
+
#
|
19
|
+
# @deprecated This is a horrible security implementation, DO NOT USE
|
20
|
+
# for anything other than testing.
|
21
|
+
#
|
22
|
+
class Auth
|
23
|
+
|
24
|
+
|
25
|
+
###############################################
|
26
|
+
# New instance
|
27
|
+
#
|
28
|
+
# @param app [Object] The rack app
|
29
|
+
# @param api [Object] the ICFS API
|
30
|
+
#
|
31
|
+
def initialize(app, api)
|
32
|
+
@app = app
|
33
|
+
@api = api
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
###############################################
|
38
|
+
# Handle the calls
|
39
|
+
#
|
40
|
+
def call(env)
|
41
|
+
|
42
|
+
# login
|
43
|
+
if env['PATH_INFO'] == '/login'.freeze
|
44
|
+
user = env['QUERY_STRING']
|
45
|
+
body = 'User set'
|
46
|
+
|
47
|
+
# set the cookie
|
48
|
+
rsp = Rack::Response.new( body, 200, {} )
|
49
|
+
rsp.set_cookie( 'icfs-user', {
|
50
|
+
value: user,
|
51
|
+
expires: Time.now + 24*60*60
|
52
|
+
})
|
53
|
+
return rsp.finish
|
54
|
+
end
|
55
|
+
|
56
|
+
# pull the username from the cookie
|
57
|
+
cookies = Rack::Request.new(env).cookies
|
58
|
+
user = cookies['icfs-user']
|
59
|
+
if !user
|
60
|
+
return [400, {'Content-Type' => 'text/plain'}, ['Login first'.freeze]]
|
61
|
+
end
|
62
|
+
@api.user = user
|
63
|
+
env['icfs'] = @api
|
64
|
+
return @app.call(env)
|
65
|
+
|
66
|
+
rescue ICFS::Error::NotFound, ICFS::Error::Value => err
|
67
|
+
return [400, {'Content-Type' => 'text/plain'}, [err.message]]
|
68
|
+
end # def call()
|
69
|
+
|
70
|
+
end # class ICFS::Demo::Auth
|
71
|
+
|
72
|
+
end # module ICFS::Demo
|
73
|
+
|
74
|
+
end # module ICFS
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# See LICENSE.txt for licensing information.
|
7
|
+
#
|
8
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
9
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
|
11
|
+
#
|
12
|
+
module ICFS
|
13
|
+
|
14
|
+
|
15
|
+
##########################################################################
|
16
|
+
# Demonstration only files
|
17
|
+
module Demo
|
18
|
+
|
19
|
+
##########################################################################
|
20
|
+
# Serve static files - Rack middleware
|
21
|
+
#
|
22
|
+
# @deprecated This is a horrible implementation, DO NOT USE
|
23
|
+
# for anything other than testing.
|
24
|
+
#
|
25
|
+
class Static
|
26
|
+
|
27
|
+
###############################################
|
28
|
+
# New instance
|
29
|
+
#
|
30
|
+
# @param stat [Hash] maps path to Hash with :path and :mime
|
31
|
+
# @param app [Object] the next rack app
|
32
|
+
#
|
33
|
+
def initialize(app, stat)
|
34
|
+
@stat = stat
|
35
|
+
@app = app
|
36
|
+
end
|
37
|
+
|
38
|
+
# Process requests
|
39
|
+
def call(env)
|
40
|
+
|
41
|
+
# see if we have a static file to serve
|
42
|
+
st = @stat[env['PATH_INFO']]
|
43
|
+
if st
|
44
|
+
cont = File.read(st['path'])
|
45
|
+
head = {
|
46
|
+
'Content-Type' => st['mime'],
|
47
|
+
'Content-Length' => cont.bytesize.to_s
|
48
|
+
}
|
49
|
+
return [200, head, [cont]]
|
50
|
+
end
|
51
|
+
|
52
|
+
return @app.call(env)
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end # class ICFS::Demo::Static
|
57
|
+
|
58
|
+
end # module ICFS::Demo
|
59
|
+
end # module ICFS
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# Investigative Case File System
|
3
|
+
#
|
4
|
+
# Copyright 2019 by Graham A. Field
|
5
|
+
#
|
6
|
+
# See LICENSE.txt for licensing information.
|
7
|
+
#
|
8
|
+
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
9
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
|
11
|
+
#
|
12
|
+
module ICFS
|
13
|
+
module Demo
|
14
|
+
|
15
|
+
##########################################################################
|
16
|
+
# Set timezone - Rack middleware
|
17
|
+
#
|
18
|
+
# @deprecated This does nothing but set a static timezone. Do not use for
|
19
|
+
# anything other than testing.
|
20
|
+
#
|
21
|
+
class Timezone
|
22
|
+
|
23
|
+
# New instance
|
24
|
+
def initialize(app, tz)
|
25
|
+
@app = app
|
26
|
+
@tz = tz.freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
# process requests
|
30
|
+
def call(env)
|
31
|
+
env['icfs.tz'] = @tz
|
32
|
+
@app.call(env)
|
33
|
+
end
|
34
|
+
|
35
|
+
end # class ICFS::Demo::Timezone
|
36
|
+
|
37
|
+
end # module ICFS::Demo
|
38
|
+
end # module ICFS
|
data/lib/icfs/elastic.rb
ADDED
@@ -0,0 +1,83 @@
|
|
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
|
+
#
|
13
|
+
module ICFS
|
14
|
+
|
15
|
+
##########################################################################
|
16
|
+
# Shared Elasticsearch methods
|
17
|
+
#
|
18
|
+
module Elastic
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
###############################################
|
23
|
+
# Read an item
|
24
|
+
#
|
25
|
+
# @param ix [Symbol] the index to read
|
26
|
+
# @param id [String] the id to read
|
27
|
+
#
|
28
|
+
# @return [String] JSON encoded object
|
29
|
+
#
|
30
|
+
def _read(ix, id)
|
31
|
+
url = '%s/_doc/%s/_source'.freeze % [ @map[ix], CGI.escape(id)]
|
32
|
+
resp = @es.run_request(:get, url, ''.freeze, {})
|
33
|
+
if resp.status == 404
|
34
|
+
return nil
|
35
|
+
elsif !resp.success?
|
36
|
+
raise('Elasticsearch read failed'.freeze)
|
37
|
+
end
|
38
|
+
return resp.body
|
39
|
+
end # def _read()
|
40
|
+
|
41
|
+
|
42
|
+
###############################################
|
43
|
+
# Write an item
|
44
|
+
#
|
45
|
+
# @param ix [Symbol] the index to write
|
46
|
+
# @param id [String] the id to write
|
47
|
+
# @param item [String] JSON encoded object to write
|
48
|
+
#
|
49
|
+
def _write(ix, id, item)
|
50
|
+
url = '%s/_doc/%s'.freeze % [ @map[ix], CGI.escape(id)]
|
51
|
+
head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
|
52
|
+
resp = @es.run_request(:put, url, item, head)
|
53
|
+
if !resp.success?
|
54
|
+
raise('Elasticsearch index failed'.freeze)
|
55
|
+
end
|
56
|
+
end # def _write()
|
57
|
+
|
58
|
+
|
59
|
+
public
|
60
|
+
|
61
|
+
|
62
|
+
###############################################
|
63
|
+
# Create ES indices
|
64
|
+
# @param maps [Hash] symbol to Elasticsearch mapping
|
65
|
+
#
|
66
|
+
def create(maps)
|
67
|
+
head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
|
68
|
+
maps.each do |ix, map|
|
69
|
+
url = @map[ix]
|
70
|
+
resp = @es.run_request(:put, url, map, head)
|
71
|
+
if !resp.success?
|
72
|
+
puts 'URL: %s' % url
|
73
|
+
puts map
|
74
|
+
puts resp.body
|
75
|
+
raise('Elasticsearch index create failed: %s'.freeze % ix.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end # def create()
|
79
|
+
|
80
|
+
|
81
|
+
end # module ICFS::Elastic
|
82
|
+
|
83
|
+
end # module ICFS
|
data/lib/icfs/items.rb
ADDED
@@ -0,0 +1,653 @@
|
|
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
|
+
#
|
13
|
+
module ICFS
|
14
|
+
|
15
|
+
|
16
|
+
##########################################################################
|
17
|
+
# Items
|
18
|
+
#
|
19
|
+
module Items
|
20
|
+
|
21
|
+
##############################################################
|
22
|
+
# Base fields
|
23
|
+
##############################################################
|
24
|
+
|
25
|
+
# ICFS version
|
26
|
+
FieldIcfs = {
|
27
|
+
method: :integer,
|
28
|
+
min: 1,
|
29
|
+
max: 1,
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
|
33
|
+
# Caseid
|
34
|
+
FieldCaseid = {
|
35
|
+
method: :string,
|
36
|
+
min: 1,
|
37
|
+
max: 32,
|
38
|
+
invalid: /[[:cntrl:][:space:]"\#$%'()*+\/;<=>?@\[\]\\^`{|}~]/.freeze,
|
39
|
+
}.freeze
|
40
|
+
|
41
|
+
|
42
|
+
# Title
|
43
|
+
FieldTitle = {
|
44
|
+
method: :string,
|
45
|
+
min: 1,
|
46
|
+
max: 128,
|
47
|
+
invalid: /[[:cntrl:]]/.freeze,
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
|
51
|
+
# Tag
|
52
|
+
# No control characters
|
53
|
+
# may not start with brackets or whitespace
|
54
|
+
FieldTag = {
|
55
|
+
method: :string,
|
56
|
+
min: 1,
|
57
|
+
max: 32,
|
58
|
+
invalid: /[[:cntrl:]]|^[\[\{ ]/.freeze,
|
59
|
+
}.freeze
|
60
|
+
|
61
|
+
|
62
|
+
# Tag for Entry
|
63
|
+
FieldTagEntry = {
|
64
|
+
method: :string,
|
65
|
+
min: 1,
|
66
|
+
max: 32,
|
67
|
+
allowed: Set[
|
68
|
+
ICFS::TagAction,
|
69
|
+
ICFS::TagIndex,
|
70
|
+
ICFS::TagCase,
|
71
|
+
].freeze,
|
72
|
+
invalid: /[[:cntrl:]]|^[\[\{ ]/.freeze,
|
73
|
+
}.freeze
|
74
|
+
|
75
|
+
|
76
|
+
# Special tags
|
77
|
+
FieldTagSpecial = {
|
78
|
+
method: :string,
|
79
|
+
allowed: Set[
|
80
|
+
ICFS::TagNone,
|
81
|
+
ICFS::TagAction,
|
82
|
+
ICFS::TagIndex,
|
83
|
+
ICFS::TagCase,
|
84
|
+
].freeze,
|
85
|
+
whitelist: true,
|
86
|
+
}.freeze
|
87
|
+
|
88
|
+
|
89
|
+
# Any tag, including empty
|
90
|
+
FieldTagAny = {
|
91
|
+
method: :any,
|
92
|
+
check: [
|
93
|
+
FieldTag,
|
94
|
+
FieldTagSpecial
|
95
|
+
].freeze,
|
96
|
+
}.freeze
|
97
|
+
|
98
|
+
|
99
|
+
# a normal perm name
|
100
|
+
# No control characters
|
101
|
+
# may not start with square brackets, curly brackets, or whitespace
|
102
|
+
FieldPermNormal = {
|
103
|
+
method: :string,
|
104
|
+
min: 1,
|
105
|
+
max: 64,
|
106
|
+
invalid: /[[:cntrl:]]|^[\[\{ ]/.freeze,
|
107
|
+
}.freeze
|
108
|
+
|
109
|
+
|
110
|
+
# A reserved case name
|
111
|
+
# square brackets
|
112
|
+
FieldPermReserve = {
|
113
|
+
method: :string,
|
114
|
+
allowed: Set[
|
115
|
+
ICFS::PermRead,
|
116
|
+
ICFS::PermWrite,
|
117
|
+
ICFS::PermManage,
|
118
|
+
ICFS::PermAction
|
119
|
+
].freeze,
|
120
|
+
whitelist: true
|
121
|
+
}.freeze
|
122
|
+
|
123
|
+
|
124
|
+
# A global permission
|
125
|
+
# curly brackets, no control characters
|
126
|
+
FieldPermGlobal = {
|
127
|
+
method: :string,
|
128
|
+
min: 1,
|
129
|
+
max: 64,
|
130
|
+
valid: /^\{[^[:cntrl:]]+\}$/.freeze,
|
131
|
+
whitelist: true,
|
132
|
+
}.freeze
|
133
|
+
|
134
|
+
|
135
|
+
# A case perm
|
136
|
+
FieldPermCase = {
|
137
|
+
method: :any,
|
138
|
+
check: [
|
139
|
+
FieldPermNormal,
|
140
|
+
FieldPermReserve,
|
141
|
+
].freeze
|
142
|
+
}.freeze
|
143
|
+
|
144
|
+
|
145
|
+
# Any perm
|
146
|
+
FieldPermAny = {
|
147
|
+
method: :any,
|
148
|
+
check: [
|
149
|
+
FieldPermNormal,
|
150
|
+
FieldPermReserve,
|
151
|
+
FieldPermGlobal,
|
152
|
+
].freeze
|
153
|
+
}.freeze
|
154
|
+
|
155
|
+
|
156
|
+
# a user/group name
|
157
|
+
# No control characters
|
158
|
+
# no space, no punctuation except , - : _
|
159
|
+
FieldUsergrp = {
|
160
|
+
method: :string,
|
161
|
+
min: 3,
|
162
|
+
max: 32,
|
163
|
+
invalid: /[\x00-\x2b\x2e\x2f\x3b-\x40\x5b-\x5e\x60\x7b-\x7f]/.freeze,
|
164
|
+
}.freeze
|
165
|
+
|
166
|
+
|
167
|
+
# a hash
|
168
|
+
FieldHash = {
|
169
|
+
method: :string,
|
170
|
+
min: 64,
|
171
|
+
max: 64,
|
172
|
+
invalid: /[^0-9a-f]/.freeze
|
173
|
+
}.freeze
|
174
|
+
|
175
|
+
|
176
|
+
# Content
|
177
|
+
FieldContent = {
|
178
|
+
method: :string,
|
179
|
+
min: 1,
|
180
|
+
max: 8*1024,
|
181
|
+
invalid: /[^[:graph:][:space:]]/.freeze,
|
182
|
+
}.freeze
|
183
|
+
|
184
|
+
|
185
|
+
# a stat name
|
186
|
+
FieldStat = {
|
187
|
+
method: :string,
|
188
|
+
min: 1,
|
189
|
+
max: 32,
|
190
|
+
invalid: /[[:cntrl:]\t\r\n\v\f]|^_/.freeze,
|
191
|
+
}.freeze
|
192
|
+
|
193
|
+
|
194
|
+
# a filename
|
195
|
+
FieldFilename = {
|
196
|
+
method: :string,
|
197
|
+
min: 1,
|
198
|
+
max: 128,
|
199
|
+
invalid: /[[:cntrl:]\\\/]|^\./.freeze
|
200
|
+
}.freeze
|
201
|
+
|
202
|
+
|
203
|
+
##############################################################
|
204
|
+
# Sub-item parts
|
205
|
+
##############################################################
|
206
|
+
|
207
|
+
# Empty Tags
|
208
|
+
#
|
209
|
+
SubTagsEmpty = {
|
210
|
+
method: :array,
|
211
|
+
min: 1,
|
212
|
+
max: 1,
|
213
|
+
check: {
|
214
|
+
method: :equals,
|
215
|
+
check: ICFS::TagNone
|
216
|
+
}.freeze
|
217
|
+
}.freeze
|
218
|
+
|
219
|
+
|
220
|
+
# Entry Tags
|
221
|
+
#
|
222
|
+
SubTagsEntry = {
|
223
|
+
method: :array,
|
224
|
+
min: 1,
|
225
|
+
check: FieldTagEntry
|
226
|
+
}.freeze
|
227
|
+
|
228
|
+
|
229
|
+
# Tags
|
230
|
+
SubTagsNormal = {
|
231
|
+
method: :array,
|
232
|
+
min: 1,
|
233
|
+
check: FieldTag
|
234
|
+
}.freeze
|
235
|
+
|
236
|
+
|
237
|
+
# Tags
|
238
|
+
SubTags = {
|
239
|
+
method: :any,
|
240
|
+
check: [ SubTagsEmpty, SubTagsNormal ].freeze
|
241
|
+
}.freeze
|
242
|
+
|
243
|
+
|
244
|
+
# Grant
|
245
|
+
SubGrant = {
|
246
|
+
method: :hash,
|
247
|
+
required: {
|
248
|
+
'perm' => FieldPermCase,
|
249
|
+
'grant' => {
|
250
|
+
method: :array,
|
251
|
+
min: 1,
|
252
|
+
check: FieldUsergrp
|
253
|
+
}.freeze
|
254
|
+
}.freeze
|
255
|
+
}.freeze
|
256
|
+
|
257
|
+
|
258
|
+
# Access
|
259
|
+
SubAccess = {
|
260
|
+
method: :array,
|
261
|
+
min: 1,
|
262
|
+
check: SubGrant
|
263
|
+
}.freeze
|
264
|
+
|
265
|
+
|
266
|
+
# Case stats
|
267
|
+
SubCaseStats = {
|
268
|
+
method: :array,
|
269
|
+
min:1,
|
270
|
+
check: FieldStat,
|
271
|
+
}.freeze
|
272
|
+
|
273
|
+
|
274
|
+
# An item in a log
|
275
|
+
SubLogItem = {
|
276
|
+
method: :hash,
|
277
|
+
required: {
|
278
|
+
'num' => Validate::IsIntPos,
|
279
|
+
'hash' => FieldHash,
|
280
|
+
}.freeze
|
281
|
+
}.freeze
|
282
|
+
|
283
|
+
|
284
|
+
# Indexes
|
285
|
+
SubIndexes = {
|
286
|
+
method: :array,
|
287
|
+
min: 1,
|
288
|
+
check: Validate::IsIntPos,
|
289
|
+
}.freeze
|
290
|
+
|
291
|
+
|
292
|
+
# Perms
|
293
|
+
SubPerms = {
|
294
|
+
method: :array,
|
295
|
+
min: 1,
|
296
|
+
check: FieldPermAny
|
297
|
+
}.freeze
|
298
|
+
|
299
|
+
|
300
|
+
# Stats
|
301
|
+
SubStats = {
|
302
|
+
method: :array,
|
303
|
+
min: 1,
|
304
|
+
check: {
|
305
|
+
method: :hash,
|
306
|
+
required: {
|
307
|
+
"name" => FieldStat,
|
308
|
+
"value" => Validate::IsFloat,
|
309
|
+
"credit" => {
|
310
|
+
method: :array,
|
311
|
+
min: 1,
|
312
|
+
max: 32,
|
313
|
+
check: FieldUsergrp
|
314
|
+
}.freeze
|
315
|
+
}.freeze
|
316
|
+
}.freeze
|
317
|
+
}.freeze
|
318
|
+
|
319
|
+
|
320
|
+
# An old file
|
321
|
+
SubFileOld = {
|
322
|
+
method: :hash,
|
323
|
+
required: {
|
324
|
+
'log' => Validate::IsIntPos,
|
325
|
+
'num' => Validate::IsIntUns,
|
326
|
+
'name' => FieldFilename,
|
327
|
+
}.freeze
|
328
|
+
}.freeze
|
329
|
+
|
330
|
+
|
331
|
+
# Case task
|
332
|
+
SubTaskCase = {
|
333
|
+
method: :hash,
|
334
|
+
required: {
|
335
|
+
'assigned' => {
|
336
|
+
method: :string,
|
337
|
+
allowed: Set[ ICFS::UserCase ].freeze,
|
338
|
+
whitelist: true
|
339
|
+
}.freeze,
|
340
|
+
'title' => FieldTitle,
|
341
|
+
'status' => Validate::IsBoolean,
|
342
|
+
'flag' => Validate::IsBoolean,
|
343
|
+
'time' => Validate::IsIntPos,
|
344
|
+
'tags' => SubTags
|
345
|
+
}.freeze
|
346
|
+
}.freeze
|
347
|
+
|
348
|
+
|
349
|
+
# Normal task
|
350
|
+
SubTaskNormal = {
|
351
|
+
method: :hash,
|
352
|
+
required: {
|
353
|
+
'assigned' => FieldUsergrp,
|
354
|
+
'title' => FieldTitle,
|
355
|
+
'status' => Validate::IsBoolean,
|
356
|
+
'flag' => Validate::IsBoolean,
|
357
|
+
'time' => Validate::IsIntPos,
|
358
|
+
'tags' => SubTags
|
359
|
+
}.freeze
|
360
|
+
}.freeze
|
361
|
+
|
362
|
+
|
363
|
+
# Tasks
|
364
|
+
SubTasks = {
|
365
|
+
method: :array,
|
366
|
+
min: 1,
|
367
|
+
0 => SubTaskCase,
|
368
|
+
check: SubTaskNormal
|
369
|
+
}.freeze
|
370
|
+
|
371
|
+
|
372
|
+
# Case task
|
373
|
+
SubTaskEditCase = {
|
374
|
+
method: :hash,
|
375
|
+
required: {
|
376
|
+
'assigned' => {
|
377
|
+
method: :string,
|
378
|
+
allowed: Set[ ICFS::UserCase ].freeze,
|
379
|
+
whitelist: true
|
380
|
+
}.freeze,
|
381
|
+
'title' => FieldTitle,
|
382
|
+
'status' => Validate::IsBoolean,
|
383
|
+
'flag' => Validate::IsBoolean,
|
384
|
+
'time' => Validate::IsIntPos,
|
385
|
+
}.freeze,
|
386
|
+
optional: {
|
387
|
+
'tags' => SubTags
|
388
|
+
}.freeze
|
389
|
+
}.freeze
|
390
|
+
|
391
|
+
|
392
|
+
# Normal task
|
393
|
+
SubTaskEditNormal = {
|
394
|
+
method: :hash,
|
395
|
+
required: {
|
396
|
+
'assigned' => FieldUsergrp,
|
397
|
+
'title' => FieldTitle,
|
398
|
+
'status' => Validate::IsBoolean,
|
399
|
+
'flag' => Validate::IsBoolean,
|
400
|
+
'time' => Validate::IsIntPos,
|
401
|
+
}.freeze,
|
402
|
+
optional: {
|
403
|
+
'tags' => SubTags
|
404
|
+
}.freeze
|
405
|
+
}.freeze
|
406
|
+
|
407
|
+
|
408
|
+
# TasksEdit
|
409
|
+
SubTasksEdit = {
|
410
|
+
method: :array,
|
411
|
+
min: 1,
|
412
|
+
0 => SubTaskEditCase,
|
413
|
+
check: SubTaskEditNormal,
|
414
|
+
}.freeze
|
415
|
+
|
416
|
+
|
417
|
+
# A new file
|
418
|
+
SubFileNew = {
|
419
|
+
method: :hash,
|
420
|
+
required: {
|
421
|
+
'temp' => Validate::IsTempfile,
|
422
|
+
'name' => FieldFilename,
|
423
|
+
}.freeze
|
424
|
+
}.freeze
|
425
|
+
|
426
|
+
|
427
|
+
##############################################################
|
428
|
+
# Check Items
|
429
|
+
##############################################################
|
430
|
+
|
431
|
+
|
432
|
+
# Case - Edit
|
433
|
+
ItemCaseEdit = {
|
434
|
+
method: :hash,
|
435
|
+
required: {
|
436
|
+
'template' => Validate::IsBoolean,
|
437
|
+
'status' => Validate::IsBoolean,
|
438
|
+
'title' => FieldTitle,
|
439
|
+
'access' => SubAccess
|
440
|
+
}.freeze,
|
441
|
+
optional: {
|
442
|
+
'tags' => SubTags,
|
443
|
+
'stats' => SubCaseStats,
|
444
|
+
}.freeze
|
445
|
+
}.freeze
|
446
|
+
|
447
|
+
|
448
|
+
# Entry - New only
|
449
|
+
ItemEntryNew = {
|
450
|
+
method: :hash,
|
451
|
+
required: {
|
452
|
+
'caseid' => FieldCaseid,
|
453
|
+
'title' => FieldTitle,
|
454
|
+
'content' => FieldContent,
|
455
|
+
}.freeze,
|
456
|
+
optional: {
|
457
|
+
'time' => Validate::IsIntPos,
|
458
|
+
'tags' => SubTagsNormal,
|
459
|
+
'index' => SubIndexes,
|
460
|
+
'action' => Validate::IsIntPos,
|
461
|
+
'perms' => SubPerms,
|
462
|
+
'stats' => SubStats,
|
463
|
+
'files' => {
|
464
|
+
method: :array,
|
465
|
+
min: 1,
|
466
|
+
check: SubFileNew
|
467
|
+
}.freeze
|
468
|
+
}.freeze
|
469
|
+
}.freeze
|
470
|
+
|
471
|
+
|
472
|
+
# Entry - Edit or New
|
473
|
+
ItemEntryEdit = {
|
474
|
+
method: :hash,
|
475
|
+
required: {
|
476
|
+
'caseid' => FieldCaseid,
|
477
|
+
'title' => FieldTitle,
|
478
|
+
'content' => FieldContent,
|
479
|
+
}.freeze,
|
480
|
+
optional: {
|
481
|
+
'entry' => Validate::IsIntPos,
|
482
|
+
'time' => Validate::IsIntPos,
|
483
|
+
'tags' => SubTagsEntry,
|
484
|
+
'index' => SubIndexes,
|
485
|
+
'action' => Validate::IsIntPos,
|
486
|
+
'perms' => SubPerms,
|
487
|
+
'stats' => SubStats,
|
488
|
+
'files' => {
|
489
|
+
method: :array,
|
490
|
+
min: 1,
|
491
|
+
check: {
|
492
|
+
method: :any,
|
493
|
+
check: [ SubFileOld, SubFileNew ].freeze
|
494
|
+
}.freeze
|
495
|
+
}.freeze
|
496
|
+
}.freeze
|
497
|
+
}.freeze
|
498
|
+
|
499
|
+
|
500
|
+
# Action - Edit or New
|
501
|
+
ItemActionEdit = {
|
502
|
+
method: :hash,
|
503
|
+
required: {
|
504
|
+
'tasks' => SubTasksEdit
|
505
|
+
}.freeze,
|
506
|
+
optional: {
|
507
|
+
'action' => Validate::IsIntPos
|
508
|
+
}.freeze
|
509
|
+
}.freeze
|
510
|
+
|
511
|
+
|
512
|
+
# Index - Edit or New
|
513
|
+
ItemIndexEdit = {
|
514
|
+
method: :hash,
|
515
|
+
required: {
|
516
|
+
'title' => FieldTitle,
|
517
|
+
'content' => FieldContent,
|
518
|
+
}.freeze,
|
519
|
+
optional: {
|
520
|
+
'index' => Validate::IsIntPos,
|
521
|
+
'tags' => SubTags,
|
522
|
+
}.freeze
|
523
|
+
}.freeze
|
524
|
+
|
525
|
+
|
526
|
+
##############################################################
|
527
|
+
# Recorded items
|
528
|
+
##############################################################
|
529
|
+
|
530
|
+
# Case
|
531
|
+
ItemCase = {
|
532
|
+
method: :hash,
|
533
|
+
required: {
|
534
|
+
'icfs' => FieldIcfs,
|
535
|
+
'caseid' => FieldCaseid,
|
536
|
+
'log' => Validate::IsIntPos,
|
537
|
+
'template' => Validate::IsBoolean,
|
538
|
+
'status' => Validate::IsBoolean,
|
539
|
+
'title' => FieldTitle,
|
540
|
+
'tags' => SubTags,
|
541
|
+
'access' => SubAccess,
|
542
|
+
}.freeze,
|
543
|
+
optional: {
|
544
|
+
'stats' => SubCaseStats,
|
545
|
+
}.freeze,
|
546
|
+
}.freeze
|
547
|
+
|
548
|
+
|
549
|
+
# Log
|
550
|
+
ItemLog = {
|
551
|
+
method: :hash,
|
552
|
+
required: {
|
553
|
+
'icfs' => FieldIcfs,
|
554
|
+
'caseid' => FieldCaseid,
|
555
|
+
'log' => Validate::IsIntPos,
|
556
|
+
'prev' => FieldHash,
|
557
|
+
'time' => Validate::IsIntPos,
|
558
|
+
'user' => FieldUsergrp,
|
559
|
+
'entry' => SubLogItem,
|
560
|
+
}.freeze,
|
561
|
+
optional: {
|
562
|
+
'index' => SubLogItem,
|
563
|
+
'action' => SubLogItem,
|
564
|
+
'case_hash' => FieldHash,
|
565
|
+
'files_hash' => {
|
566
|
+
method: :array,
|
567
|
+
min: 1,
|
568
|
+
check: FieldHash
|
569
|
+
}.freeze,
|
570
|
+
}.freeze,
|
571
|
+
}.freeze
|
572
|
+
|
573
|
+
|
574
|
+
# Entry
|
575
|
+
ItemEntry = {
|
576
|
+
method: :hash,
|
577
|
+
required: {
|
578
|
+
'icfs' => FieldIcfs,
|
579
|
+
'caseid' => FieldCaseid,
|
580
|
+
'entry' => Validate::IsIntPos,
|
581
|
+
'log' => Validate::IsIntPos,
|
582
|
+
'user' => FieldUsergrp,
|
583
|
+
'time' => Validate::IsIntPos,
|
584
|
+
'title' => FieldTitle,
|
585
|
+
'content' => FieldContent,
|
586
|
+
'tags' => {
|
587
|
+
method: :any,
|
588
|
+
check: [
|
589
|
+
SubTagsEmpty,
|
590
|
+
SubTagsEntry,
|
591
|
+
].freeze
|
592
|
+
}.freeze,
|
593
|
+
}.freeze,
|
594
|
+
optional: {
|
595
|
+
'index' => SubIndexes,
|
596
|
+
'action' => Validate::IsIntPos,
|
597
|
+
'perms' => SubPerms,
|
598
|
+
'stats' => SubStats,
|
599
|
+
'files' => {
|
600
|
+
method: :array,
|
601
|
+
min: 1,
|
602
|
+
check: SubFileOld
|
603
|
+
}.freeze
|
604
|
+
}.freeze
|
605
|
+
}.freeze
|
606
|
+
|
607
|
+
|
608
|
+
# Action
|
609
|
+
ItemAction = {
|
610
|
+
method: :hash,
|
611
|
+
required: {
|
612
|
+
'icfs' => FieldIcfs,
|
613
|
+
'caseid' => FieldCaseid,
|
614
|
+
'action' => Validate::IsIntPos,
|
615
|
+
'log' => Validate::IsIntPos,
|
616
|
+
'tasks' => SubTasks
|
617
|
+
}.freeze
|
618
|
+
}.freeze
|
619
|
+
|
620
|
+
|
621
|
+
# Index
|
622
|
+
ItemIndex = {
|
623
|
+
method: :hash,
|
624
|
+
required: {
|
625
|
+
'icfs' => FieldIcfs,
|
626
|
+
'caseid' => FieldCaseid,
|
627
|
+
'index' => Validate::IsIntPos,
|
628
|
+
'log' => Validate::IsIntPos,
|
629
|
+
'title' => FieldTitle,
|
630
|
+
'content' => FieldContent,
|
631
|
+
'tags' => SubTags
|
632
|
+
}.freeze,
|
633
|
+
}.freeze
|
634
|
+
|
635
|
+
|
636
|
+
# Current
|
637
|
+
ItemCurrent = {
|
638
|
+
method: :hash,
|
639
|
+
required: {
|
640
|
+
'icfs' => FieldIcfs,
|
641
|
+
'caseid' => FieldCaseid,
|
642
|
+
'log' => Validate::IsIntPos,
|
643
|
+
'hash' => FieldHash,
|
644
|
+
'entry' => Validate::IsIntPos,
|
645
|
+
'action' => Validate::IsIntUns,
|
646
|
+
'index' => Validate::IsIntUns,
|
647
|
+
}.freeze
|
648
|
+
}.freeze
|
649
|
+
|
650
|
+
|
651
|
+
end # module ICFS::Items
|
652
|
+
|
653
|
+
end # module ICFS
|