icfs 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/bin/icfs_demo_fcgi.rb +2 -0
  3. data/{bin/icfs_demo_ssl_gen.rb → devel/demo/ssl_gen.rb} +25 -13
  4. data/devel/demo/ssl_gen.yml +14 -0
  5. data/devel/icfs-wrk/Dockerfile +1 -1
  6. data/devel/run/base.rb +92 -0
  7. data/devel/run/copy-s3.rb +2 -0
  8. data/devel/run/email.rb +36 -0
  9. data/devel/run/email_imap.rb +43 -0
  10. data/devel/run/email_smime.rb +47 -0
  11. data/devel/run/init-icfs.rb +2 -0
  12. data/devel/run/webrick.rb +5 -57
  13. data/lib/icfs/api.rb +101 -90
  14. data/lib/icfs/cache.rb +2 -0
  15. data/lib/icfs/cache_elastic.rb +127 -125
  16. data/lib/icfs/{web/config.rb → config.rb} +3 -3
  17. data/lib/icfs/{web/config_redis.rb → config_redis.rb} +8 -8
  18. data/lib/icfs/{web/config_s3.rb → config_s3.rb} +8 -8
  19. data/lib/icfs/demo/auth.rb +5 -7
  20. data/lib/icfs/demo/static.rb +2 -0
  21. data/lib/icfs/elastic.rb +10 -8
  22. data/lib/icfs/email/basic.rb +242 -0
  23. data/lib/icfs/email/core.rb +293 -0
  24. data/lib/icfs/email/from.rb +52 -0
  25. data/lib/icfs/email/imap.rb +148 -0
  26. data/lib/icfs/email/smime.rb +139 -0
  27. data/lib/icfs/items.rb +5 -3
  28. data/lib/icfs/store.rb +20 -18
  29. data/lib/icfs/store_fs.rb +7 -5
  30. data/lib/icfs/store_s3.rb +4 -2
  31. data/lib/icfs/users.rb +5 -3
  32. data/lib/icfs/users_fs.rb +8 -6
  33. data/lib/icfs/users_redis.rb +12 -10
  34. data/lib/icfs/users_s3.rb +6 -4
  35. data/lib/icfs/utils/backup.rb +30 -29
  36. data/lib/icfs/utils/check.rb +36 -34
  37. data/lib/icfs/validate.rb +24 -15
  38. data/lib/icfs/web/auth_ssl.rb +7 -9
  39. data/lib/icfs/web/client.rb +671 -679
  40. data/lib/icfs.rb +174 -10
  41. metadata +16 -7
  42. data/devel/devel-webrick.yml +0 -49
