sym 2.8.2 → 3.0.1

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.
Files changed (61) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +29 -22
  3. data/.envrc +7 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +158 -920
  6. data/.rubocop_todo.yml +115 -0
  7. data/.travis.yml +16 -26
  8. data/CHANGELOG.md +239 -167
  9. data/Gemfile +1 -0
  10. data/LICENSE +2 -2
  11. data/README.adoc +675 -0
  12. data/README.pdf +29732 -19
  13. data/Rakefile +10 -4
  14. data/bin/changelog +34 -0
  15. data/bin/sym.completion.bash +6 -4
  16. data/codecov.yml +29 -0
  17. data/design/sym-class-dependency-future-refactor.png +0 -0
  18. data/design/sym-class-dependency-vertical.png +0 -0
  19. data/design/sym-class-dependency.graffle +0 -0
  20. data/design/sym-class-dependency.png +0 -0
  21. data/design/sym-help.png +0 -0
  22. data/exe/keychain +3 -3
  23. data/exe/sym +8 -5
  24. data/lib/ruby_warnings.rb +7 -0
  25. data/lib/sym.rb +2 -8
  26. data/lib/sym/app.rb +7 -9
  27. data/lib/sym/app/args.rb +3 -2
  28. data/lib/sym/app/cli.rb +34 -23
  29. data/lib/sym/app/cli_slop.rb +17 -11
  30. data/lib/sym/app/commands.rb +1 -1
  31. data/lib/sym/app/commands/base_command.rb +2 -1
  32. data/lib/sym/app/commands/bash_completion.rb +3 -3
  33. data/lib/sym/app/commands/keychain_add_key.rb +1 -1
  34. data/lib/sym/app/commands/open_editor.rb +1 -1
  35. data/lib/sym/app/commands/password_protect_key.rb +4 -4
  36. data/lib/sym/app/commands/show_examples.rb +6 -6
  37. data/lib/sym/app/input/handler.rb +8 -2
  38. data/lib/sym/app/keychain.rb +15 -9
  39. data/lib/sym/app/output/base.rb +1 -1
  40. data/lib/sym/app/output/noop.rb +2 -1
  41. data/lib/sym/app/password/cache.rb +1 -1
  42. data/lib/sym/app/password/providers.rb +3 -6
  43. data/lib/sym/app/private_key/decryptor.rb +2 -2
  44. data/lib/sym/app/private_key/detector.rb +4 -7
  45. data/lib/sym/app/private_key/key_source_check.rb +2 -3
  46. data/lib/sym/application.rb +9 -14
  47. data/lib/sym/configuration.rb +1 -5
  48. data/lib/sym/constants.rb +40 -24
  49. data/lib/sym/data.rb +2 -2
  50. data/lib/sym/data/wrapper_struct.rb +20 -12
  51. data/lib/sym/errors.rb +13 -2
  52. data/lib/sym/extensions/instance_methods.rb +11 -12
  53. data/lib/sym/extensions/stdlib.rb +2 -3
  54. data/lib/sym/extensions/with_retry.rb +1 -1
  55. data/lib/sym/extensions/with_timeout.rb +1 -1
  56. data/lib/sym/version.rb +54 -5
  57. data/sym.gemspec +38 -35
  58. metadata +132 -66
  59. data/.codeclimate.yml +0 -30
  60. data/README.md +0 -623
  61. data/lib/sym/app/password/providers/drb_provider.rb +0 -41
@@ -37,7 +37,7 @@ module Sym
37
37
  # Sort commands based on the #dependencies array, which itself is sorted
38
38
  # based on command dependencies.
39
39
  def sorted_commands
40
- @sorted_commands ||= self.commands.to_a.sort_by{|klass| dependencies.index(klass.short_name) }
40
+ @sorted_commands ||= self.commands.to_a.sort_by{ |klass| dependencies.index(klass.short_name) }
41
41
  @sorted_commands
42
42
  end
43
43
 
@@ -50,6 +50,7 @@ module Sym
50
50
  extend Forwardable
51
51
 
52
52
  attr_accessor :application
53
+
53
54
  def_delegators :@application, :opts, :opts_slop, :key, :stdin, :stdout, :stderr, :kernel
