osp 0.6.1 → 0.7.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.
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Release gem to https://rubygems.org.
4
+
5
+ SCRIPT_BASEDIR=$(dirname "$0")
6
+
7
+
8
+ set -e
9
+ which mv &> /dev/null || { echo 'ERROR: mv not found in PATH'; exit 1; }
10
+ which gem &> /dev/null || { echo 'ERROR: gem not found in PATH'; exit 1; }
11
+
12
+ cd "${SCRIPT_BASEDIR}/.."
13
+
14
+ # Load Environment Variables
15
+ [[ -f .env ]] && source .env
16
+
17
+ if [[ -z "${GEMSPEC_FILE}" ]] ; then
18
+ echo 'ERROR: one of the environment variables is missing'
19
+
20
+ echo "GEMSPEC_FILE: '${GEMSPEC_FILE}'"
21
+
22
+ exit 1
23
+ fi
24
+
25
+ gem_file=$(gem build "${GEMSPEC_FILE}" 2> /dev/null | grep 'File:' | tail -1 | awk '{ print $2 }')
26
+
27
+ echo "push gem file '$gem_file'"
28
+ gem push "$gem_file"
29
+
30
+ # Create tmp directory.
31
+ if [[ ! -d tmp/releases ]]; then
32
+ mkdir -p tmp/releases
33
+
34
+ chmod u=rwx,go-rwx tmp
35
+ chmod u=rwx,go-rwx tmp/releases
36
+ fi
37
+ mv -v -i "$gem_file" tmp/releases
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Run Tests.
4
+
5
+ SCRIPT_BASEDIR=$(dirname "$0")
6
+ RUBYOPT=-w
7
+ TZ=Europe/Vienna
8
+
9
+
10
+ set -e
11
+ which bundler &> /dev/null || { echo 'ERROR: bundler not found in PATH'; exit 1; }
12
+
13
+ cd "${SCRIPT_BASEDIR}/.."
14
+
15
+ bundler exec ./test/suite_all.rb
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Uninstall gem.
4
+
5
+ SCRIPT_BASEDIR=$(dirname "$0")
6
+
7
+
8
+ set -e
9
+ which gem &> /dev/null || { echo 'ERROR: gem not found in PATH'; exit 1; }
10
+
11
+ cd "${SCRIPT_BASEDIR}/.."
12
+
13
+ # Load Environment Variables
14
+ [[ -f .env ]] && source .env
15
+
16
+ if [[ -z "${GEM_NAME}" ]] ; then
17
+ echo 'ERROR: one of the environment variables is missing'
18
+
19
+ echo "GEM_NAME: '${GEM_NAME}'"
20
+
21
+ exit 1
22
+ fi
23
+
24
+ gem uninstall "${GEM_NAME}" --all --executables
@@ -2,7 +2,7 @@
2
2
  require 'thefox-ext'
3
3
 
4
4
  class String
5
- def is_valid?
6
- is_upper? || is_lower? || is_digit?
7
- end
5
+ def is_valid?
6
+ is_upper? || is_lower? || is_digit?
7
+ end
8
8
  end
@@ -3,188 +3,199 @@ require 'pathname'
3
3
  require 'fileutils'
4
4
 
5
5
  module TheFox