@@ -0,0 +1,52 @@
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
+ # frozen_string_literal: true
13
+
14
+ require_relative 'core'
15
+
16
+ module ICFS
17
+ module Email
18
+
19
+ ##########################################################################
20
+ # Receive email user based on FROM: header
21
+ #
22
+ # @note Only use this in conjunction with some form of email spoofing
23
+ # prevention.
24
+ #
25
+ class From
26
+
27
+
28
+ ###############################################
29
+ # New instance
30
+ #
31
+ # @param map [Object] Maps email address to username
32
+ #
33
+ def initialize(map)
34
+ @map = map
35
+ end # def initialize()
36
+
37
+
38
+ ###############################################
39
+ # Extract the user based on the FROM: email.
40
+ #
41
+ def receive(env)
42
+ email = env[:msg].from.first
43
+ unam = @map[email]
44
+ env[:user] = unam if unam
45
+ return :continue
46
+ end # def receive()
47
+
48
+
49
+ end # class ICFS::Email::From
50
+
51
+ end # module ICFS::Email
52
+ end # module ICFS
@@ -0,0 +1,148 @@
1
+
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+
13
+ # frozen_string_literal: true
14
+
15
+ require 'net/imap'
16
+ require 'mail'
17
+
18
+ module ICFS
19
+ module Email
20
+
21
+ ##########################################################################
22
+ # Get email using IMAP
23
+ #
24
+ class Imap
25
+
26
+ ###############################################
27
+ # New instance
28
+ #
29
+ # @param core [Email::Core] The core email processor
30
+ # @param log [Logger] The log
31
+ # @param opts [Hash] configuration options
32
+ # @option opts [String] :address The server address
33
+ # @option opts [Integer] :port The server port
34
+ # @option opts [Boolean] :enable_ssl Use SSL or not
35
+ # @option opts [String] :username The login username
36
+ # @option opts [String] :password The login password
37
+ # @option opts [String] :mailbox The mailbox to read
38
+ # @option opts [Array,String] :keys keys to pass to {::Net::IMAP#uid_search}
39
+ # @option opts [Integer] :idle_time Seconds to wait IDLE before polling
40
+ # @option opts [Integer] :reconnect Seconds after which we can reconnect
41
+ #
42
+ def initialize(core, log, opts={})
43
+ @core = core
44
+ @log = log
45
+ @cfg = {
46
+ :port => 993,
47
+ :enable_ssl => true,
48
+ :keys => 'ALL',
49
+ :idle_time => 60*5,
50
+ :reconnect => 60*15,
51
+ }.merge!(opts)
52
+ end # def initialize()
53
+
54
+
55
+ ###############################################
56
+ # Handles reconnects when dropped
57
+ #
58
+ def reconnect()
59
+
60
+ while true
61
+ start = Time.now
62
+ begin
63
+ fetch()
64
+ rescue EOFError
65
+ if( (Time.now - start) > @cfg[:reconnect] )
66
+ @log.info('IMAP: lost connection, reconnecting')
67
+ next
68
+ else
69
+ @log.error('IMAP: disconnected under the reconnect limit')
70
+ break
71
+ end
72
+ end
73
+ end
74
+
75
+ end # def reconnect()
76
+
77
+
78
+ ###############################################
79
+ # Fetch messages
80
+ #
81
+ def fetch()
82
+
83
+ # open & login
84
+ imap = ::Net::IMAP.new(@cfg[:address], @cfg[:port],
85
+ @cfg[:enable_ssl], nil, false)
86
+ imap.login(@cfg[:username], @cfg[:password])
87
+ @log.info('IMAP: open and login')
88
+
89
+ # mailbox
90
+ if @cfg[:mailbox]
91
+ imap.select(::Net::IMAP.encode_utf7(@cfg[:mailbox]))
92
+ @log.info('IMAP: mailbox selected: %s' % @cfg[:mailbox])
93
+ else
94
+ @log.error('IMAP: no mailbox specified')
95
+ mbxs = imap.list('', '*')
96
+ @log.info('IMAP: mailbox list: %s' % mbxs.map{|mb| mb.name }.join(', '))
97
+ return
98
+ end
99
+
100
+ while true
101
+ # fetch messages
102
+ uids = imap.uid_search(@cfg[:keys])
103
+ @log.debug('IMAP: %d messages found' % uids.size)
104
+ uids.each do |uid|
105
+ fd = imap.uid_fetch(uid, ['RFC822'])[0]
106
+ @log.debug('IMAP: message fetched')
107
+ msg = ::Mail.new(fd.attr['RFC822'])
108
+ @core.receive(msg)
109
+ if @cfg[:delete]
110
+ imap.uid_store(uid, "+FLAGS", [:Deleted])
111
+ @log.debug('IMAP: message deleted')
112
+ end
113
+ end
114
+ if @cfg[:delete]
115
+ imap.expunge
116
+ @log.debug('IMAP: expunged')
117
+ end
118
+
119
+ # wait IDLE until new mail
120
+ if @cfg[:idle_time]
121
+ @log.debug('IMAP: starting IDLE')
122
+ imap.idle(@cfg[:idle_time]) do |resp|
123
+ if resp.is_a?(::Net::IMAP::UntaggedResponse) && resp.name == "EXISTS"
124
+ @log.debug('IMAP: new mail, IDLE done')
125
+ imap.idle_done
126
+ end
127
+ end
128
+ @log.debug('IMAP: exited IDLE')
129
+
130
+ # or we just run once
131
+ else
132
+ @log.debug('IMAP: fetch completed')
133
+ break
134
+ end
135
+ end
136
+
137
+ ensure
138
+ if defined?(imap) && imap && !imap.disconnected?
139
+ imap.disconnect
140
+ @log.info('IMAP: disconnected')
141
+ end
142
+ end # def fetch()
143
+
144
+
145
+ end # class ICFS::Email::Imap
146
+
147
+ end # module ICFS::Email
148
+ end # module ICFS
@@ -0,0 +1,139 @@
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
+ # frozen_string_literal: true
13
+
14
+ require 'openssl'
15
+
16
+ require_relative 'core'
17
+
18
+ module ICFS
19
+ module Email
20
+
21
+ ##########################################################################
22
+ # Receive S/MIME email
23
+ #
24
+ # This processes the most common S/MIME implementation:
25
+ # * Signed: multipart/signed; protocol="application/pkcs7-signature" with
26
+ # application/pkcs7-signature as the second part
27
+ # * Encrypted: application/pkcs7-mime containing eenvelope data
28
+ # * Both: application/pkcs7-mime containing encrypted data, which contains
29
+ # multipart/signed message
30
+ #
31
+ # In all cases, it will generate an email containing just the content. If
32
+ # the message is signed and verifies, it will try a lookup of the DN to
33
+ # determine the user.
34
+ #
35
+ class Smime
36
+
37
+
38
+ ###############################################
39
+ # New instance
40
+ #
41
+ # @param key [::OpenSSL::PKey] The key for the ICFS gateway
42
+ # @param cert [::OpenSSL::X509::Certificate] the ICFS gateway certificate
43
+ # @param ca [::OpenSSL::X509::Store] Trusted CA certs
44
+ # @param map [Object] Maps DN to user name
45
+ #
46
+ def initialize(key, cert, ca, map)
47
+ @key = key
48
+ @cert = cert
49
+ @ca = ca
50
+ @map = map
51
+ end # end def initialize()
52
+
53
+
54
+ ###############################################
55
+ # Process for S/MIME encryption or signatures
56
+ #
57
+ def receive(env)
58
+
59
+ # start with the main message
60
+ part = env[:msg]
61
+ replace = false
62
+
63
+ # process all PKCS7
64
+ while true
65
+
66
+ case part.header[:content_type].string
67
+
68
+ # encrypted
69
+ when 'application/pkcs7-mime', 'application/x-pkcs7-mime'
70
+
71
+ # decrypt
72
+ enc_raw = part.body.decoded
73
+ enc_p7 = ::OpenSSL::PKCS7.new(enc_raw)
74
+ dec_raw = enc_p7.decrypt(@key, @cert)
75
+
76
+ # new part is whatever we decrypted
77
+ part = ::Mail::Part.new(dec_raw)
78
+ replace = true
79
+
80
+ # signed
81
+ when 'multipart/signed'
82
+
83
+ # check sig
84
+ multi = part.body.parts
85
+ msg = multi[0].raw_source.dup.force_encoding('ASCII-8BIT')
86
+ msg = msg[2..-1] if msg[0,2] == "\r\n" # extra cr/lf
87
+ sig_raw = multi[1].body.decoded
88
+ sig_p7 = ::OpenSSL::PKCS7.new(sig_raw)
89
+ val = sig_p7.verify([], @ca, msg)
90
+ break unless val
91
+
92
+ # get cert that signed first
93
+ si = sig_p7.signers.first
94
+ cert = sig_p7.certificates.select do |c|
95
+ (c.issuer == si.issuer) && (c.serial == si.serial)
96
+ end
97
+ break if cert.empty?
98
+
99
+ # get user
100
+ unam = @map[cert.first.subject.to_s(::OpenSSL::X509::Name::RFC2253)]
101
+ break unless unam
102
+
103
+ # values
104
+ env[:user] = unam
105
+ env[:time] = si.signed_time.to_i
106
+
107
+ # new part is body
108
+ part = multi[0]
109
+ replace = true
110
+
111
+ # not PKCS7
112
+ else
113
+ break
114
+ end
115
+ end
116
+
117
+ return :continue unless replace
118
+
119
+ # create new message
120
+ msg = ::Mail::Message.new
121
+ msg.body = part.body.encoded
122
+ oh = env[:msg].header
123
+ hd = msg.header
124
+ Core::CopyFields.each do |fn|
125
+ fi = oh[fn]
126
+ hd[fn] = fi.value if fi
127
+ end
128
+ part.header.fields.each{|fd| hd[fd.name] = fd.value }
129
+
130
+ # Mail acts wierd about parts unless we run it thru text...
131
+ env[:msg] = ::Mail::Message.new(msg.encoded)
132
+
133
+ return :continue
134
+ end # def receive()
135
+
136
+ end # class ICFS::Email::Smime
137
+
138
+ end # module ICFS::Email
139
+ end # module ICFS
data/lib/icfs/items.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
+ # frozen_string_literal: true
13
+
12
14
  require 'json'