54
55
 
55
56
  def initialize(application)
@@ -73,7 +74,7 @@ module Sym
73
74
  end
74
75
 
75
76
  def add_to_keychain_if_needed(key)
76
- if opts[:keychain] && Sym::App.is_osx?
77
+ if opts[:keychain] && Sym::App.osx?
77
78
  Sym::App::KeyChain.new(opts[:keychain], opts).add(key)
78
79
  else
79
80
  key
@@ -15,7 +15,7 @@ module Sym
15
15
  file = opts[:bash_support]
16
16
 
17
17
  out = ''
18
- Sym::Constants::Bash::Config.each_pair do |key, config|
18
+ Sym::Constants.config.each_pair do |key, config|
19
19
  script_name = key.to_s
20
20
 
21
21
  # This removes the old version of this file.
@@ -43,14 +43,14 @@ module Sym
43
43
  out << "\nPlease reload your terminal session to activate bash completion\n"
44
44
  out << "and other installed BASH utilities.\n"
45
45
  out << "\nAlternatively, just type #{"source #{file}".bold.green} to reload BASH.\n"
46
- out << "Also — go ahead and try running #{"sym -h".bold.blue} and #{"symit -h".bold.blue}.\n"
46
+ out << "Also — go ahead and try running #{'sym -h'.bold.blue} and #{'symit -h'.bold.blue}.\n"
47
47
  end
48
48
 
49
49
  private
50
50
 
51
51
  def append_completion_script(file, script)
52
52
  File.open(file, 'a') do |fd|
53
- fd.write(script + "\n")
53
+ fd.write("#{script}\n")
54
54
  end
55
55
  end
56
56
 
@@ -13,7 +13,7 @@ module Sym
13
13
 
14
14
  def execute
15
15
  if Sym.default_key? && Sym.default_key == self.key
16
- raise 'Refusing to import key specified in the default key file ' + Sym.default_key_file.italic
16
+ raise "Refusing to import key specified in the default key file #{Sym.default_key_file.italic}"
17
17
  end
18
18
  raise Sym::Errors::NoPrivateKeyFound.new("Unable to resolve private key from argument '#{opts[:key]}'") if self.key.nil?
19
19
  add_to_keychain_if_needed(self.key)
@@ -48,7 +48,7 @@ module Sym
48
48
  end
49
49
 
50
50
  def timestamp
51
- @timestamp ||= Time.now.to_a.select { |d| d.is_a?(Fixnum) }.map { |d| '%02d' % d }[0..-3].reverse.join
51
+ @timestamp ||= Time.now.to_a.select { |d| d.is_a?(Integer) }.map { |d| '%02d' % d }[0..-3].reverse.join
52
52
  end
53
53
 
54
54
  def process(code)
@@ -16,10 +16,10 @@ module Sym
16
16
  the_key = self.key
17
17
 
18
18
  if opts[:password]
19
- encrypted_key, password = encrypt_with_password(the_key)
20
- add_password_to_the_cache(encrypted_key, password)
21
- the_key = encrypted_key
22
- end
19
+ encrypted_key, password = encrypt_with_password(the_key)
20
+ add_password_to_the_cache(encrypted_key, password)
21
+ the_key = encrypted_key
22
+ end
23
23
 
24
24
  add_to_keychain_if_needed(the_key)
25
25
 
@@ -16,12 +16,12 @@ module Sym
16
16
  result: '75ngenJpB6zL47/8Wo7Ne6JN1pnOsqNEcIqblItpfg4='.green)
17
17
 
18
18
  output << example(comment: 'generate a new key with a cached password & save to the default key file',
19
- command: 'sym -gcpqo ' + Sym.default_key_file,
20
- echo: 'New Password : ' + '••••••••••'.green,
21
- result: 'Confirm Password : ' + '••••••••••'.green)
19
+ command: "sym -gcpqo #{Sym.default_key_file}",
20
+ echo: "New Password : #{'••••••••••'.green}",
21
+ result: "Confirm Password : #{'••••••••••'.green}")
22
22
 
