icfs 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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