13
15
 
14
16
 
@@ -34,12 +36,12 @@ module Items
34
36
  #
35
37
  def self.parse(json, name, val)
36
38
  if json.nil?
37
- raise(Error::NotFound, '%s not found'.freeze % name)
39
+ raise(Error::NotFound, '%s not found' % name)
38
40
  end
39
41
  begin
40
42
  itm = JSON.parse(json)
41
43
  rescue
42
- raise(Error::Value, 'JSON parsing failed'.freeze)
44
+ raise(Error::Value, 'JSON parsing failed')
43
45
  end
44
46
  Items.validate(itm, name, val)
45
47
  return itm
@@ -74,7 +76,7 @@ module Items
74
76
  def self.validate(obj, name, val)
75
77
  err = Validate.check(obj, val)
76
78
  if err
77
- raise(Error::Value, '%s has bad values: %s'.freeze %
79
+ raise(Error::Value, '%s has bad values: %s' %
78
80
  [name, err.inspect])
79
81
  end
80
82
  end
data/lib/icfs/store.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
+ # frozen_string_literal: true
13
+
12
14
  #
13
15
  module ICFS
14
16
 
@@ -222,9 +224,9 @@ class Store
222
224
  def _case(cid, lnum)
223
225
  @base + [
224
226
  cid,
225
- 'c'.freeze,
226
- '%d.json'.freeze % lnum
227
- ].join('/'.freeze)
227
+ 'c',
228
+ '%d.json' % lnum
229
+ ].join('/')
228
230
  end