23
23
  output << example(comment: 'encrypt a plain text string with default key file, and immediately decrypt it',
24
- command: 'sym -es ' + '"secret string"'.bold.yellow + ' | sym -d',
24
+ command: "sym -es #{'"secret string"'.bold.yellow} | sym -d",
25
25
  result: 'secret string'.green)
26
26
 
27
27
  output << example(comment: 'encrypt secrets file using key in the environment, and --negate option:',
@@ -35,7 +35,7 @@ module Sym
35
35
  result: 'secret string'.green)
36
36
 
37
37
  output << example(comment: 'encrypt/decrypt sym.yml using the default key file',
38
- command: 'sym -gcq > ' + Sym.default_key_file,
38
+ command: "sym -gcq > #{Sym.default_key_file}",
39
39
  echo: 'sym -n secrets.yml',
40
40
  result: 'sym -df secrets.yml.enc',
41
41
  )
@@ -56,7 +56,7 @@ Diff:
56
56
  # (c) 2016 Konstantin Gredeskoul. All rights reserved.'.green.bold)
57
57
 
58
58
 
59
- if Sym::App.is_osx?
59
+ if Sym::App.osx?
60
60
  output << example(comment: 'generate a new password-encrypted key, save it to your Keychain:',
61
61
  command: 'sym -gpcx staging.key')
62
62
 
@@ -6,7 +6,7 @@ module Sym
6
6
  class Handler
7
7
  attr_accessor :stdin, :stdout, :stderr, :kernel
8
8
 
9
- def initialize(stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = nil)
9
+ def initialize(stdin = $stdin, stdout = $stdout, stderr = $stderr, kernel = nil)
10
10
  self.stdin = stdin
11
11
  self.stdout = stdout
12
12
  self.stderr = stderr
@@ -27,7 +27,13 @@ module Sym
27
27
  end
28
28
 
29
29
  def prompt(message, color)
30
- raise Sym::Errors::CantReadPasswordNoTTY.new('key requires a password, however STDIN is not a TTY') unless stdin.tty?
30
+ unless $stdin.isatty && $stdin.tty?
31
+ raise Sym::Errors::CantReadPasswordNoTTY.new('key requires a password, however STDIN is not a TTY')
32
+ end
33
+ highline(message, color)
34
+ end
35
+
36
+ def highline(message, color)
31
37
  HighLine.new(stdin, stderr).ask(message.bold) { |q| q.echo = '•'.send(color) }
32
38
  end
33
39
 
@@ -39,11 +39,13 @@ module Sym
39
39
  self.key_name = key_name
40
40
  self.opts = opts
41
41
  self.class.validate!
42
- stderr_off
42
+ opts[:trace] ? stderr_on : stderr_off
43
43
  end
44
44
 
45
45
  def add(password)
46
- execute command(:add, "-U -w '#{password}' ")
46
+ delete rescue nil
47
+ sleep 0.1
48
+ execute command(:add, " -T /usr/bin/security -w '#{password}' ")
47
49
  end
48
50
 
49
51
  def find
@@ -56,10 +58,14 @@ module Sym
56
58
 
57
59
  def execute(command)
58
60
  command += ' 2>/dev/null' if stderr_disabled
59
- puts "> #{command.yellow.green}" if opts[:verbose]
61
+ puts "> #{command.yellow}" if opts[:verbose]
60
62
  output = `#{command}`
61
63
  result = $?
62
- raise Sym::Errors::KeyChainCommandError.new("Command error: #{result}, command: #{command}") unless result.success?
64
+ unless result.success?
65
+ warn "> ERROR running command:\n> $ #{output.red}" if !stderr_disabled && opts[:verbose]
66
+ raise Sym::Errors::KeyChainCommandError.new("Command error: #{result}, command: #{command}")
67
+ end
68
+
63
69
  output.chomp
64
70
  rescue Errno::ENOENT => e
65
71
  raise Sym::Errors::KeyChainCommandError.new("Command error: #{e.message}, command: #{command}")
@@ -80,16 +86,16 @@ module Sym
80
86
  out << extras if extras
81
87
  out = out.join
82
88
  # Do not actually ever run these commands on non MacOSX
