icfs 0.1.3 → 0.2.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 +4 -4
- data/bin/icfs_demo_fcgi.rb +2 -0
- data/{bin/icfs_demo_ssl_gen.rb → devel/demo/ssl_gen.rb} +25 -13
- data/devel/demo/ssl_gen.yml +14 -0
- data/devel/icfs-wrk/Dockerfile +1 -1
- data/devel/run/base.rb +92 -0
- data/devel/run/copy-s3.rb +2 -0
- data/devel/run/email.rb +36 -0
- data/devel/run/email_imap.rb +43 -0
- data/devel/run/email_smime.rb +47 -0
- data/devel/run/init-icfs.rb +2 -0
- data/devel/run/webrick.rb +5 -57
- data/lib/icfs/api.rb +101 -90
- data/lib/icfs/cache.rb +2 -0
- data/lib/icfs/cache_elastic.rb +127 -125
- data/lib/icfs/{web/config.rb → config.rb} +3 -3
- data/lib/icfs/{web/config_redis.rb → config_redis.rb} +8 -8
- data/lib/icfs/{web/config_s3.rb → config_s3.rb} +8 -8
- data/lib/icfs/demo/auth.rb +5 -7
- data/lib/icfs/demo/static.rb +2 -0
- data/lib/icfs/elastic.rb +10 -8
- data/lib/icfs/email/basic.rb +242 -0
- data/lib/icfs/email/core.rb +293 -0
- data/lib/icfs/email/from.rb +52 -0
- data/lib/icfs/email/imap.rb +148 -0
- data/lib/icfs/email/smime.rb +139 -0
- data/lib/icfs/items.rb +5 -3
- data/lib/icfs/store.rb +20 -18
- data/lib/icfs/store_fs.rb +7 -5
- data/lib/icfs/store_s3.rb +4 -2
- data/lib/icfs/users.rb +5 -3
- data/lib/icfs/users_fs.rb +8 -6
- data/lib/icfs/users_redis.rb +12 -10
- data/lib/icfs/users_s3.rb +6 -4
- data/lib/icfs/utils/backup.rb +30 -29
- data/lib/icfs/utils/check.rb +36 -34
- data/lib/icfs/validate.rb +24 -15
- data/lib/icfs/web/auth_ssl.rb +7 -9
- data/lib/icfs/web/client.rb +671 -679
- data/lib/icfs.rb +174 -10
- metadata +16 -7
- 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'
|
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'
|
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'
|
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'
|
226
|
-
'%d.json'
|
227
|
-
].join('/'
|
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'
|
238
|
-
'%d.json'
|
239
|
-
].join('/'
|
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'
|
251
|
+
'e',
|
250
252
|
enum.to_s,
|
251
|
-
'%d.json'
|
252
|
-
].join('/'
|
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'
|
264
|
+
'e',
|
263
265
|
enum.to_s,
|
264
|
-
'%d-%d.bin'
|
265
|
-
].join('/'
|
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'
|
277
|
+
'a',
|
276
278
|
anum.to_s,
|
277
|
-
lnum.to_s + '.json'
|
278
|
-
].join('/'
|
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'
|
290
|
+
'i',
|
289
291
|
xnum.to_s,
|
290
|
-
lnum.to_s + '.json'
|
291
|
-
].join('/'
|
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] == '/'
|
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'
|
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'
|
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'
|
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'
|
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 || ''
|
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'
|
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'
|
34
|
-
'role'
|
35
|
-
'group'
|
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'
|
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'
|
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'
|
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'
|
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'
|
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'
|
77
|
+
tmp = Tempfile.new('_tmp', @path, :encoding => 'ascii-8bit')
|
76
78
|
tmp.write(json)
|
77
79
|
tmp.close
|
78
80
|
|
data/lib/icfs/users_redis.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 '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] || ''
|
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'
|
54
|
-
@log.info('User/role/group cache flush: %s'
|
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'
|
66
|
+
Items.validate(urg, 'User/role/group name', Items::FieldUsergrp)
|
65
67
|
key = _key(urg)
|
66
|
-
@log.debug('User/role/group read: %s'
|
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'
|
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'
|
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'
|
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'
|
142
|
+
json = Items.generate(obj, 'User/Role/Group', Users::ValUser)
|
141
143
|
key = _key(obj['name'])
|
142
|
-
@log.info('User/role/group write: %s'
|
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()
|