229
231
 
230
232
 
@@ -234,9 +236,9 @@ class Store
234
236
  def _log(cid, lnum)
235
237
  @base + [
236
238
  cid,
237
- 'l'.freeze,
238
- '%d.json'.freeze % lnum
239
- ].join('/'.freeze)
239
+ 'l',
240
+ '%d.json' % lnum
241
+ ].join('/')
240
242
  end
241
243
 
242
244
 
@@ -246,10 +248,10 @@ class Store
246
248
  def _entry(cid, enum, lnum)
247
249
  @base + [
248
250
  cid,
249
- 'e'.freeze,
251
+ 'e',
250
252
  enum.to_s,
251
- '%d.json'.freeze % lnum
252
- ].join('/'.freeze)
253
+ '%d.json' % lnum
254
+ ].join('/')
253
255
  end
254
256
 
255
257
 
@@ -259,10 +261,10 @@ class Store
259
261
  def _file(cid, enum, lnum, fnum)
260
262
  @base + [
261
263
  cid,
262
- 'e'.freeze,
264
+ 'e',
263
265
  enum.to_s,
264
- '%d-%d.bin'.freeze % [lnum, fnum]
265
- ].join('/'.freeze)
266
+ '%d-%d.bin' % [lnum, fnum]
267
+ ].join('/')
266
268
  end
267
269
 
268
270
 
@@ -272,10 +274,10 @@ class Store
272
274
  def _action(cid, anum, lnum)
273
275
  @base + [
274
276
  cid,
275
- 'a'.freeze,
277
+ 'a',
276
278
  anum.to_s,
277
- lnum.to_s + '.json'.freeze
278
- ].join('/'.freeze)
279
+ lnum.to_s + '.json'
280
+ ].join('/')
279
281
  end
280
282
 
281
283
 
@@ -285,10 +287,10 @@ class Store
285
287
  def _index(cid, xnum, lnum)
286
288
  @base + [
287
289
  cid,
288
- 'i'.freeze,
290
+ 'i',
289
291
  xnum.to_s,
290
- lnum.to_s + '.json'.freeze
291
- ].join('/'.freeze)
292
+ lnum.to_s + '.json'
293
+ ].join('/')
292
294
  end
293
295
 
294
296
 
data/lib/icfs/store_fs.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
+ # frozen_string_literal: true
13
+
12
14
  require 'fileutils'
13
15
  require 'tempfile'
14
16
 
@@ -29,7 +31,7 @@ class StoreFs < Store
29
31
  # @param base [String] the base directory
30
32
  #
31
33
  def initialize(base)
