sym 2.8.2 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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