83
- out = "echo Run this –\"#{out}\", on #{Sym::App.this_os}?\nAre you sure?" unless Sym::App.is_osx?
89
+ out = "echo Run this –\"#{out}\", on #{Sym::App.this_os}?\nAre you sure?" unless Sym::App.osx?
84
90
  out
85
91
  end
86
92
 
87
93
  def base_command(action)
88
94
  [
89
- "security #{action}-#{self.class.sub_section} ",
90
- "-a '#{self.class.user}' ",
91
- "-D '#{self.class.kind}' ",
92
- "-s '#{self.key_name}' "
95
+ "/usr/bin/security #{action}-#{self.class.sub_section} ",
96
+ "-a #{self.class.user} ",
97
+ "-D #{self.class.kind} ",
98
+ "-s #{self.key_name} "
93
99
  ]
94
100
  end
95
101
  end
@@ -6,7 +6,7 @@ module Sym
6
6
 
7
7
  attr_accessor :opts, :stdin, :stdout, :stderr, :kernel
8
8
 
9
- def initialize(opts, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = nil)
9
+ def initialize(opts, stdin = $stdin, stdout = $stdout, stderr = $stderr, kernel = nil)
10
10
  self.opts = opts
11
11
  self.stdin = stdin
12
12
  self.stdout = stdout
@@ -7,7 +7,8 @@ module Sym
7
7
  required_option :quiet
8
8
 
9
9
  def output_proc
10
- ->(*) { ; }
10
+ ->(*) do
11
+ end
11
12
  end
12
13
  end
13
14
  end
@@ -36,7 +36,7 @@ module Sym
36
36
  self.enabled = opts[:enabled]
37
37
  self.verbose = opts[:verbose]
38
38
  self.timeout = opts[:timeout] || ::Sym::Configuration.config.password_cache_timeout
39
- self.provider = Providers.provider(opts[:provider], opts[:provider_opts] || {})
39
+ self.provider = Providers.provider(opts[:provider], **(opts[:provider_opts] || {}))
40
40
  self.enabled = false unless self.provider
41
41
  self
42
42
  end
@@ -4,9 +4,7 @@ module Sym
4
4
  module Providers
5
5
 
6
6
  class << self
7
- attr_accessor :registry
8
- attr_accessor :providers
9
- attr_accessor :detected
7
+ attr_accessor :registry, :providers, :detected
10
8
 
11
9
  def register(provider_class)
12
10
  self.registry ||= {}
@@ -15,7 +13,7 @@ module Sym
15
13
  self.providers << provider_class
16
14
  end
17
15
 
18
- # Detect first instance that is "alive?" and return it.
16
+ # Detect first instance tht is "alive?" and return it.
19
17
  def detect
20
18
  self.detected ||= self.providers.inject(nil) do |instance, provider_class|
21
19
  instance || (p = provider_class.new; p.alive? ? p : nil)
@@ -38,7 +36,7 @@ module Sym
38
36
 
39
37
  def provider_from_argument(p, **opts, &block)
40
38
  case p
41
- when String, Symbol
39
+ when String, Symbol
42
40
  provider_class_name = "#{p.to_s.capitalize}Provider"
43
41
  Sym::App::Password::Providers.const_defined?(provider_class_name) ?
44
42
  Sym::App::Password::Providers.const_get(provider_class_name).new(**opts, &block) :
@@ -53,4 +51,3 @@ end
53
51
 
54
52
  # Order is important — they are tried in this order for auto detect
55
53
  require 'sym/app/password/providers/memcached_provider'
56
- require 'sym/app/password/providers/drb_provider'
@@ -31,10 +31,10 @@ module Sym
31
31
  rescue ::OpenSSL::Cipher::CipherError => e
32
32
  input_handler.puts 'Invalid password. Please try again.'
33
33
 
34
- if ((retries += 1) < 3)
34
+ if (retries += 1) < 3
35
35
  retry
36
36
  else
37
- raise(Sym::Errors::InvalidPasswordProvidedForThePrivateKey.new('Invalid password.'))
37
+ raise(Sym::Errors::WrongPasswordForKey.new('Invalid password.'))
38
38
  end
39
39
  end
40
40
  else