32
- if base[-1] == '/'.freeze
34
+ if base[-1] == '/'
33
35
  @base = base.freeze
34
36
  else
35
37
  @base = (base + '/').freeze
@@ -75,7 +77,7 @@ class StoreFs < Store
75
77
  # (see Store#tempfile)
76
78
  #
77
79
  def tempfile
78
- Tempfile.new('tmp'.freeze, @base, :encoding => 'ascii-8bit'.freeze)
80
+ Tempfile.new('tmp', @base, :encoding => 'ascii-8bit')
79
81
  end
80
82
 
81
83
 
@@ -86,7 +88,7 @@ class StoreFs < Store
86
88
  # Read an item
87
89
  #
88
90
  def _read(fnam)
89
- return File.read(fnam, encoding: 'utf-8'.freeze)
91
+ return File.read(fnam, encoding: 'utf-8')
90
92
  rescue Errno::ENOENT
91
93
  return nil
92
94
  end
@@ -96,12 +98,12 @@ class StoreFs < Store
96
98
  # Write an item
97
99
  #
98
100
  def _write(fnam, item)
99
- File.open(fnam, 'w'.freeze, encoding: 'utf-8'.freeze) do |fi|
101
+ File.open(fnam, 'w', encoding: 'utf-8') do |fi|
100
102
  fi.write(item)
101
103
  end
102
104
  rescue Errno::ENOENT
103
105
  FileUtils.mkdir_p(File.dirname(fnam))
104
- File.open(fnam, 'w'.freeze, encoding: 'utf-8'.freeze) do |fi|
106
+ File.open(fnam, 'w', encoding: 'utf-8') do |fi|
105
107
  fi.write(item)
106
108
  end
107
109
  end
data/lib/icfs/store_s3.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
+ # frozen_string_literal: true
13
+
12
14
  require 'aws-sdk-s3'
13
15
  require 'tempfile'
14
16
 
@@ -30,7 +32,7 @@ class StoreS3 < Store
30
32
  def initialize(client, bucket, prefix=nil)
31
33
  @s3 = client
32
34
  @bck = bucket
33
- @base = prefix || ''.freeze
35
+ @base = prefix || ''
34
36
  end # def initialize()
35
37
 
36
38
 
@@ -79,7 +81,7 @@ class StoreS3 < Store
79
81
  # (see Store#tempfile)
80
82
  #
81
83
  def tempfile
82
- Tempfile.new('tmp'.freeze, encoding: 'ascii-8bit'.freeze)
84
+ Tempfile.new('tmp', encoding: 'ascii-8bit')
83
85
  end
84
86
 
85
87
 
data/lib/icfs/users.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
+ # frozen_string_literal: true
13
+
12
14
  require 'set'
13
15
 
14
16
  module ICFS
@@ -30,9 +32,9 @@ class Users
30
32
  'type' => {
31
33
  method: :string,
32
34
  allowed: Set[
33
- 'user'.freeze,
34
- 'role'.freeze,
35
- 'group'.freeze,
35
+ 'user',
36
+ 'role',
37
+ 'group',
36
38
  ].freeze
37
39
  }.freeze
38
40
  }.freeze,
data/lib/icfs/users_fs.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
+ # frozen_string_literal: true
13
+
12
14
  require 'tempfile'
13
15
  require 'fileutils'
14
16
  require 'json'
@@ -37,7 +39,7 @@ class UsersFs < Users
37
39
  # Path to store the file
38
40
  #
39
41
  def _path(urg)
40
- File.join(@path, urg + '.json'.freeze)
42
+ File.join(@path, urg + '.json')
41
43
  end
42
44
  private :_path
43
45
 
@@ -52,11 +54,11 @@ class UsersFs < Users
52
54
  # (see Users#read)
53
55
  #
54
56
  def read(urg)
55
- Items.validate(urg, 'User/Role/Group name'.freeze, Items::FieldUsergrp)
57
+ Items.validate(urg, 'User/Role/Group name', Items::FieldUsergrp)
56
58
  json = File.read(_path(urg))
57
- obj = Items.parse(json, 'User/Role/Group'.freeze, Users::ValUser)
59
+ obj = Items.parse(json, 'User/Role/Group', Users::ValUser)
58
60
  if obj['name'] != urg
