kbsecret 0.9.5 → 0.9.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a93f10c5ff4add094ea83dc950239250ffda4e31
4
- data.tar.gz: 3d8ddfe46e26ed3751d541b762c56804fb194c35
3
+ metadata.gz: 95af6343b31d058c770ddc7c9ee0a82c3a5f984c
4
+ data.tar.gz: 6d07e7ea5d2a10b0d2f3a46979d447c3785642ed
5
5
  SHA512:
6
- metadata.gz: d92789c5a47dfccb4422545b9bdf5c7bfc3d086599783336c982cc02a2e37749bab41e9cfc5579db9a3451b726683e044d3efaa302eefd3e5e79d2037af31719
7
- data.tar.gz: 2fc805de715e71bbfc577f9f1258b9b2b46d5cad421126173afd6d8d3864b386e8da035c6b3e99caaa5e361f86d49abfec5e4a93a982dad050ce9e5b0318d5be
6
+ metadata.gz: b60c937f2a8bf3c9271299f602727059af992b0bc2510040ce057fbcd08beae232af1c84d8fa1def9a0e32c362511ec7e6906772a655ed1e8c756f1c83025ba2
7
+ data.tar.gz: 1f11837a4cdd43d7f4439a48544bbf54a7e46715ef069495cf106469bebb65634d5948ee2b370d7d9c75bae960506c2482656321b96031642d8549122db26095
data/lib/kbsecret/cli.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "colored2"
4
3
  require "slop"
5
4
  require "dreck"
6
5
  require "abbrev"
6
+ require "pastel"
7
7
 
8
8
  module KBSecret
9
9
  # An encapsulation of useful methods for kbsecret's CLI.
@@ -13,6 +13,9 @@ module KBSecret
13
13
  # Abbreviations for record types (e.g., `env` for `environment`).
14
14
  TYPE_ALIASES = Hash.new { |_, k| k }.update(Abbrev.abbrev(Record.record_types)).freeze
15
15
 
16
+ # ANSI color objects for prettifying output.
17
+ GREEN, YELLOW, RED = ->(p) { [p.green, p.yellow, p.red].map(&:detach) }.call(Pastel.new)
18
+
16
19
  # @return [Slop::Result, nil] the result of option parsing, if requested
17
20
  # via {#slop}
18
21
  attr_reader :opts
@@ -155,7 +158,7 @@ module KBSecret
155
158
  # @return [void]
156
159
  def info(msg)
157
160
  return unless @opts.verbose?
158
- STDERR.puts "#{"Info".green}: #{msg}"
161
+ STDERR.puts "#{GREEN["Info"]}: #{msg}"
159
162
  end
160
163
 
161
164
  # Print an informational message via {#info} and exit successfully.
@@ -172,7 +175,7 @@ module KBSecret
172
175
  # @return [void]
173
176
  def warn(msg)
174
177
  return if @opts.no_warn?
175
- STDERR.puts "#{"Warning".yellow}: #{msg}"
178
+ STDERR.puts "#{YELLOW["Warning"]}: #{msg}"
176
179
  end
177
180
 
178
181
  # Print an error message and terminate.
@@ -180,7 +183,7 @@ module KBSecret
180
183
  # @return [void]
181
184
  # @note This method does not return!
182
185
  def die(msg)
183
- pretty = "#{"Fatal".red}: #{msg}"
186
+ pretty = "#{RED["Fatal"]}: #{msg}"
184
187
  abort pretty
185
188
  end
186
189
 
@@ -190,7 +193,7 @@ module KBSecret
190
193
  # @return [void]
191
194
  # @note This method does not return!
192
195
  def die(msg)
193
- pretty = "#{"Fatal".red}: #{msg}"
196
+ pretty = "#{RED["Fatal"]}: #{msg}"
194
197
  abort pretty
195
198
  end
196
199
 
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "kbsecret"
5
+ include KBSecret
6
+
7
+ cmd = CLI.create do |c|
8
+ c.slop do |o|
9
+ o.banner = <<~HELP
10
+ Usage:
11
+ kbsecret cp [options] <source> <destination> <record [record ...]>
12
+ HELP
13
+
14
+ o.bool "-f", "--force", "force copying (ignore overwrites)"
15
+ o.bool "-m", "--move", "delete the record after copying"
16
+ end
17
+
18
+ c.dreck do
19
+ string :src_sess
20
+ string :dst_sess
21
+ list :string, :labels
22
+ end
23
+ end
24
+
25
+ cmd.guard do
26
+ src_sess = Session.new label: cmd.args[:src_sess]
27
+ dst_sess = Session.new label: cmd.args[:dst_sess]
28
+
29
+ selected_records = src_sess.records.select { |r| cmd.args[:labels].include?(r.label) }
30
+ cmd.die "No such record(s)." if selected_records.empty?
31
+
32
+ overlaps = dst_sess.record_labels & selected_records.map(&:label)
33
+
34
+ # the code below actually handles the overwriting if necessary, but we fail early here for
35
+ # friendliness and to avoid half-copying the selected records
36
+ unless overlaps.empty? || cmd.opts.force?
37
+ cmd.die "Refusing to overwrite existing record(s) without --force."
38
+ end
39
+
40
+ selected_records.each do |record|
41
+ dst_sess.import_record(record, overwrite: cmd.opts.force?)
42
+ src_sess.delete_record(label) if cmd.opts.move?
43
+ end
44
+ end
@@ -38,6 +38,8 @@ type = cmd.args[:type]
38
38
  label = cmd.args[:label]
