icfs 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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()
|