6
- module OSP
7
-
8
- class Database
9
-
10
- attr_accessor :has_changed
11
-
12
- def initialize(file_path, osp)
13
- @file_path = file_path
14
- @osp = osp
15
- @load_callback_method = nil
16
- @write_callback_method = nil
17
- @has_changed = false
18
-
19
- @data = {
20
- 'meta' => {
21
- 'version' => 1,
22
- 'created_at' => DateTime.now.to_s,
23
- 'updated_at' => DateTime.now.to_s,
24
- },
25
- 'hosts' => Hash.new,
26
- }
27
- end
28
-
29
- def load_callback_method=(m)
30
- @load_callback_method = m
31
- end
32
-
33
- def load_callback(*o)
34
- if !@load_callback_method.nil?
35
- @load_callback_method.call(*o)
36
- end
37
- end
38
-
39
- def load
40
- load_callback(1000, 'Check for existing database file.')
41
-
42
- if @file_path.exist?
43
- load_callback(1050, "Use database file: #{@file_path}")
44
-
45
- load_callback(1100, "Read file '#{@file_path}'.")
46
- db_meta = File.binread(@file_path)
47
-
48
- load_callback(1200, 'Process database metadata.')
49
- db_meta = Base64.strict_decode64(db_meta)
50
- db_meta = MessagePack.unpack(db_meta)
51
-
52
- db_e = Base64.strict_decode64(db_meta['db'])
53
- mac = OpenSSL::Digest::SHA256.digest(db_e)
54
- if db_meta['mac'] == mac
55
- load_callback(1300, 'Setup database decryption.')
56
- dk_sha256 = OpenSSL::Digest::SHA256.digest(@osp.dk)
57
- iv = Base64.strict_decode64(db_meta['iv'])
58
-
59
- aes = OpenSSL::Cipher::Cipher.new('AES-256-CBC')
60
- aes.decrypt
61
- aes.key = dk_sha256
62
- aes.iv = iv
63
-
64
- begin
65
- load_callback(1350, 'Decrypt database.')
66
- db_b64 = aes.update(db_e)
67
- db_b64 << aes.final
68
- rescue Exception #=> e
69
- raise 'Incorrect email and password combination.'
70
- end
71
-
72
- load_callback(1400, 'Build database.')
73
- @data = MessagePack.unpack(Base64.strict_decode64(db_b64))
74
-
75
- @data['hosts'] = @data['hosts'].map{ |name, host|
76
- host_o = TheFox::OSP::Host.from_h(host)
77
- host_o.osp = @osp
78
- [name, host_o]
79
- }.to_h
80
-
81
- load_callback(9000, 'Database startup done.')
82
- else
83
- raise 'Database integrity check failed.'
84
- end
85
- else
86
- load_callback(9500, 'Database startup done.')
87
- end
88
- end
89
-
90
- def write_callback_method=(m)
91
- @write_callback_method = m
92
- end
93
-
94
- def write_callback(*o)
95
- unless @write_callback_method.nil?
96
- @write_callback_method.call(*o)
97
- end
98
- end
99
-
100
- def write
101
- write_callback(1000, 'Check database for changes.')
102
-
103
- if @has_changed
104
- tmp = Pathname.new("#{@file_path}~").expand_path
105
-
106
- # http://stackoverflow.com/questions/9049789/aes-encryption-key-versus-iv
107
- # http://keepass.info/help/base/security.html
108
- # https://gist.github.com/byu/99651
109
-
110
- write_callback(1100, 'Make temp database.')
111
- db_c = @data.clone
112
- db_c['hosts'] = db_c['hosts'].map{ |name, host| [name, host.clone.to_h] }.to_h
113
-
114
- write_callback(1200, 'Setup database encryption.')
115
- dk_sha256 = OpenSSL::Digest::SHA256.digest(@osp.dk)
116
- iv = OpenSSL::Cipher::Cipher.new('AES-256-CBC').random_iv
117
-
118
- aes = OpenSSL::Cipher::Cipher.new('AES-256-CBC')
119
- aes.encrypt
120
- aes.key = dk_sha256
121
- aes.iv = iv
122
-
123
- write_callback(1250, 'Encrypt database.')
124
- db_e = aes.update(Base64.strict_encode64(db_c.to_msgpack))
125
- db_e << aes.final
126
-
127
- mac = OpenSSL::Digest::SHA256.digest(db_e)
128
-
129
- db_out = {
130
- 'version' => 1,
131
- 'iv' => Base64.strict_encode64(iv),
132
- 'db' => Base64.strict_encode64(db_e),
133
- 'mac' => mac,
134
- }
135
- db_out = db_out.to_msgpack
136
- db_out = Base64.strict_encode64(db_out)
137
-
138
- write_callback(1300, "Write temp file to '#{tmp}'.")
139
- File.write(tmp, 'tmp')
140
- tmp.chmod(0600)
141
- File.binwrite(tmp, db_out)
142
-
143
- backup_dts = Time.now.strftime('%Y%m%d-%H%M%S')
144
- backup = Pathname.new("#{@file_path}~backup_#{backup_dts}_" << Digest::SHA256.file(tmp.to_s).hexdigest[0..7])
145
-
146
- write_callback(1350, "Backup temp file to '#{backup}'.")
147
- File.write(backup, 'tmp')
148
- backup.chmod(0600)
149
- FileUtils.cp(tmp, backup)
150
-
151
- write_callback(1390, "Finally, move temp file to '#{@file_path}'.")
152
- File.write(@file_path, 'tmp')
153
- @file_path.chmod(0600)
154
- tmp.rename(@file_path)
155
-
156
- @has_changed = false
157
- else
158
- write_callback(9500, 'Nothing changed, nothing written.')
159
- end
160
- end
161
-
162
- def update
163
- @data['meta']['updated_at'] = DateTime.now.to_s
164
- end
165
-
166
- def hosts
167
- @data['hosts']
168
- end
169
-
170
- def add_host(host)
171
- @data['hosts'][host.name] = host
172
- update
173
- @has_changed = true
174
- end
175
-
176
- def remove_host(host)
177
- if @data['hosts'].has_key?(host.name)
178
- @data['hosts'].delete(host.name)
179
- update
180
- @has_changed = true
181
- true
182
- else
183
- false
184
- end
185
- end
186
-
187
- end
188
-
189
- end
6
+ module OSP
7
+
8
+ class Database
9
+
10
+ attr_accessor :has_changed
11
+
12
+ def initialize(file_path, osp)
13
+ @file_path = file_path
14
+ @osp = osp
15
+ @load_callback_method = nil
16
+ @write_callback_method = nil
17
+ @has_changed = false
18
+
19
+ @data = {
20
+ 'meta' => {
21
+ 'version' => 1,
22
+ 'created_at' => DateTime.now.to_s,
23
+ 'updated_at' => DateTime.now.to_s,
24
+ },
25
+ 'hosts' => Hash.new,
26
+ }
27
+ end
28
+
29
+ def load_callback_method=(m)
30
+ @load_callback_method = m
31
+ end
32
+
33
+ def load_callback(*o)
34
+ if !@load_callback_method.nil?
35
+ @load_callback_method.call(*o)
36
+ end
37
+ end
38
+
39
+ def load
40
+ load_callback(1000, 'Check for existing database file.')
41
+
42
+ if @file_path.exist?
43
+ load_callback(1050, "Use database file: #{@file_path}")
44
+
45
+ load_callback(1100, "Read file '#{@file_path}'.")
46
+ db_meta = File.binread(@file_path)
47
+
48
+ load_callback(1200, 'Process database metadata.')
49
+ db_meta = Base64.strict_decode64(db_meta)
50
+ db_meta = MessagePack.unpack(db_meta)
51
+
52
+ db_e = Base64.strict_decode64(db_meta['db'])
53
+ mac = OpenSSL::Digest::SHA256.digest(db_e)
54
+ if db_meta['mac'] == mac
55
+ load_callback(1300, 'Setup database decryption.')
56
+ dk_sha256 = OpenSSL::Digest::SHA256.digest(@osp.dk)
57
+ iv = Base64.strict_decode64(db_meta['iv'])
58
+
59
+ aes = OpenSSL::Cipher::AES.new(256, 'CBC')
60
+ aes.decrypt
61
+ aes.key = dk_sha256
62
+ aes.iv = iv
63
+
64
+ begin
65
+ load_callback(1350, 'Decrypt database.')
66
+ db_b64 = aes.update(db_e)
67
+ db_b64 << aes.final
68
+ rescue Exception #=> e
69
+ raise 'Incorrect email and password combination.'
70
+ end
71
+
72
+ load_callback(1400, 'Build database.')
73
+ @data = MessagePack.unpack(Base64.strict_decode64(db_b64))
74
+
75
+ @data['hosts'] = @data['hosts'].map{ |name, host|
76
+ host_o = TheFox::OSP::Host.from_h(host)
77
+ host_o.osp = @osp
78
+ [name, host_o]
79
+ }.to_h
80
+
81
+ load_callback(9000, 'Database startup done.')
82
+ else
83
+ raise 'Database integrity check failed.'
84
+ end
85
+ else
86
+ load_callback(9500, 'Database startup done.')
87
+ end
88
+ end
89
+
90
+ def write_callback_method=(m)
91
+ @write_callback_method = m
92
+ end
93
+
94
+ def write_callback(*o)
95
+ unless @write_callback_method.nil?
96
+ @write_callback_method.call(*o)
97
+ end
98
+ end
99
+
100
+ def write
101
+ write_callback(1000, 'Check database for changes.')
102
+
103
+ if @has_changed
104
+ tmp = Pathname.new("#{@file_path}~").expand_path
105
+
106
+ # http://stackoverflow.com/questions/9049789/aes-encryption-key-versus-iv
107
+ # http://keepass.info/help/base/security.html
108
+ # https://gist.github.com/byu/99651
109
+
110
+ write_callback(1100, 'Make temp database.')
111
+ db_c = @data.clone
112
+ db_c['hosts'] = db_c['hosts'].map{ |name, host| [name, host.clone.to_h] }.to_h
113
+
114
+ write_callback(1200, 'Setup database encryption.')
115
+ dk_sha256 = OpenSSL::Digest::SHA256.digest(@osp.dk)
116
+ iv = OpenSSL::Cipher::AES.new(256, 'CBC').random_iv
117
+
118
+ aes = OpenSSL::Cipher::AES.new(256, 'CBC')
119
+ aes.encrypt
120
+ aes.key = dk_sha256
121
+ aes.iv = iv
122
+
123
+ write_callback(1250, 'Encrypt database.')
124
+ db_e = aes.update(Base64.strict_encode64(db_c.to_msgpack))
125
+ db_e << aes.final
126
+
127
+ mac = OpenSSL::Digest::SHA256.digest(db_e)
128
+
129
+ db_out = {
130
+ 'version' => 1,
131
+ 'iv' => Base64.strict_encode64(iv),
132
+ 'db' => Base64.strict_encode64(db_e),
133
+ 'mac' => mac,
134
+ }
135
+ db_out = db_out.to_msgpack
136
+ db_out = Base64.strict_encode64(db_out)
137
+
138
+ write_callback(1300, "Write temp file to '#{tmp}'.")
139
+ File.write(tmp, 'tmp')
140
+ tmp.chmod(0600)
141
+ File.binwrite(tmp, db_out)
142
+
143
+ backup_dts = Time.now.strftime('%Y%m%d-%H%M%S')
144
+ backup = Pathname.new("#{@file_path}~backup_#{backup_dts}_" << Digest::SHA256.file(tmp.to_s).hexdigest[0..7])
145
+
146
+ write_callback(1350, "Backup temp file to '#{backup}'.")
147
+ File.write(backup, 'tmp')
148
+ backup.chmod(0600)
149
+ FileUtils.cp(tmp, backup)
150
+
151
+ write_callback(1390, "Finally, move temp file to '#{@file_path}'.")
152
+ File.write(@file_path, 'tmp')
153
+ @file_path.chmod(0600)
154
+ tmp.rename(@file_path)
155
+
156
+ @has_changed = false
157
+ else
158
+ write_callback(9500, 'Nothing changed, nothing written.')
159
+ end
160
+ end
161
+
162
+ def update
163
+ @data['meta']['updated_at'] = DateTime.now.to_s
164
+ end
165
+
166
+ def hosts
167
+ @data['hosts']
168
+ end
169
+
170
+ def add_host(host)
171
+ @data['hosts'][host.name] = host
172
+ update
173
+ @has_changed = true
174
+ end
175
+
176
+ def remove_host(host)
177
+ if @data['hosts'].has_key?(host.name)
178
+ @data['hosts'].delete(host.name)
179
+ update
180
+ @has_changed = true
181
+ true
182
+ else
183
+ false
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Return all hosts and passwords as CSV string.
189
+ def csv
190
+ "HOST,PASSWORD\r\n"
191
+ @data['hosts']
192
+ .map{ |id, host|
193
+ "#{host.name},#{host.password}"
194
+ }
195
+ .join("\r\n")
196
+ end
197
+
198
+ end
199
+
200
+ end
190
201
  end