39
39
  resolved_type = CLI::TYPE_ALIASES[type]
40
40
 
41
+ # the code below actually handles the overwriting if necessary, but we fail early here
42
+ # for friendliness and to avoid prompting the user for input unnecessarily
41
43
  if cmd.session.record?(label) && !cmd.opts.force?
42
44
  cmd.die "Refusing to overwrite an existing record without --force."
43
45
  end
@@ -61,4 +63,6 @@ fields = if cmd.opts.terse?
61
63
  end
62
64
  end
63
65
 
64
- cmd.guard { cmd.session.add_record(resolved_type, label, *fields) }
66
+ cmd.guard do
67
+ cmd.session.add_record resolved_type, label, *fields, overwrite: cmd.opts.force?
68
+ end
@@ -11,9 +11,9 @@ def new_session(label, cmd)
11
11
  end
12
12
 
13
13
  if cmd.opts[:team]
14
- teams = Keybase::Local::Team.list_memberships["teams"]
14
+ teams = Keybase::Local::Team.list_self_memberships.teams
15
15
 
16
- unless teams.any? { |t| t["fq_name"] == cmd.opts[:team] }
16
+ unless teams.any? { |t| t.fq_name == cmd.opts[:team] }
17
17
  cmd.die "No such team (either nonexistent or non-member)."
18
18
  end
19
19
 
@@ -9,6 +9,7 @@ module KBSecret
9
9
 
10
10
  # Raised during record loading if a particular file can't be loaded
11
11
  class RecordLoadError < KBSecretError
12
+ # @param path [String] the path to the record
12
13
  def initialize(path)
13
14
  base = File.basename(path)
14
15
  super "Failed to load record in file: '#{base}'"
@@ -17,6 +18,7 @@ module KBSecret
17
18
 
18
19
  # Raised during record creation if an unknown record type is requested.
19
20
  class RecordTypeUnknownError < KBSecretError
21
+ # @param type [String, Symbol] the record type
20
22
  def initialize(type)
21
23
  super "Unknown record type: '#{type}'"
22
24
  end
@@ -24,13 +26,25 @@ module KBSecret
24
26
 
25
27
  # Raised during record creation if too many/few arguments are given.
26
28
  class RecordCreationArityError < KBSecretError
29
+ # @param exp [Integer] the number of expected arguments
30
+ # @param act [Integer] the number of actual arguments
27
31
  def initialize(exp, act)
28
32
  super "Needed #{exp} arguments for this record, got #{act}"
29
33
  end
30
34
  end
31
35
 
36
+ # Raised when record creation or import would cause an unintended overwrite.
37
+ class RecordOverwriteError < KBSecretError
38
+ # @param session [Session] the session being modified
39
+ # @param label [String] the label being overwritten in the session
40
+ def initialize(session, label)
41
+ super "Record '#{label}' already exists in '#{session.label}'"
42
+ end
43
+ end
44
+
32
45
  # Raised during session load if an error occurs.
33
46
  class SessionLoadError < KBSecretError
47
+ # @param msg [String] the error message
34
48
  def initialize(msg)
35
49
  super "Session loading failure: #{msg}"
36
50
  end
@@ -38,13 +52,23 @@ module KBSecret
38
52
 
39
53
  # Raised during session lookup if an unknown session is requested.
40
54
  class SessionUnknownError < KBSecretError
55
+ # @param sess [String, Symbol] the label of the session
41
56
  def initialize(sess)
42
57
  super "Unknown session: '#{sess}'"
43
58
  end
44
59
  end
45
60
 
61
+ # Raised during record import if the source is the same as the destination.
62
+ class SessionImportError < KBSecretError
63
+ # @param session [Session] the session being imported into
64
+ def initialize(session)
65
+ super "Session '#{session.label}' cannot import records from itself"
66
+ end
67
+ end
68
+
46
69
  # Raised during generator lookup if an unknown profile is requested.
47
70
  class GeneratorUnknownError < KBSecretError
71
+ # @param gen [String, Symbol] the label of the generator
48
72
  def initialize(gen)
49
73
  super "Unknown generator profile: '#{gen}'"
50
74
  end
@@ -52,6 +76,7 @@ module KBSecret
52
76
 
53
77
  # Raised during generator creation if an unknown generator format is requested.
54
78
  class GeneratorFormatError < KBSecretError
79
+ # @param fmt [String, Symbol] the format of the generator
55
80
  def initialize(fmt)
56
81
  super "Unknown generator format: '#{fmt}'"
57
82
  end
@@ -59,6 +84,7 @@ module KBSecret
59
84
 
60
85
  # Raised during generator creation if a non-positive generator length is requested.