@@ -23,11 +23,10 @@ module Sym
23
23
  # procs on a given string.
24
24
  def read!
25
25
  KeySourceCheck::CHECKS.each do |source_check|
26
- if result = source_check.detect(self) rescue nil
27
- if key_ = normalize_key(result.key)
28
- key_source_ = result.to_s
29
- return key_, key_source_
30
- end
26
+ next unless result = source_check.detect(self) rescue nil
27
+ if key_ = normalize_key(result.key)
28
+ key_source_ = result.to_s
29
+ return key_, key_source_
31
30
  end
32
31
  end
33
32
  nil
@@ -51,8 +50,6 @@ module Sym
51
50
  rescue
52
51
  nil
53
52
  end
54
- else
55
- nil
56
53
  end
57
54
  end
58
55
  end
@@ -16,9 +16,8 @@ module Sym
16
16
  attr_accessor :name, :reducted, :input, :output
17
17
 
18
18
  def initialize(name:,
19
- reducted: false,
20
- input: ->(detector) { detector.opts[:key] },
21
- output:)
19
+ output:, reducted: false,
20
+ input: ->(detector) { detector.opts[:key] })
22
21
 
23
22
  self.name = name
24
23
  self.reducted = reducted
@@ -31,8 +31,7 @@ module Sym
31
31
  :password_cache,
32
32
  :stdin, :stdout, :stderr, :kernel
33
33
 
34
- def initialize(opts, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = nil)
35
-
34
+ def initialize(opts, stdin = $stdin, stdout = $stdout, stderr = $stderr, kernel = nil)
36
35
  self.stdin = stdin
37
36
  self.stdout = stdout
38
37
  self.stderr = stderr
@@ -111,16 +110,12 @@ module Sym
111
110
  end
112
111
 
113
112
  def editor
114
- editors_to_try.find { |editor| File.exist?(editor) }
113
+ editors_to_try.compact.find { |editor| File.exist?(editor) }
115
114
  end
116
115
 
117
116
  def process_output(result)
118
- unless result.is_a?(Hash)
119
- self.output.call(result)
120
- result
121
- else
122
- result
123
- end
117
+ self.output.call(result) unless result.is_a?(Hash)
118
+ result
124
119
  end
125
120
 
126
121
  private
@@ -182,7 +177,7 @@ module Sym
182
177
  args[:verbose] = opts[:verbose]
183
178
  args[:provider] = opts[:cache_provider] if opts[:cache_provider]
184
179
 
185
- self.password_cache = Sym::App::Password::Cache.instance.configure(args)
180
+ self.password_cache = Sym::App::Password::Cache.instance.configure(**args)
186
181
  end
187
182
 
188
183
  def process_edit_option
@@ -207,7 +202,7 @@ module Sym
207
202
  end
208
203
 
209
204
  def initialize_action
210
- self.action = if opts[:encrypt] then
205
+ self.action = if opts[:encrypt]
211
206
  :encr
212
207
  elsif opts[:decrypt]
213
208
  :decr
@@ -217,7 +212,7 @@ module Sym
217
212
  # If we are encrypting or decrypting, and no data has been provided, check if we
218
213
  # should read from STDIN
219
214
  def initialize_data_source
220
- if self.action && opts[:string].nil? && opts[:file].nil? && !(self.stdin.tty?)
215
+ if self.action && opts[:string].nil? && opts[:file].nil? && !self.stdin.tty?
221
216
  opts[:file] = '-'
222
217
  end
223
218
  end
@@ -229,9 +224,9 @@ module Sym
229
224
  detect_key_source
230
225
  if args.require_key? && !self.key
231
226
  log :error, 'Unable to determine the key, which appears to be required with current args'
232
- raise Sym::Errors::NoPrivateKeyFound, 'Private key is required when ' + (self.action ? self.action.to_s + 'ypting' : provided_flags.join(', '))
227
+ raise Sym::Errors::NoPrivateKeyFound, "Private key is required when #{self.action ? "#{self.action.to_s}ypting" : provided_flags.join(', ')}"
233
228
  end