59
- raise(Error::Values, 'UsersFs user %s name mismatch'.freeze % fn)
61
+ raise(Error::Values, 'UsersFs user %s name mismatch' % fn)
60
62
  end
61
63
  return obj
62
64
  rescue Errno::ENOENT
@@ -68,11 +70,11 @@ class UsersFs < Users
68
70
  # (see Users#write)
69
71
  #
70
72
  def write(obj)
71
- Items.validate(obj, 'User/Role/Group'.freeze, Users::ValUser)
73
+ Items.validate(obj, 'User/Role/Group', Users::ValUser)
72
74
  json = JSON.pretty_generate(obj)
73
75
 
74
76
  # write to temp file
75
- tmp = Tempfile.new('_tmp'.freeze, @path, :encoding => 'ascii-8bit'.freeze)
77
+ tmp = Tempfile.new('_tmp', @path, :encoding => 'ascii-8bit')
76
78
  tmp.write(json)
77
79
  tmp.close
78
80
 
@@ -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
+ # frozen_string_literal: true
13
+
12
14
  require 'redis'
13
15
 
14
16
  module ICFS
@@ -31,7 +33,7 @@ class UsersRedis < Users
31
33
  def initialize(redis, base, opts={})
32
34
  @redis = redis
33
35
  @base = base
34
- @pre = opts[:prefix] || ''.freeze
36
+ @pre = opts[:prefix] || ''
35
37
  @exp = opts[:expires] || 1*60*60 # 1 hour default
36
38
  @log = opts[:log]
37
39
  end
@@ -50,8 +52,8 @@ class UsersRedis < Users
50
52
  # (see Users#flush)
51
53
  #
52
54
  def flush(urg)
53
- Items.validate(urg, 'User/role/group name'.freeze, Items::FieldUsergrp)
54
- @log.info('User/role/group cache flush: %s'.freeze % urg) if @log
55
+ Items.validate(urg, 'User/role/group name', Items::FieldUsergrp)
56
+ @log.info('User/role/group cache flush: %s' % urg) if @log
55
57
  @redis.del(_key(urg))
56
58
  return true
57
59
  end # def flush()
@@ -61,21 +63,21 @@ class UsersRedis < Users
61
63
  # (see Users#read)
62
64
  #
63
65
  def read(urg)
64
- Items.validate(urg, 'User/role/group name'.freeze, Items::FieldUsergrp)
66
+ Items.validate(urg, 'User/role/group name', Items::FieldUsergrp)
65
67
  key = _key(urg)
66
- @log.debug('User/role/group read: %s'.freeze % urg) if @log
68
+ @log.debug('User/role/group read: %s' % urg) if @log
67
69
 
68
70
  # try cache
69
71
  json = @redis.get(key)
70
72
  if json
71
- @log.debug('User/role/group cache hit: %s'.freeze % urg) if @log
73
+ @log.debug('User/role/group cache hit: %s' % urg) if @log
72
74
  return JSON.parse(json)
73
75
  end
74
76
 
75
77
  # get base object from base store
76
78
  bse = @base.read(urg)
77
79
  if !bse
78
- @log.warn('User/role/group not found: %s'.freeze % urg) if @log
80
+ @log.warn('User/role/group not found: %s' % urg) if @log
79
81
  return nil
80
82
  end
81
83
 
@@ -128,7 +130,7 @@ class UsersRedis < Users
128
130
  # save to cache
129
131
  @redis.set(key, json)
130
132
  @redis.expire(key, @exp)
131
- @log.info('User/role/group cached: %s %s'.freeze % [urg, json]) if @log
133
+ @log.info('User/role/group cached: %s %s' % [urg, json]) if @log
132
134
  return bse
133
135
  end # def read()
134
136
 
@@ -137,9 +139,9 @@ class UsersRedis < Users
137
139
  # (see Users#write)
138
140
  #
139
141
  def write(obj)
140
- json = Items.generate(obj, 'User/Role/Group'.freeze, Users::ValUser)
142
+ json = Items.generate(obj, 'User/Role/Group', Users::ValUser)
141
143
  key = _key(obj['name'])
142
- @log.info('User/role/group write: %s'.freeze % urg) if @log
144
+ @log.info('User/role/group write: %s' % urg) if @log
143
145
  @redis.del(key)
144
146
  @base.write(obj)
145
147
  end # def write()