61
86
  class GeneratorLengthError < KBSecretError
87
+ # @param length [Integer] the length of the generator
62
88
  def initialize(length)
63
89
  super "Bad secret generator length (#{length}, must be positive)"
64
90
  end
@@ -62,17 +62,25 @@ module KBSecret
62
62
  # @param type [String, Symbol] the type of record (see {Record.record_types})
63
63
  # @param label [String, Symbol] the new record's label
64
64
  # @param args [Array<String>] the record-type specific arguments
65
+ # @param overwrite [Boolean] whether or not to overwrite an existing record if necessary
65
66
  # @return [void]
66
67
  # @raise [Exceptions::UnknownRecordTypeError] if the requested type does not exist
67
68
  # in {Record.record_types}
68
69
  # @raise [Exceptions::RecordCreationArityError] if the number of specified record
69
70
  # arguments does not match the record type's constructor
70
- def add_record(type, label, *args)
71
+ # @raise [Exceptions::RecordOverwriteError] if the record addition would cause an
72
+ # unchecked overwrite
73
+ def add_record(type, label, *args, overwrite: false)
71
74
  klass = Record.class_for(type.to_sym)
72
75
  arity = klass.external_fields.length
73
76
 
74
77
  raise Exceptions::RecordCreationArityError.new(arity, args.size) unless arity == args.size
75
78
 
79
+ if record? label
80
+ raise Exceptions::RecordOverwriteError.new(self, label) unless overwrite
81
+ delete_record label
82
+ end
83
+
76
84
  body = klass.external_fields.zip(args).to_h
77
85
  record = klass.new(self, label.to_s, **body)
78
86
 
@@ -80,6 +88,26 @@ module KBSecret
80
88
  record.sync!
81
89
  end
82
90
 
91
+ # Import an existing record from another session.
92
+ # @param record [Record] the record to import
93
+ # @param overwrite [Boolean] whether or not to overwrite an existing record if necessary
94
+ # @return [void]
95
+ # @raise [Exceptions::SessionImportError] if the record's source session is our session
96
+ # @raise [Exceptions::RecordOverwriteError] if record import would cause an unchecked overwrite
97
+ def import_record(record, overwrite: false)
98
+ raise Exceptions::SessionImportError, self if self == record.session
99
+
100
+ if record? record.label
101
+ raise Exceptions::RecordOverwriteError.new(self, record.label) unless overwrite
102
+ delete_record record.label
103
+ end
104
+
105
+ klass = record.class
106
+ imported_record = klass.load!(self, record.to_h)
107
+ records << imported_record
108
+ imported_record.sync!
109
+ end
110
+
83
111
  # Delete a record from the session, if it exists. Does nothing if
84
112
  # no such record can be found.
85
113
  # @param label [String, Symbol] the label of the record to delete
@@ -113,6 +141,15 @@ module KBSecret
113
141
  Dir[File.join(path, "*.json")]
114
142
  end
115
143
 
144
+ # Compare two sessions for equality.
145
+ # @param other [Session] the other object to compare against
146
+ # @return [Boolean] whether or not the two sessions are equal
147
+ # @note The equality of two sessions is determined *solely* by them having the same
148
+ # session directory, *not* by having the same label *or* the same in-memory state.
149
+ def ==(other)
150
+ other.class == self.class && other.path == path
151
+ end
152
+
116
153
  # Load all records associated with the session.
117
154
  # @return [Array<Record::Abstract>] all records in the session
118
155
  # @api private
data/lib/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module KBSecret
4
4
  # kbsecret's current version
5
- VERSION = "0.9.5"
5
+ VERSION = "0.9.6"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kbsecret
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-30 00:00:00.000000000 Z
11
+ date: 2017-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aruba
@@ -137,61 +137,61 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '1.1'
139
139
  - !ruby/object:Gem::Dependency
140
- name: colored2
140
+ name: dreck
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '3.1'
145
+ version: 0.2.1
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '3.1'
152
+ version: 0.2.1
153
153
  - !ruby/object:Gem::Dependency
154
- name: dreck
154
+ name: inih
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.2.1
159
+ version: '1.1'
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.2.1
166
+ version: '1.1'
167
167
  - !ruby/object:Gem::Dependency
168
- name: inih
168
+ name: keybase-unofficial
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: '1.1'
173
+ version: '1.0'
174
174
  type: :runtime
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: '1.1'
180
+ version: '1.0'
181
181
  - !ruby/object:Gem::Dependency
182
- name: keybase-unofficial
182
+ name: pastel
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: '0.9'
187
+ version: '0.7'
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: '0.9'
194
+ version: '0.7'
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: slop
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -233,6 +233,7 @@ files:
233
233
  - bin/kbsecret
234
234
  - lib/kbsecret.rb
235
235
  - lib/kbsecret/cli.rb
236
+ - lib/kbsecret/cli/kbsecret-cp
236
237
  - lib/kbsecret/cli/kbsecret-dump-fields
237
238
  - lib/kbsecret/cli/kbsecret-env
238
239
  - lib/kbsecret/cli/kbsecret-generator