icfs 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/icfs.css +1 -0
- data/{bin/icfs_dev_todo.rb → devel/create.sh} +4 -9
- data/devel/elastic.sh +20 -0
- data/devel/icfs-wrk/Dockerfile +27 -0
- data/devel/minio.sh +22 -0
- data/devel/redis.sh +18 -0
- data/devel/server.sh +18 -0
- data/devel/test/es-s3-fs-init.rb +93 -0
- data/devel/test/es-s3-fs-webrick.rb +79 -0
- data/devel/test/es-s3-restore.rb +56 -0
- data/devel/test/init.rb +93 -0
- data/devel/test/run.rb +96 -0
- data/lib/icfs/api.rb +22 -11
- data/lib/icfs/cache_elastic.rb +9 -1
- data/lib/icfs/demo/auth.rb +7 -1
- data/lib/icfs/store.rb +6 -12
- data/lib/icfs/store_fs.rb +5 -1
- data/lib/icfs/users.rb +0 -3
- data/lib/icfs/users_fs.rb +19 -70
- data/lib/icfs/users_redis.rb +127 -0
- data/lib/icfs/users_s3.rb +68 -0
- data/lib/icfs/utils/backup.rb +212 -7
- data/lib/icfs/utils/check.rb +1 -5
- data/lib/icfs/web/client.rb +101 -8
- data/lib/icfs/web/config.rb +108 -0
- data/lib/icfs/web/config_redis.rb +78 -0
- data/lib/icfs/web/config_s3.rb +66 -0
- data/lib/icfs.rb +0 -2
- metadata +18 -4
- data/lib/icfs/users_elastic.rb +0 -168
data/lib/icfs/web/client.rb
CHANGED
@@ -21,9 +21,6 @@ module Web
|
|
21
21
|
##########################################################################
|
22
22
|
# Web Client
|
23
23
|
#
|
24
|
-
# @todo Improve time handling for web interface
|
25
|
-
# @todo Scrub the javascript
|
26
|
-
#
|
27
24
|
class Client
|
28
25
|
|
29
26
|
###############################################
|
@@ -170,6 +167,7 @@ class Client
|
|
170
167
|
when 'case_edit'; return _call_case_edit(env)
|
171
168
|
when 'entry_edit'; return _call_entry_edit(env)
|
172
169
|
when 'index_edit'; return _call_index_edit(env)
|
170
|
+
when 'config_edit'; return _call_config_edit(env)
|
173
171
|
|
174
172
|
# view
|
175
173
|
when 'home', ''; return _call_home(env)
|
@@ -369,6 +367,8 @@ class Client
|
|
369
367
|
['after'.freeze, :after, :time].freeze,
|
370
368
|
['tags'.freeze, :tags, :string].freeze,
|
371
369
|
['purpose'.freeze, :purpose, :string].freeze,
|
370
|
+
['size'.freeze, :size, :integer].freeze,
|
371
|
+
['page'.freeze, :page, :integer].freeze,
|
372
372
|
['sort'.freeze, :sort, :string].freeze,
|
373
373
|
].freeze
|
374
374
|
|
@@ -430,6 +430,8 @@ class Client
|
|
430
430
|
['content'.freeze, :content, :string].freeze,
|
431
431
|
['tags'.freeze, :tags, :string].freeze,
|
432
432
|
['purpose'.freeze, :purpose, :string].freeze,
|
433
|
+
['size'.freeze, :size, :integer].freeze,
|
434
|
+
['page'.freeze, :page, :integer].freeze,
|
433
435
|
['sort'.freeze, :sort, :string].freeze,
|
434
436
|
].freeze
|
435
437
|
|
@@ -753,6 +755,43 @@ class Client
|
|
753
755
|
end # def _call_index_edit()
|
754
756
|
|
755
757
|
|
758
|
+
###############################################
|
759
|
+
# Edit configuration
|
760
|
+
#
|
761
|
+
def _call_config_edit(env)
|
762
|
+
env['icfs.page'] = 'Config Edit'.freeze
|
763
|
+
api = env['icfs']
|
764
|
+
cfg = env['icfs.config']
|
765
|
+
_verb_getpost(env)
|
766
|
+
|
767
|
+
# get the form
|
768
|
+
if env['REQUEST_METHOD'] == 'GET'.freeze
|
769
|
+
parts = [ _form_config(env) ]
|
770
|
+
body = [
|
771
|
+
_div_nav(env),
|
772
|
+
_div_desc('Edit Configuration'.freeze, ''.freeze),
|
773
|
+
_div_form(env, '/config_edit/'.freeze, nil, parts,
|
774
|
+
'Save Config'.freeze),
|
775
|
+
].join(''.freeze)
|
776
|
+
return _resp_success(env, body)
|
777
|
+
|
778
|
+
# post the form
|
779
|
+
elsif env['REQUEST_METHOD'] == 'POST'.freeze
|
780
|
+
para = _util_post(env)
|
781
|
+
_post_config(env, para).each{|key, val| cfg.set(key,val) }
|
782
|
+
cfg.save
|
783
|
+
|
784
|
+
# display the index
|
785
|
+
body = [
|
786
|
+
_div_nav(env),
|
787
|
+
_div_desc('Edit Configuration'.freeze, 'Settings saved'.freeze),
|
788
|
+
_div_info(env),
|
789
|
+
].join(''.freeze)
|
790
|
+
return _resp_success(env, body)
|
791
|
+
end
|
792
|
+
end # def _call_config_edit()
|
793
|
+
|
794
|
+
|
756
795
|
###############################################
|
757
796
|
# User Home page
|
758
797
|
def _call_home(env)
|
@@ -1020,6 +1059,7 @@ class Client
|
|
1020
1059
|
after: Time.now.to_i - 60*60*24*30,
|
1021
1060
|
purpose: 'User Stats - Last 30 days'.freeze,
|
1022
1061
|
}, 'Stats'.freeze),
|
1062
|
+
_a_config_edit(env, 'Config'),
|
1023
1063
|
_a_info(env, 'Info'.freeze),
|
1024
1064
|
]
|
1025
1065
|
end
|
@@ -1067,7 +1107,7 @@ class Client
|
|
1067
1107
|
#
|
1068
1108
|
def _div_info(env)
|
1069
1109
|
api = env['icfs']
|
1070
|
-
tz = env['icfs.tz'
|
1110
|
+
tz = env['icfs.config'].get('tz')
|
1071
1111
|
|
1072
1112
|
# roles/groups/perms
|
1073
1113
|
roles = api.roles.map{|rol| DivInfoList % Rack::Utils.escape_html(rol)}
|
@@ -2996,7 +3036,7 @@ class Client
|
|
2996
3036
|
FormEntryStatEach % [
|
2997
3037
|
stats_cnt, claim_cnt,
|
2998
3038
|
stats_cnt, esc, esc,
|
2999
|
-
stats_cnt, st['
|
3039
|
+
stats_cnt, st['value'].to_s,
|
3000
3040
|
claims.join(''.freeze)
|
3001
3041
|
]
|
3002
3042
|
end
|
@@ -3032,7 +3072,7 @@ class Client
|
|
3032
3072
|
title, time, content,
|
3033
3073
|
tags_cnt, tags,
|
3034
3074
|
files_cnt, files,
|
3035
|
-
|
3075
|
+
env['SCRIPT_NAME'],
|
3036
3076
|
Rack::Utils.escape(cid),
|
3037
3077
|
index_cnt, index,
|
3038
3078
|
stats_sel, stats_cnt, stats,
|
@@ -3638,6 +3678,36 @@ class Client
|
|
3638
3678
|
</div>'.freeze
|
3639
3679
|
|
3640
3680
|
|
3681
|
+
###############################################
|
3682
|
+
# Config Form
|
3683
|
+
#
|
3684
|
+
def _form_config(env)
|
3685
|
+
cfg = env['icfs.config']
|
3686
|
+
tz = cfg.get('tz')
|
3687
|
+
return FormConfig % [tz]
|
3688
|
+
end # def _form_config()
|
3689
|
+
|
3690
|
+
|
3691
|
+
# Config form
|
3692
|
+
FormConfig = '
|
3693
|
+
<div class="sect">
|
3694
|
+
<div class="sect-main">
|
3695
|
+
<div class="sect-label">Config</div>
|
3696
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3697
|
+
Configuration settings.
|
3698
|
+
</div></div>
|
3699
|
+
<div class="sect-fill"> </div>
|
3700
|
+
</div>
|
3701
|
+
<div class="form-row">
|
3702
|
+
<div class="list-label">Timezone:</div>
|
3703
|
+
<input class="form-tz" name="cfg-tz" type="text" value="%s">
|
3704
|
+
<div class="tip"><div class="tip-disp"></div><div class="tip-info">
|
3705
|
+
Timezone to display date/times, format as +/-HH:MM.
|
3706
|
+
</div></div>
|
3707
|
+
</div>
|
3708
|
+
</div>'.freeze
|
3709
|
+
|
3710
|
+
|
3641
3711
|
###########################################################
|
3642
3712
|
# Post
|
3643
3713
|
###########################################################
|
@@ -3948,6 +4018,17 @@ class Client
|
|
3948
4018
|
end # def _post_index()
|
3949
4019
|
|
3950
4020
|
|
4021
|
+
###############################################
|
4022
|
+
# Config edit
|
4023
|
+
#
|
4024
|
+
def _post_config(env, para)
|
4025
|
+
cfg = {
|
4026
|
+
'tz' => para['cfg-tz']
|
4027
|
+
}
|
4028
|
+
return cfg
|
4029
|
+
end # def _post_config()
|
4030
|
+
|
4031
|
+
|
3951
4032
|
###########################################################
|
3952
4033
|
# Links
|
3953
4034
|
###########################################################
|
@@ -4129,6 +4210,17 @@ class Client
|
|
4129
4210
|
end
|
4130
4211
|
|
4131
4212
|
|
4213
|
+
###############################################
|
4214
|
+
# Link to Config edit
|
4215
|
+
#
|
4216
|
+
def _a_config_edit(env, txt)
|
4217
|
+
'<a href="%s/config_edit">%s</a>'.freeze % [
|
4218
|
+
env['SCRIPT_NAME'],
|
4219
|
+
Rack::Utils.escape_html(txt)
|
4220
|
+
]
|
4221
|
+
end
|
4222
|
+
|
4223
|
+
|
4132
4224
|
###############################################
|
4133
4225
|
# Link to Home
|
4134
4226
|
#
|
@@ -4287,7 +4379,8 @@ class Client
|
|
4287
4379
|
# Epoch time as local
|
4288
4380
|
#
|
4289
4381
|
def _util_time(env, time)
|
4290
|
-
|
4382
|
+
tz = env['icfs.config'].get('tz')
|
4383
|
+
Time.at(time).getlocal(tz).strftime('%F %T'.freeze)
|
4291
4384
|
end
|
4292
4385
|
|
4293
4386
|
|
@@ -4304,7 +4397,7 @@ class Client
|
|
4304
4397
|
|
4305
4398
|
# default use parse
|
4306
4399
|
ma = /[+-]\d{2}:\d{2}$/.match str
|
4307
|
-
tstr = ma ? str : str + env['icfs.tz'
|
4400
|
+
tstr = ma ? str : str + env['icfs.config'].get('tz')
|
4308
4401
|
time = Time.parse(tstr).to_i
|
4309
4402
|
rescue ArgumentError
|
4310
4403
|
return nil
|
@@ -0,0 +1,108 @@
|
|
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
|
+
module Web
|
15
|
+
|
16
|
+
##########################################################################
|
17
|
+
# Configuration storage interface
|
18
|
+
#
|
19
|
+
# @abstract
|
20
|
+
#
|
21
|
+
class Config
|
22
|
+
|
23
|
+
###############################################
|
24
|
+
# Valid config options
|
25
|
+
ValConfig = {
|
26
|
+
method: :hash,
|
27
|
+
optional: {
|
28
|
+
'tz' => {
|
29
|
+
method: :string,
|
30
|
+
valid: /[+\-](0[0-9]|1[0-2]):[0-5][0-9]/.freeze,
|
31
|
+
whitelist: true,
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
###############################################
|
37
|
+
# New instance
|
38
|
+
#
|
39
|
+
# @param defaults [Hash] The default options
|
40
|
+
#
|
41
|
+
def initialize(defaults={})
|
42
|
+
@data = {}
|
43
|
+
@unam = nil
|
44
|
+
@defaults = defaults
|
45
|
+
end # def initialize()
|
46
|
+
|
47
|
+
|
48
|
+
###############################################
|
49
|
+
# The configuration values hash
|
50
|
+
#
|
51
|
+
attr_accessor :data
|
52
|
+
|
53
|
+
|
54
|
+
###############################################
|
55
|
+
# The configuration defaults
|
56
|
+
#
|
57
|
+
attr_reader :defaults
|
58
|
+
|
59
|
+
|
60
|
+
###############################################
|
61
|
+
# Get a value
|
62
|
+
#
|
63
|
+
# @param key [String] The name of the config setting
|
64
|
+
#
|
65
|
+
def get(key)
|
66
|
+
@data.key?(key) ? @data[key] : @defaults[key]
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
###############################################
|
71
|
+
# Set a value
|
72
|
+
#
|
73
|
+
# @param key [String] The name of the config setting
|
74
|
+
# @param val [Object] The value of the config setting
|
75
|
+
#
|
76
|
+
def set(key, val)
|
77
|
+
@data[key] = val
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
###############################################
|
82
|
+
# Where to store objects
|
83
|
+
#
|
84
|
+
def _key(unam)
|
85
|
+
@pre + unam
|
86
|
+
end # def _key()
|
87
|
+
private :_key
|
88
|
+
|
89
|
+
|
90
|
+
###############################################
|
91
|
+
# Load a user configuration
|
92
|
+
#
|
93
|
+
# @param unam [String] the user name to load
|
94
|
+
# @return [Boolean] if any config data was found for the user
|
95
|
+
#
|
96
|
+
def load(unam); raise NotImplementedError; end
|
97
|
+
|
98
|
+
|
99
|
+
###############################################
|
100
|
+
# Save a user configuration
|
101
|
+
#
|
102
|
+
def save; raise NotImplementedError; end
|
103
|
+
|
104
|
+
|
105
|
+
end # class ICFS::Web::Config
|
106
|
+
|
107
|
+
end # module ICFS::Web
|
108
|
+
end # module ICFS
|
@@ -0,0 +1,78 @@
|
|
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_relative 'config'
|
13
|
+
|
14
|
+
module ICFS
|
15
|
+
module Web
|
16
|
+
|
17
|
+
##########################################################################
|
18
|
+
# Implement Config with a Redis cache
|
19
|
+
#
|
20
|
+
class ConfigRedis < Config
|
21
|
+
|
22
|
+
|
23
|
+
###############################################
|
24
|
+
# New instance
|
25
|
+
#
|
26
|
+
# @param redis [Redis] The redis client
|
27
|
+
# @param base [Config] The base Config store
|
28
|
+
# @param opts [Hash] Options
|
29
|
+
# @option opts [String] :prefix Prefix for Redis key
|
30
|
+
# @option opts [Integer] :expires Expiration time in seconds
|
31
|
+
#
|
32
|
+
def initialize(redis, base, opts={})
|
33
|
+
super(base.defaults)
|
34
|
+
@redis = redis
|
35
|
+
@base = base
|
36
|
+
@pre = opts[:prefix] || ''.freeze
|
37
|
+
@exp = opts[:expires] || 1*60*60 # 1 hour default
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
###############################################
|
42
|
+
# (see Config#load)
|
43
|
+
#
|
44
|
+
def load(unam)
|
45
|
+
Items.validate(unam, 'User/Role/Group name'.freeze, Items::FieldUsergrp)
|
46
|
+
@unam = unam.dup
|
47
|
+
key = _key(unam)
|
48
|
+
|
49
|
+
# try cache
|
50
|
+
json = @redis.get(key)
|
51
|
+
if json
|
52
|
+
@data = Items.parse(json, 'Config values'.freeze, Config::ValConfig)
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
# get base object
|
57
|
+
succ = @base.load(unam)
|
58
|
+
@data = @base.data
|
59
|
+
return succ
|
60
|
+
end # def load()
|
61
|
+
|
62
|
+
|
63
|
+
###############################################
|
64
|
+
# (see Config#save)
|
65
|
+
#
|
66
|
+
def save()
|
67
|
+
raise(RuntimeError, 'Save requires a user name'.freeze) if !@unam
|
68
|
+
json = Items.generate(@data, 'Config values'.freeze, Config::ValConfig)
|
69
|
+
@redis.del(_key(@unam))
|
70
|
+
@base.data = @data
|
71
|
+
@base.save
|
72
|
+
end # def save()
|
73
|
+
|
74
|
+
|
75
|
+
end # class ICFS::Web::Config
|
76
|
+
|
77
|
+
end # module ICFS::Web
|
78
|
+
end # module ICFS
|
@@ -0,0 +1,66 @@
|
|
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_relative 'config'
|
13
|
+
|
14
|
+
module ICFS
|
15
|
+
module Web
|
16
|
+
|
17
|
+
##########################################################################
|
18
|
+
# Configuration storage implemented in S3
|
19
|
+
#
|
20
|
+
class ConfigS3 < Config
|
21
|
+
|
22
|
+
|
23
|
+
###############################################
|
24
|
+
# New instance
|
25
|
+
#
|
26
|
+
# @param defaults [Hash] The default options
|
27
|
+
# @param s3 [Aws::S3::Client] the configured S3 client
|
28
|
+
# @param bucket [String] The bucket name
|
29
|
+
# @param prefix [String] Prefix to use for object keys
|
30
|
+
#
|
31
|
+
def initialize(defaults, s3, bucket, prefix=nil)
|
32
|
+
super(defaults)
|
33
|
+
@s3 = s3
|
34
|
+
@bck = bucket
|
35
|
+
@pre = prefix || ''.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
###############################################
|
40
|
+
# (see Config#load)
|
41
|
+
#
|
42
|
+
def load(unam)
|
43
|
+
Items.validate(unam, 'User/Role/Group name'.freeze, Items::FieldUsergrp)
|
44
|
+
@unam = unam.dup
|
45
|
+
json = @s3.get_object( bucket: @bck, key: _key(unam) ).body.read
|
46
|
+
@data = Items.parse(json, 'Config values'.freeze, Config::ValConfig)
|
47
|
+
return true
|
48
|
+
rescue
|
49
|
+
@data = {}
|
50
|
+
return false
|
51
|
+
end # def load()
|
52
|
+
|
53
|
+
|
54
|
+
###############################################
|
55
|
+
# (see Config#save)
|
56
|
+
#
|
57
|
+
def save()
|
58
|
+
raise(RuntimeError, 'Save requires a user name'.freeze) if !@unam
|
59
|
+
json = Items.generate(@data, 'Config values'.freeze, Config::ValConfig)
|
60
|
+
@s3.put_object( bucket: @bck, key: _key(@unam), body: json )
|
61
|
+
end # def save()
|
62
|
+
|
63
|
+
end # class ICFS::Web::ConfigS3
|
64
|
+
|
65
|
+
end # module ICFS::Web
|
66
|
+
end # module ICFS
|
data/lib/icfs.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: icfs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Graham A. Field
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |2-
|
14
14
|
|
@@ -29,7 +29,6 @@ files:
|
|
29
29
|
- bin/icfs_demo_fcgi.rb
|
30
30
|
- bin/icfs_demo_ssl_gen.rb
|
31
31
|
- bin/icfs_demo_web.rb
|
32
|
-
- bin/icfs_dev_todo.rb
|
33
32
|
- data/demo_config.yml
|
34
33
|
- data/docker/build-web.sh
|
35
34
|
- data/docker/compose-demo.yml
|
@@ -42,6 +41,17 @@ files:
|
|
42
41
|
- data/docker/nginx.conf
|
43
42
|
- data/icfs.css
|
44
43
|
- data/icfs.js
|
44
|
+
- devel/create.sh
|
45
|
+
- devel/elastic.sh
|
46
|
+
- devel/icfs-wrk/Dockerfile
|
47
|
+
- devel/minio.sh
|
48
|
+
- devel/redis.sh
|
49
|
+
- devel/server.sh
|
50
|
+
- devel/test/es-s3-fs-init.rb
|
51
|
+
- devel/test/es-s3-fs-webrick.rb
|
52
|
+
- devel/test/es-s3-restore.rb
|
53
|
+
- devel/test/init.rb
|
54
|
+
- devel/test/run.rb
|
45
55
|
- lib/icfs.rb
|
46
56
|
- lib/icfs/api.rb
|
47
57
|
- lib/icfs/cache.rb
|
@@ -55,13 +65,17 @@ files:
|
|
55
65
|
- lib/icfs/store_fs.rb
|
56
66
|
- lib/icfs/store_s3.rb
|
57
67
|
- lib/icfs/users.rb
|
58
|
-
- lib/icfs/users_elastic.rb
|
59
68
|
- lib/icfs/users_fs.rb
|
69
|
+
- lib/icfs/users_redis.rb
|
70
|
+
- lib/icfs/users_s3.rb
|
60
71
|
- lib/icfs/utils/backup.rb
|
61
72
|
- lib/icfs/utils/check.rb
|
62
73
|
- lib/icfs/validate.rb
|
63
74
|
- lib/icfs/web/auth_ssl.rb
|
64
75
|
- lib/icfs/web/client.rb
|
76
|
+
- lib/icfs/web/config.rb
|
77
|
+
- lib/icfs/web/config_redis.rb
|
78
|
+
- lib/icfs/web/config_s3.rb
|
65
79
|
homepage: https://github.com/g4field/icfs
|
66
80
|
licenses:
|
67
81
|
- GPL-3.0
|
data/lib/icfs/users_elastic.rb
DELETED
@@ -1,168 +0,0 @@
|
|
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
|
-
# @todo Add logging
|
22
|
-
#
|
23
|
-
class UsersElastic < Users
|
24
|
-
|
25
|
-
include Elastic
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
###############################################
|
30
|
-
# The ES mappings for the indexes
|
31
|
-
#
|
32
|
-
Maps = {
|
33
|
-
:users => '{
|
34
|
-
"mappings": { "_doc": { "properties": {
|
35
|
-
"name": { "type": "text" },
|
36
|
-
"type": { "type": "keyword" },
|
37
|
-
"roles": { "type": "keyword" },
|
38
|
-
"groups": { "type": "keyword" },
|
39
|
-
"perms": { "type": "keyword" },
|
40
|
-
"first": { "enabled": false },
|
41
|
-
"last": { "type": "date", "format": "epoch_second" },
|
42
|
-
"active": { "type": "boolean" }
|
43
|
-
}}}
|
44
|
-
}'.freeze,
|
45
|
-
}.freeze
|
46
|
-
|
47
|
-
public
|
48
|
-
|
49
|
-
###############################################
|
50
|
-
# New instance
|
51
|
-
#
|
52
|
-
# @param map [Hash] Symbol to String of the indexes.
|
53
|
-
# Must provide :user
|
54
|
-
# @param es [Faraday] Faraday instance to the Elasticsearch cluster
|
55
|
-
# @param src [Users] Source of authoritative information
|
56
|
-
# @param exp [Integer] Maximum time to cache a response
|
57
|
-
#
|
58
|
-
def initialize(map, es, src, exp=3600)
|
59
|
-
@map = map
|
60
|
-
@es = es
|
61
|
-
@src = src
|
62
|
-
@exp = exp
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
###############################################
|
67
|
-
# Validate a user
|
68
|
-
#
|
69
|
-
ValUserCache = {
|
70
|
-
method: :hash,
|
71
|
-
required: {
|
72
|
-
'name' => Items::FieldUsergrp,
|
73
|
-
'type' => {
|
74
|
-
method: :string,
|
75
|
-
allowed: Set[
|
76
|
-
'user'.freeze,
|
77
|
-
'role'.freeze,
|
78
|
-
'group'.freeze,
|
79
|
-
].freeze
|
80
|
-
}.freeze,
|
81
|
-
'first' => Validate::IsIntPos,
|
82
|
-
'last' => Validate::IsIntPos,
|
83
|
-
'active' => Validate::IsBoolean,
|
84
|
-
}.freeze,
|
85
|
-
optional: {
|
86
|
-
'roles' => {
|
87
|
-
method: :array,
|
88
|
-
check: Items::FieldUsergrp,
|
89
|
-
uniq: true
|
90
|
-
}.freeze,
|
91
|
-
'groups' => {
|
92
|
-
method: :array,
|
93
|
-
check: Items::FieldUsergrp,
|
94
|
-
uniq: true
|
95
|
-
}.freeze,
|
96
|
-
'perms' => {
|
97
|
-
method: :array,
|
98
|
-
check: Items::FieldPermGlobal,
|
99
|
-
uniq: true
|
100
|
-
}.freeze
|
101
|
-
}.freeze
|
102
|
-
}.freeze
|
103
|
-
|
104
|
-
|
105
|
-
###############################################
|
106
|
-
# (see Users#read)
|
107
|
-
#
|
108
|
-
def read(urg)
|
109
|
-
json = _read(:users, urg)
|
110
|
-
now = Time.now.to_i
|
111
|
-
|
112
|
-
# not in the cache
|
113
|
-
if !json
|
114
|
-
|
115
|
-
# read from source
|
116
|
-
obj = @src.read(urg)
|
117
|
-
return nil if !obj
|
118
|
-
|
119
|
-
# first time
|
120
|
-
obj['first'] = now
|
121
|
-
obj['last'] = now
|
122
|
-
obj['active'] = true
|
123
|
-
|
124
|
-
# store in cache
|
125
|
-
json = Items.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
|
126
|
-
_write(:users, urg, json)
|
127
|
-
|
128
|
-
# use cached version
|
129
|
-
else
|
130
|
-
obj = Items.parse(json, 'User/Role/Group'.freeze, ValUserCache)
|
131
|
-
end
|
132
|
-
|
133
|
-
# expired
|
134
|
-
if (obj['last'] + @exp) < now
|
135
|
-
|
136
|
-
# read from source
|
137
|
-
obj2 = @src.read(urg)
|
138
|
-
|
139
|
-
# update
|
140
|
-
if obj2
|
141
|
-
obj['active'] = true
|
142
|
-
obj['roles'] = obj2['roles']
|
143
|
-
obj['groups'] = obj2['groups']
|
144
|
-
obj['perms'] = obj2['perms']
|
145
|
-
else
|
146
|
-
obj['active'] = false
|
147
|
-
end
|
148
|
-
obj['last'] = now
|
149
|
-
|
150
|
-
# and store in cache
|
151
|
-
json = Items.generate(obj, 'User/Role/Group'.freeze, ValUserCache)
|
152
|
-
_write(:users, urg, json)
|
153
|
-
end
|
154
|
-
|
155
|
-
# not active
|
156
|
-
return nil unless obj['active']
|
157
|
-
|
158
|
-
# clean out cached info
|
159
|
-
obj.delete('first'.freeze)
|
160
|
-
obj.delete('last'.freeze)
|
161
|
-
obj.delete('active'.freeze)
|
162
|
-
|
163
|
-
return obj
|
164
|
-
end # def read()
|
165
|
-
|
166
|
-
end # class ICFS::Users
|
167
|
-
|
168
|
-
end # module ICFS
|