icfs 0.1.1 → 0.1.2

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.
@@ -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['val'].to_s,
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
- Rack::Utils.escape(env['SCRIPT_NAME']),
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
- Time.at(time).getlocal(env['icfs.tz']).strftime('%F %T'.freeze)
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
@@ -16,8 +16,6 @@ require_relative 'icfs/validate'
16
16
  ##########################################################################
17
17
  # Investigative Case File System
18
18
  #
19
- # @todo Delete Items and move into this module
20
- #
21
19
  module ICFS
22
20
 
23
21
 
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.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-05-23 00:00:00.000000000 Z
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
@@ -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