234
- log :debug, "initialize_key_source: detected key ends with [...#{(key ? key[-5..-1] : 'nil').bold.magenta}]"
229
+ log :debug, "initialize_key_source: detected key ends with [...#{(key ? key[-5..] : 'nil').bold.magenta}]"
235
230
  log :debug, "opts: #{self.provided_value_options.to_s.green.bold}"
236
231
  log :debug, "flags: #{self.provided_flags.to_s.green.bold}"
237
232
  end
@@ -35,10 +35,6 @@ module Sym
35
35
 
36
36
  # See file +lib/sym.rb+ where these values are defined.
37
37
 
38
- attr_accessor :data_cipher, :password_cipher, :private_key_cipher
39
- attr_accessor :compression_enabled, :compression_level
40
- attr_accessor :password_cache_default_provider, :password_cache_timeout
41
- attr_accessor :password_cache_arguments
42
- attr_accessor :default_key_file, :encrypted_file_extension
38
+ attr_accessor :data_cipher, :password_cipher, :private_key_cipher, :compression_enabled, :compression_level, :password_cache_default_provider, :password_cache_timeout, :password_cache_arguments, :default_key_file, :encrypted_file_extension
43
39
  end
44
40
  end
data/lib/sym/constants.rb CHANGED
@@ -1,43 +1,59 @@
1
1
  require 'logger'
2
2
  module Sym
3
+ #
4
+ # This module is responsible for installing Sym BASH extensions.
5
+ #
3
6
  module Constants
4
- module Bash
5
7
 
6
- BASH_FILES = Dir.glob("#{File.expand_path('../../../bin', __FILE__)}/sym.*.bash").freeze
8
+ BASH_FILES = Dir.glob("#{File.expand_path('../../bin', __dir__)}/sym.*.bash").freeze
7
9
 
8
- Config = {}
10
+ class << self
11
+ attr_reader :user_home
9
12
 
10
- class << self
11
- def register_bash_files!
12
- BASH_FILES.each do |bash_file|
13
- register_bash_extension bash_file, Config
14
- end
15
- end
13
+ def user_home=(value)
14
+ @user_home = value
15
+ register_bash_files!
16
+ end
16
17
 
17
- private
18
+ def config
19
+ @config ||= {}
20
+ end
18
21
 
19
- def register_bash_extension(bash_file, hash)
20
- source_file = File.basename(bash_file)
21
- home_file = "#{Dir.home}/.#{source_file}"
22
+ def sym_key_file
23
+ "#{user_home}/.sym.key"
24
+ end
22
25
 
23
- hash[source_file.gsub(/sym\./, '').gsub(/\.bash/, '').to_sym] = {
24
- dest: home_file,
25
- source: bash_file,
26
- script: "[[ -f #{home_file} ]] && source #{home_file}"
27
- }
26
+ def register_bash_files!
27
+ BASH_FILES.each do |bash_file|
28
+ register_bash_extension bash_file
28
29
  end
29
30
  end
30
31
 
31
- self.register_bash_files!
32
+ private
33
+
34
+ def register_bash_extension(bash_file)
35
+ return unless user_home && Dir.exist?(user_home)
36
+
37
+ source_file = File.basename(bash_file)
38
+ home_file = "#{user_home}/.#{source_file}"
39
+ config_key = source_file.gsub(/sym\./, '').gsub(/\.bash/, '').to_sym
40
+
41
+ config[config_key] = {
42
+ dest: home_file,
43
+ source: bash_file,
44
+ script: "[[ -f #{home_file} ]] && source #{home_file}"
45
+ }
46
+ end
32
47
  end
33
48
 
49
+ self.user_home ||= ::Dir.home rescue nil
50
+ self.user_home ||= '/tmp'
51
+
52
+ self.register_bash_files!
53
+
34
54
  module Log
35
55
  NIL = Logger.new(nil).freeze # empty logger
36
- LOG = Logger.new(STDERR).freeze
56
+ LOG = Logger.new($stderr).freeze
37
57
  end
38
-
39
- ENV_ARGS_VARIABLE_NAME = 'SYM_ARGS'.freeze
40
- SYM_KEY_FILE = "#{ENV['HOME']}/.sym.key"
41
-
42
58
  end
43
59
  end