tarkin 0.9.3.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 35f0d3f9b104422ff8e9006fdc86edc172a2bced
4
+ data.tar.gz: 07f422fe6aa640ce741e1db7e5c606066bdbec5b
5
+ SHA512:
6
+ metadata.gz: 43ee41bc484127095e744870924552bbb11b595ff22dcffd15c9c4bf96950764298b2ee6d000e04707400f7bb945b80c1c5e41233b7a438bb765143016f94bcf
7
+ data.tar.gz: c83d91be2afe5deb122360ac73f0ca543b771a06749f9ab78786ad6b42f3560a9e2c9dbd2d2891a088fcc0a633de4a3c0d297a55c93e4d6b668c8cfd906b1f17
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tarkin'
3
+ require 'optparse'
4
+ require 'highline/import'
5
+ require 'open-uri'
6
+ include CommandLineReporter if require 'command_line_reporter'
7
+ require 'tarkin_commands'
8
+ require 'tarkin_sh'
9
+
10
+ options = {}
11
+ OptionParser.new do |opts|
12
+ opts.banner = "Usage: tarkin [options] [PATH TO PASSWORD*]"
13
+ opts.separator "Client for Tarkin Team Password Manager: https://github.com/grych/tarkin"
14
+ opts.separator "Options:"
15
+ opts.on("-l", "--ls PATH", "Lists the directory") do |p|
16
+ options[:ls] = p
17
+ end
18
+ opts.on("-f", "--find TERM", "Search for items and directories, may use asterisks *") do |p|
19
+ options[:find] = p
20
+ end
21
+ opts.on("-x", "--long", "Long listing (like ls -l)") do |l|
22
+ options[:long] = l
23
+ end
24
+ opts.separator "Examples:"
25
+ opts.separator "tarkin /db/prod/oracle/scott"
26
+ opts.separator "tarkin --long --list /db/prod"
27
+ opts.separator "tarkin --find scott"
28
+ end.parse!
29
+
30
+ # pp options
31
+
32
+ paths = ARGV
33
+
34
+ client = TarkinClient.new
35
+ commands = TarkinCommands.new(client)
36
+
37
+ if options[:ls]
38
+ commands.ls(options[:ls], options[:long])
39
+ elsif options[:find]
40
+ commands.find(options[:find])
41
+ elsif paths.empty?
42
+ TarkinSh.start client, commands
43
+ else
44
+ paths.each do |pwd|
45
+ if pwd[/^-?\d+$/]
46
+ pwd = pwd.to_i
47
+ else
48
+ pwd = URI::encode(pwd)
49
+ end
50
+ begin
51
+ commands.cat(pwd)
52
+ rescue TarkinClientException => e
53
+ puts "Not found (#{e.message})"
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,568 @@
1
+ # From: https://github.com/hamxiaoz/cmd
2
+ # minor fixed to work with >= 1.9.3 by Tomek 'Grych' Gryszkiewicz <grych@tg.pl>
3
+ #
4
+
5
+ READLINE_SUPPORTED = (require 'readline'; true) rescue false
6
+ require 'abbrev'
7
+
8
+ # A simple framework for writing line-oriented command interpreters, based
9
+ # heavily on Python's {cmd.py}[http://docs.python.org/lib/module-cmd.html].
10
+ #
11
+ # These are often useful for test harnesses, administrative tools, and
12
+ # prototypes that will later be wrapped in a more sophisticated interface.
13
+ #
14
+ # A Cmd instance or subclass instance is a line-oriented interpreter
15
+ # framework. There is no good reason to instantiate Cmd itself; rather,
16
+ # it's useful as a superclass of an interpreter class you define yourself
17
+ # in order to inherit Cmd's methods and encapsulate action methods.
18
+ class Cmd
19
+
20
+ module ClassMethods
21
+ @@docs = {}
22
+ @@shortcuts = {}
23
+ @@handlers = {}
24
+ @@prompt = '> '
25
+ @@shortcut_table = {}
26
+
27
+ # Set documentation for a command
28
+ #
29
+ # doc :help, 'Display this help.'
30
+ # def do_help
31
+ # # etc
32
+ # end
33
+ def doc(command, docstring = nil)
34
+ docstring = docstring ? docstring : yield
35
+ @@docs[command.to_s] = docstring
36
+ end
37
+
38
+ def docs
39
+ @@docs
40
+ end
41
+ module_function :docs
42
+
43
+ # Set what to do in the event that the given exception is raised.
44
+ #
45
+ # handle StackOverflowError, :handle_stack_overflow
46
+ #
47
+ def handle(exception, handler)
48
+ @@handlers[exception.to_s] = handler
49
+ end
50
+ module_function :handle
51
+
52
+ # Sets what the prompt is. Accepts a String, a block or a Symbol.
53
+ #
54
+ # == Block
55
+ #
56
+ # prompt_with { Time.now }
57
+ #
58
+ # == Symbol
59
+ #
60
+ # prompt_with :set_prompt
61
+ #
62
+ # == String
63
+ #
64
+ # prompt_with "#{self.class.name}> "
65
+ #
66
+ def prompt_with(*p, &block)
67
+ @@prompt = block_given? ? block : p.first
68
+ end
69
+
70
+ # Returns the evaluation of expression passed to prompt_with. Result has
71
+ # +to_s+ called on it as Readline expects a String for its prompt.
72
+ # XXX This could probably be more robust
73
+ def prompt
74
+ case @@prompt
75
+ when Symbol
76
+ self.send @@prompt
77
+ when Proc
78
+ @@prompt.call
79
+ else
80
+ @@prompt
81
+ end.to_s
82
+ end
83
+ module_function :prompt
84
+
85
+ # Create a command short cut
86
+ #
87
+ # shortcut '?', 'help'
88
+ # def do_help
89
+ # # etc
90
+ # end
91
+ def shortcut(short, command)
92
+ (@@shortcuts[command.to_s] ||= []).push short
93
+ @@shortcut_table[short] = command.to_s
94
+ end
95
+
96
+ def shortcut_table
97
+ @@shortcut_table
98
+ end
99
+ module_function :shortcut_table
100
+
101
+ def shortcuts
102
+ @@shortcuts
103
+ end
104
+ module_function :shortcuts
105
+
106
+ def custom_exception_handlers
107
+ @@handlers
108
+ end
109
+ module_function :custom_exception_handlers
110
+
111
+ # Defines a method which returns all defined methods which start with the
112
+ # passed in prefix followed by an underscore. Used to define methods to
113
+ # collect things such as all defined 'complete' and 'do' methods.
114
+ def define_collect_method(prefix)
115
+ method = 'collect_' + prefix
116
+ unless self.respond_to?(method)
117
+ define_method(method) do
118
+ self.methods.grep(/^#{prefix}_/).map {|meth| meth[prefix.size + 1..-1]}
119
+ end
120
+ end
121
+ end
122
+ end
123
+ extend ClassMethods
124
+ include ClassMethods
125
+
126
+ @hide_undocumented_commands = nil
127
+ class << self
128
+ # Flag that sets whether undocumented commands are listed in the help
129
+ attr_accessor :hide_undocumented_commands
130
+
131
+ def run(intro = nil)
132
+ new.cmdloop(intro)
133
+ end
134
+ end
135
+
136
+ # STDIN stream used
137
+ attr_writer :stdin
138
+
139
+ # STDOUT stream used
140
+ attr_writer :stdout
141
+
142
+ # The current command
143
+ attr_writer :current_command
144
+
145
+ prompt_with :default_prompt
146
+
147
+ def initialize
148
+ @stdin, @stdout = STDIN, STDOUT
149
+ @stop = false
150
+ setup
151
+ end
152
+
153
+ # Starts up the command loop
154
+ def cmdloop(intro = nil)
155
+ preloop
156
+ write intro if intro
157
+ begin
158
+ set_completion_proc(:complete)
159
+ begin
160
+ execute_command
161
+ # Catch ^C
162
+ rescue Interrupt
163
+ user_interrupt
164
+ # I don't know why ZeroDivisionError isn't caught below...
165
+ rescue ZeroDivisionError
166
+ handle_all_remaining_exceptions(ZeroDivisionError)
167
+ rescue => exception
168
+ handle_all_remaining_exceptions(exception)
169
+ end
170
+ end until @stop
171
+ postloop
172
+ end
173
+ alias :run :cmdloop
174
+
175
+ shortcut '?', 'help'
176
+ doc :help, 'This help message.'
177
+ def do_help(command = nil)
178
+ if command
179
+ command = translate_shortcut(command)
180
+ docs.include?(command) ? print_help(command) : no_help(command)
181
+ else
182
+ documented_commands.each {|cmd| print_help cmd}
183
+ print_undocumented_commands if undocumented_commands?
184
+ end
185
+ end
186
+
187
+ # Called when the +command+ has no associated documentation, this could
188
+ # potentially mean that the command is non existant
189
+ def no_help(command)
190
+ write "No help for command '#{command}'"
191
+ end
192
+
193
+ doc :exit, 'Terminate the program.'
194
+ def do_exit; stoploop end
195
+
196
+ # Turns off readline even if it is supported
197
+ def turn_off_readline
198
+ @readline_supported = false
199
+ self
200
+ end
201
+
202
+ protected
203
+
204
+ def execute_command
205
+ unless ARGV.empty?
206
+ stoploop
207
+ execute_line(ARGV * ' ')
208
+ else
209
+ execute_line(display_prompt(prompt, true))
210
+ end
211
+ end
212
+
213
+ def handle_all_remaining_exceptions(exception)
214
+ if exception_is_handled?(exception)
215
+ run_custom_exception_handling(exception)
216
+ else
217
+ handle_exception(exception)
218
+ end
219
+ end
220
+
221
+ def execute_line(command)
222
+ postcmd(run_command(precmd(command)))
223
+ end
224
+
225
+ def stoploop
226
+ @stop = true
227
+ end
228
+
229
+ # Indicates whether readline support is enabled
230
+ def readline_supported?
231
+ @readline_supported = READLINE_SUPPORTED if @readline_supported.nil?
232
+ @readline_supported
233
+ end
234
+
235
+ # Determines if the given exception has a custome handler.
236
+ def exception_is_handled?(exception)
237
+ custom_exception_handler(exception)
238
+ end
239
+
240
+ # Runs the customized exception handler for the given exception.
241
+ def run_custom_exception_handling(exception)
242
+ case handler = custom_exception_handler(exception)
243
+ when String
244
+ write handler
245
+ when Symbol
246
+ # grych@tg.pl FIX: added exception as an argument
247
+ self.send(handler, exception)
248
+ end
249
+ end
250
+
251
+ # Returns the customized handler for the exception
252
+ def custom_exception_handler(exception)
253
+ # grych@tg.pl FIX, was: exception.to_s which return exception message in ruby >=(?) 1.9.3
254
+ custom_exception_handlers[exception.class.to_s]
255
+ end
256
+
257
+
258
+ # Called at object creation. This can be treated like 'initialize' for sub
259
+ # classes.
260
+ def setup
261
+ end
262
+
263
+ # Exceptions in the cmdloop are caught and passed to +handle_exception+.
264
+ # Custom exception classes must inherit from StandardError to be
265
+ # passed to +handle_exception+.
266
+ def handle_exception(exception)
267
+ raise exception
268
+ end
269
+
270
+ # Displays the prompt.
271
+ def display_prompt(prompt, with_history = true)
272
+ line = if readline_supported?
273
+ Readline::readline(prompt, with_history)
274
+ else
275
+ print prompt
276
+ @stdin.gets
277
+ end
278
+ line.respond_to?(:strip) ? line.strip : line
279
+ end
280
+
281
+ # The current command.
282
+ def current_command
283
+ translate_shortcut @current_command
284
+ end
285
+
286
+ # Called when the user hits ctrl-C or ctrl-D. Terminates execution by default.
287
+ def user_interrupt
288
+ write 'Terminating' # XXX get rid of this
289
+ stoploop
290
+ end
291
+
292
+ # XXX Not implementd yet. Called when a do_ method that takes arguments doesn't get any
293
+ def arguments_missing
294
+ write 'Invalid arguments'
295
+ do_help(current_command) if docs.include?(current_command)
296
+ end
297
+
298
+ # A bit of a hack I'm afraid. Since subclasses will be potentially
299
+ # overriding user_interrupt we want to ensure that it returns true so that
300
+ # it can be called with 'and return'
301
+ def interrupt
302
+ user_interrupt or true
303
+ end
304
+
305
+ # Displays the help for the passed in command.
306
+ def print_help(cmd)
307
+ offset = docs.keys.longest_string_length
308
+ write "#{cmd.ljust(offset)} -- #{docs[cmd]}" +
309
+ (has_shortcuts?(cmd) ? " #{display_shortcuts(cmd)}" : '')
310
+ end
311
+
312
+ def display_shortcuts(cmd)
313
+ "(aliases: #{shortcuts[cmd].join(', ')})"
314
+ end
315
+
316
+ # The method name that corresponds to the passed in command.
317
+ def command(cmd)
318
+ "do_#{cmd}".intern
319
+ end
320
+
321
+ # The method name that corresponds to the complete command for the pass in
322
+ # command.
323
+ def complete_method(cmd)
324
+ "complete_#{cmd}".intern
325
+ end
326
+
327
+ # Call back executed at the start of the cmdloop.
328
+ def preloop
329
+ end
330
+
331
+ # Call back executed at the end of the cmdloop.
332
+ def postloop
333
+ end
334
+
335
+ # Receives line submitted at prompt and passes it along to the command
336
+ # being called.
337
+ def precmd(line)
338
+ line
339
+ end
340
+
341
+ # Receives the returned value of the called command.
342
+ def postcmd(line)
343
+ line
344
+ end
345
+
346
+ # Called when an empty line is entered in response to the prompt.
347
+ def empty_line
348
+ end
349
+
350
+ define_collect_method('do')
351
+ define_collect_method('complete')
352
+
353
+ # The default completor. Looks up all do_* methods.
354
+ def complete(command)
355
+ commands = completion_grep(command_list, command)
356
+ if commands.size == 1
357
+ cmd = commands.first
358
+ set_completion_proc(complete_method(cmd)) if collect_complete.include?(cmd)
359
+ end
360
+ commands
361
+ end
362
+
363
+ # Lists of commands (i.e. do_* methods minus the 'do_' part).
364
+ def command_list
365
+ collect_do - subcommand_list
366
+ end
367
+
368
+ # Definitive list of shortcuts and abbreviations of a command.
369
+ def command_lookup_table
370
+ return @command_lookup_table if @command_lookup_table
371
+ @command_lookup_table = command_abbreviations.merge(shortcut_table)
372
+ end
373
+
374
+ # Returns lookup table of unambiguous identifiers for commands.
375
+ def command_abbreviations
376
+ return @command_abbreviations if @command_abbreviations
377
+ @command_abbreviations = Abbrev::abbrev(command_list)
378
+ end
379
+
380
+ # List of all subcommands.
381
+ def subcommand_list
382
+ with_underscore, without_underscore = collect_do.partition {|command| command.include?('_')}
383
+ with_underscore.find_all {|do_method| without_underscore.include?(do_method[/^[^_]+/])}
384
+ end
385
+
386
+ # Lists all subcommands of a given command.
387
+ def subcommands(command)
388
+ completion_grep(subcommand_list, translate_shortcut(command).to_s + '_')
389
+ end
390
+
391
+ # Indicates whether a given command has any subcommands.
392
+ def has_subcommands?(command)
393
+ !subcommands(command).empty?
394
+ end
395
+
396
+ # List of commands which are documented.
397
+ def documented_commands
398
+ docs.keys.sort
399
+ end
400
+
401
+ # Indicates whether undocummented commands will be listed by the help
402
+ # command (they are listed by default).
403
+ def undocumented_commands_hidden?
404
+ self.class.hide_undocumented_commands
405
+ end
406
+
407
+ def print_undocumented_commands
408
+ return if undocumented_commands_hidden?
409
+ # TODO perhaps do some fancy stuff so that if the number of undocumented
410
+ # commands is greater than 80 cols or some such passed in number it
411
+ # presents them in a columnar fashion much the way readline does by default
412
+ write ' '
413
+ write 'Undocumented commands'
414
+ write '====================='
415
+ write undocumented_commands.join(' ' * 4)
416
+ end
417
+
418
+ # Returns list of undocumented commands.
419
+ def undocumented_commands
420
+ command_list - documented_commands
421
+ end
422
+
423
+ # Indicates if any commands are undocumeted.
424
+ def undocumented_commands?
425
+ !undocumented_commands.empty?
426
+ end
427
+
428
+ # Completor for the help command.
429
+ def complete_help(command)
430
+ completion_grep(documented_commands, command)
431
+ end
432
+
433
+ def completion_grep(collection, pattern)
434
+ collection.grep(/^#{Regexp.escape(pattern)}/)
435
+ end
436
+
437
+ # Writes out a message with newline.
438
+ def write(*strings)
439
+ # We want newlines at the end of every line, so don't join with "\n"
440
+ strings.each do |string|
441
+ @stdout.write string
442
+ @stdout.write "\n"
443
+ end
444
+ end
445
+ alias :puts :write
446
+
447
+ # Writes out a message without newlines appended.
448
+ def print(*strings)
449
+ strings.each {|string| @stdout.write string}
450
+ end
451
+
452
+ # shortcut '!', 'shell'
453
+ # doc :shell, 'Executes a shell.'
454
+ # # Executes a shell, perhaps should only be defined by subclasses.
455
+ # def do_shell(line)
456
+ # shell = ENV['SHELL']
457
+ # line ? write(%x(#{line}).strip) : system(shell)
458
+ # end
459
+
460
+ # Takes care of collecting the current command and its arguments if any and
461
+ # dispatching the appropriate command.
462
+ def run_command(line)
463
+ cmd, args = parse_line(line)
464
+ sanitize_readline_history(line) if line
465
+ unless cmd then empty_line; return end
466
+
467
+ cmd = translate_shortcut(cmd)
468
+ self.current_command = cmd
469
+ set_completion_proc(complete_method(cmd)) if collect_complete.include?(complete_method(cmd))
470
+ cmd_method = command(cmd)
471
+ if self.respond_to?(cmd_method)
472
+ # Perhaps just catch exceptions here (related to arity) and call a
473
+ # method that reports a generic error like 'invalid arguments'
474
+ self.method(cmd_method).arity.zero? ? self.send(cmd_method) : self.send(cmd_method, tokenize_args(args))
475
+ else
476
+ command_missing(current_command, tokenize_args(args))
477
+ end
478
+ end
479
+
480
+ # Receives the line as it was passed from the prompt (barring modification
481
+ # in precmd) and splits it into a command section and an args section. The
482
+ # args are by default set to nil if they are boolean false or empty then
483
+ # joined with spaces. The tokenize method can be used to further alter the
484
+ # args.
485
+ def parse_line(line)
486
+ # line will be nil if ctr-D was pressed
487
+ user_interrupt and return if line.nil?
488
+
489
+ cmd, *args = line.split
490
+ args = args.empty? ? nil : args * ' '
491
+ if args and has_subcommands?(cmd)
492
+ if cmd = find_subcommand_in_args(subcommands(cmd), line.split)
493
+ # XXX Completion proc should be passed array of subcommands somewhere
494
+ args = line.split.join('_').match(/^#{cmd}/).post_match.gsub('_', ' ').strip
495
+ args = nil if args.empty?
496
+ end
497
+ end
498
+ [cmd, args]
499
+ end
500
+
501
+ # Extracts a subcommand if there is one from the command line submitted. I guess this is a hack.
502
+ def find_subcommand_in_args(subcommands, args)
503
+ (subcommands & (1..args.size).to_a.map {|num_elems| args.first(num_elems).join('_')}).max
504
+ end
505
+
506
+ # Looks up command shortcuts (e.g. '?' is a shortcut for 'help'). Short
507
+ # cuts can be added by using the shortcut class method.
508
+ def translate_shortcut(cmd)
509
+ command_lookup_table[cmd] || cmd
510
+ end
511
+
512
+ # Indicates if the passed in command has any registerd shortcuts.
513
+ def has_shortcuts?(cmd)
514
+ command_shortcuts(cmd)
515
+ end
516
+
517
+ # Returns the set of registered shortcuts for a command, or nil if none.
518
+ def command_shortcuts(cmd)
519
+ shortcuts[cmd]
520
+ end
521
+
522
+ # Called on command arguments as they are passed into the command.
523
+ def tokenize_args(args)
524
+ args
525
+ end
526
+
527
+ # Cleans up the readline history buffer by performing tasks such as
528
+ # removing empty lines and piggy-backed duplicates. Only executed if
529
+ # running with readline support.
530
+ def sanitize_readline_history(line)
531
+ return unless readline_supported?
532
+ # Strip out empty lines
533
+ Readline::HISTORY.pop if line.match(/^\s*$/)
534
+ # Remove duplicates
535
+ Readline::HISTORY.pop if Readline::HISTORY[-2] == line rescue IndexError
536
+ end
537
+
538
+ # Readline completion uses a procedure that takes the current readline
539
+ # buffer and returns an array of possible matches against the current
540
+ # buffer. This method sets the current procedure to use. Commands can
541
+ # specify customized completion procs by defining a method following the
542
+ # naming convetion complet_{command_name}.
543
+ def set_completion_proc(cmd)
544
+ return unless readline_supported?
545
+ Readline.completion_proc = self.method(cmd)
546
+ end
547
+
548
+ # Called when the line entered at the prompt does not map to any of the
549
+ # defined commands. By default it reports that there is no such command.
550
+ def command_missing(command, args)
551
+ write "No such command '#{command}'"
552
+ end
553
+
554
+ def default_prompt
555
+ "#{self.class.name}> "
556
+ end
557
+
558
+ end
559
+
560
+ module Enumerable #:nodoc:
561
+ def longest_string_length
562
+ inject(0) {|longest, item| longest >= item.size ? longest : item.size}
563
+ end
564
+ end
565
+
566
+ if __FILE__ == $0
567
+ Cmd.run
568
+ end
@@ -0,0 +1,201 @@
1
+ require 'highline/import'
2
+ require 'yaml'
3
+
4
+ require 'arest'
5
+
6
+ class TarkinClientException < StandardError; end
7
+
8
+ class TarkinClient
9
+ SETTINGS_FILES = ["#{Dir.home}/.tarkin", ".tarkin", "/etc/tarkin"]
10
+ API = "_api/v1"
11
+
12
+ attr_accessor :settings
13
+ attr_reader :api_client
14
+
15
+ # Constructor. Needs to know the Tarkin Server parameters.
16
+ # They can be passed with three differen ways::
17
+ #
18
+ # * the options hash. Notice that in this case the token will not be stored in the ~/.tarkin file
19
+ # >> tc = TarkinClient.new email: 'user@example.com', password: 'password0', tarkin_url: 'http://tarkin.tg.pl'
20
+ # # TarkinClient <server: http://tarkin.tg.pl, authorized: true>
21
+ # >> tc = TarkinClient.new tarkin_url: 'http://tarkin.tg.pl', token: 'Ahoishwqbn32ldhw......'
22
+ # # TarkinClient <server: http://tarkin.tg.pl, authorized: true>
23
+ #
24
+ # * by reading the stored parameters in settings file (like ~/.tarkin)
25
+ # >> tc = TarkinClient.new
26
+ # # TarkinClient <server: http://localhost:3000, authorized: true>
27
+ #
28
+ # * by asking user via command line (when file not found)
29
+ # >> tc = TarkinClient.new
30
+ # # Your Tarkin server URL: |http://tarkin.tg.pl| http://localhost:3000
31
+ # # Your Tarkin account email: |user@example.com| grychu@gmail.com
32
+ # # Password for grychu@gmail.com: ********
33
+ # # TarkinClient <server: http://localhost:3000, authorized: true>
34
+ def initialize(**options)
35
+ @authorized = false
36
+ @settings = {}
37
+ if options[:email] && options[:password] && options[:tarkin_url]
38
+ @settings = options.select { |k,v| [:email, :tarkin_url].include? k }
39
+ @settings[:token] = get_token(@settings[:email], options[:password])
40
+ elsif options[:token] && options[:tarkin_url]
41
+ @settings[:tarkin_url] = options[:tarkin_url]
42
+ @settings[:token] = options[:token]
43
+ else
44
+ get_settings
45
+ save_settings
46
+ end
47
+ @api_client = ARest.new(api_url, token: @settings[:token])
48
+ end
49
+
50
+ # Returns Hash containing :id, :username and :password
51
+ # Gets Item.id or full path to the Item as a parameter
52
+ #
53
+ # >> tc.password(107)
54
+ # # {
55
+ # # "id" => 110,
56
+ # # "username" => "sysdba",
57
+ # # "password" => "secret_top"
58
+ # # }
59
+ #
60
+ # >> tc.password('/db/oracle/C84PCPY/sysdba')
61
+ # # {
62
+ # # "id" => 110,
63
+ # # "username" => "sysdba",
64
+ # # "password" => "secret_top"
65
+ # # }
66
+ def password(path_or_id)
67
+ case path_or_id
68
+ when String
69
+ # full path given
70
+ u = "#{path_or_id}.json"
71
+ when Fixnum
72
+ # Item ID given
73
+ u = "_password/#{path_or_id}.json"
74
+ end
75
+ response = @api_client.get(u)
76
+ if response.ok?
77
+ response.deserialize
78
+ else
79
+ raise TarkinClientException, "Can't get password, server returns #{response.code}: #{response.message}"
80
+ end
81
+ end
82
+
83
+ # Returns the content of given directory
84
+ # Output is a hash containing:
85
+ # * directories => [ {:name, :id, :created_at, :updated_at, :description} ]
86
+ # * items => [ {:username, :id, :created_at, :updated_at, :description} ]
87
+ #
88
+ # > tc.ls '/db/prod/oracle'
89
+ # #> {:directories=>
90
+ # [{:name=>"C84PROD", :id=>14, :created_at=>"2015-06-07T10:36:56.463Z", :updated_at=>"2015-06-07T10:36:56.463Z", :description=>"Production"}],
91
+ # :items=>
92
+ # [{:id=>6,
93
+ # :username=>"scott",
94
+ # :created_at=>"2015-06-07T10:38:27.981Z",
95
+ # :updated_at=>"2015-06-07T10:38:27.981Z",
96
+ # :description=>"The same user in all production databases"}]}
97
+ def ls(path = '/')
98
+ u = path.strip.chomp.sub(/(\/)+$/,'') # remove trailing slashes
99
+ u = if u == '' then '_dir.json' else "_dir/#{u}.json" end
100
+ response = @api_client.get(u)
101
+ if response.ok?
102
+ response.deserialize
103
+ else
104
+ if response.code == "404"
105
+ raise TarkinClientException, "No such file or directory"
106
+ else
107
+ raise TarkinClientException, "Can't list directory, server returns #{response.code}: #{response.message}"
108
+ end
109
+ end
110
+ end
111
+
112
+ # Search for a username or directory
113
+ # Input: a String to search, may contain wildcard (*)
114
+ # Output: array of hashes: [ { :label, :redirect_to }]
115
+ #
116
+ # > tc.find 'sys'
117
+ # #> [{:label=>"/db/prod/oracle/C84PROD/sys", :redirect_to=>"/db/prod/oracle/C84PROD#4"},
118
+ # {:label=>"/db/prod/oracle/C84PROD/sysadm", :redirect_to=>"/db/prod/oracle/C84PROD#5"}]
119
+ def find(term)
120
+ response = @api_client.get("_find.json", form_data: {term: term})
121
+ if response.ok?
122
+ response.deserialize
123
+ else
124
+ raise TarkinClientException, "Can't search for '#{term}', server returns #{response.code}: #{response.message}"
125
+ end
126
+ end
127
+
128
+ # Returns string with valid token
129
+ # > tc.token
130
+ # #> "zvaY5...sds="
131
+ def token
132
+ @settings[:token]
133
+ end
134
+
135
+ # Returns true, if there is a connectivity to the server
136
+ # and you are authorized
137
+ def authorized?
138
+ @authorized || check_connectivity
139
+ end
140
+
141
+ def inspect
142
+ "TarkinClient <server: #{@settings[:tarkin_url]}, authorized: #{authorized?}>"
143
+ end
144
+
145
+ private
146
+ def api_url
147
+ "#{@settings[:tarkin_url]}/#{API}"
148
+ end
149
+
150
+ def get_settings
151
+ settings_file = SETTINGS_FILES.find {|file| File.exists? file}
152
+ if settings_file
153
+ @settings = YAML::load_file settings_file
154
+ else
155
+ new_settings
156
+ end
157
+ end
158
+
159
+ def new_settings
160
+ @settings = Hash.new
161
+ until @settings[:token]
162
+ @settings[:tarkin_url] = ask("Your <%= color('Tarkin', BOLD) %> server URL: ") do |q|
163
+ q.default = @settings[:tarkin_url] || "http://tarkin.tg.pl"
164
+ q.validate = /^(http|https):\/\/.*/ix
165
+ end
166
+ @settings[:email] = ask("Your <%= color('Tarkin', BOLD) %> account email: ") do |q|
167
+ q.default = @settings[:email] || "user@example.com"
168
+ q.validate = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
169
+ end
170
+ password = ask("Password for #{@settings[:email]}: ") { |q| q.echo = "*" }
171
+ @settings[:token] = get_token(@settings[:email], password)
172
+ end
173
+ end
174
+
175
+ def save_settings
176
+ File.open(SETTINGS_FILES.first, 'w') { |f| f.puts @settings.to_yaml }
177
+ end
178
+
179
+ def get_token(email, password)
180
+ begin
181
+ client = ARest.new(api_url, username: email, password: password)
182
+ response = client.get('_authorize.json')
183
+ rescue SocketError
184
+ say "<%= color('Cannot connect to server.', BOLD) %> Please retry."
185
+ return nil
186
+ end
187
+ unless response.ok?
188
+ say "<%= color('#{response.message}', BOLD) %>. Please retry."
189
+ nil
190
+ else
191
+ @authorized = true
192
+ response.deserialize[:token]
193
+ end
194
+ end
195
+
196
+ def check_connectivity
197
+ # @authorized = api_uri['_ping'].get.ok? unless @authorized
198
+ # @authorized = "#{api_url}/_ping".to_uri.get("", "Authorization" => "Token token=#{@settings[:token]}").ok? unless @authorized
199
+ @api_client.get("_ping").ok? unless @authorized
200
+ end
201
+ end
@@ -0,0 +1,105 @@
1
+ class String
2
+ # taken from rails
3
+ def truncate(truncate_at, options = {})
4
+ s = self.gsub(/\r\n/, ' ')
5
+ return s unless length > truncate_at
6
+
7
+ omission = options[:omission] || '...'
8
+ length_with_room_for_omission = truncate_at - omission.length
9
+ stop = if options[:separator]
10
+ rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
11
+ else
12
+ length_with_room_for_omission
13
+ end
14
+
15
+ "#{s[0, stop]}#{omission}"
16
+ end
17
+ end
18
+
19
+ class TarkinCommands
20
+ def initialize(client)
21
+ @client = client
22
+ end
23
+
24
+ def cat(pwd)
25
+ puts @client.password(pwd)[:password]
26
+ end
27
+
28
+ def ls(path, long)
29
+ list = @client.ls(URI::encode(path))
30
+ if long
31
+ all = (list[:directories].collect{|dir| ["#{dir[:name]}/", 'blue', dir[:created_at], dir[:updated_at], dir[:description]]} +
32
+ list[:items].collect{|item| [item[:username], 'white', item[:created_at], item[:updated_at], item[:description]]}).sort
33
+ unless all.empty?
34
+ cols = 3
35
+ len = max_len(all)
36
+ table border: false do
37
+ all.each do |thing|
38
+ row do
39
+ column thing[2].to_time.strftime('%Y-%m-%d %T'), width: 22
40
+ column thing[3].to_time.strftime('%Y-%m-%d %T'), width: 22
41
+ column thing[0], width: len+2, color: thing[1]
42
+ chars_to_end = HighLine::SystemExtensions.terminal_size.first - 22*2 - (len+6)
43
+ column thing[4].truncate(chars_to_end), width: chars_to_end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ else
49
+ # all contains directories and users, a list of list - item and display color: [[dir1, 'blue'], [user1, 'white']]
50
+ # reversed because we will be poping the table
51
+ all = (list[:directories].collect{|dir| ["#{dir[:name]}/", 'blue']} + list[:items].collect{|item| [item[:username], 'white']}).sort.reverse
52
+ unless all.empty?
53
+ cols = columns(all)
54
+ rows = all.count / cols + ( all.count % cols == 0 ? 0 : 1 )
55
+ len = max_len(all)
56
+
57
+ table border: false do
58
+ rows.times do
59
+ row do
60
+ cols.times do
61
+ item = all.pop
62
+ column(item && item.first , width: len, color: item && item.last)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def find(term)
72
+ list = @client.find(term).sort_by {|x| x[:label]}
73
+ list.each do |thing|
74
+ unless thing[:redirect_to].include?('#')
75
+ puts thing[:label].blue
76
+ else
77
+ puts thing[:label].white
78
+ end
79
+ end
80
+ end
81
+
82
+ # Returns items only in given directory
83
+ def items(dir)
84
+ @client.ls(URI::encode(dir))[:items]
85
+ end
86
+
87
+ # Returns only dirs
88
+ def dirs(dir)
89
+ @client.ls(URI::encode(dir))[:directories]
90
+ end
91
+
92
+ private
93
+ def max_len(array_or_arrays)
94
+ if array_or_arrays.empty?
95
+ nil
96
+ else
97
+ array_or_arrays.max_by{|x| x.first.length}.first.length
98
+ end
99
+ end
100
+
101
+ def columns(array_or_arrays)
102
+ screen_x = HighLine::SystemExtensions.terminal_size.first
103
+ (screen_x / (max_len(array_or_arrays) + 1.0)).floor
104
+ end
105
+ end
@@ -0,0 +1,145 @@
1
+ require 'cmd'
2
+
3
+ class TarkinSh < Cmd
4
+ SETTINGS_FILE = "#{Dir.home}/.tarkin_sh"
5
+ HIST_LINES = 100 # how many history lines to save
6
+
7
+ prompt_with :prompt_command
8
+
9
+ handle TarkinClientException, :handle_client_exception
10
+
11
+ doc :ls, 'List contants of directory
12
+ -l -- verbose (long) view'
13
+ def do_ls(args)
14
+ if args[:args].empty?
15
+ commands.ls @cd, args[:options].include?('l')
16
+ else
17
+ args[:args].each do |dir|
18
+ commands.ls full_dir(dir), args[:options].include?('l')
19
+ end
20
+ end
21
+ end
22
+
23
+ doc :pwd, 'Print working directory'
24
+ def do_pwd
25
+ write @cd
26
+ end
27
+
28
+ doc :cd, 'Change directory'
29
+ def do_cd(args)
30
+ if args[:args].empty?
31
+ @cd = '/'
32
+ else
33
+ # TODO: warn if change to non-existing directory
34
+ dir = args[:args].first
35
+ @cd = full_dir dir
36
+ end
37
+ end
38
+ def complete_cd(args)
39
+ commands.dirs(full_dir(@cd)).collect {|x| x[:name]}.select {|x| x.start_with? args}
40
+ end
41
+
42
+ shortcut 'less', :cat
43
+ shortcut 'more', :cat
44
+ shortcut 'get', :cat
45
+ doc :cat, 'Shows password'
46
+ def do_cat(args)
47
+ args[:args].each do |item|
48
+ commands.cat(full_dir(item))
49
+ end
50
+ end
51
+ def complete_cat(args)
52
+ commands.items(full_dir(@cd)).collect {|x| x[:username]}.select {|x| x.start_with? args}
53
+ end
54
+
55
+ shortcut 'search', :find
56
+ doc :find, 'Find for item or directory'
57
+ def do_find(args)
58
+ if args[:args].empty?
59
+ commands.ls @cd, false
60
+ else
61
+ commands.find(args[:args])
62
+ end
63
+ end
64
+
65
+ def do_help(command = nil)
66
+ command = command[:args].first
67
+ if command
68
+ command = translate_shortcut(command)
69
+ docs.include?(command) ? print_help(command) : no_help(command)
70
+ else
71
+ documented_commands.each {|cmd| print_help cmd}
72
+ print_undocumented_commands if undocumented_commands?
73
+ end
74
+ end
75
+
76
+ shortcut '!', 'shell'
77
+ doc :shell, 'Executes a shell.'
78
+ # Executes a shell, perhaps should only be defined by subclasses.
79
+ def do_shell(line)
80
+ line = line[:original]
81
+ shell = ENV['SHELL']
82
+ write shell
83
+ write "**#{line}**"
84
+ line.empty? ? system(shell) : write(%x(#{line}).strip)
85
+ end
86
+
87
+ protected
88
+ def self.start(client, commands)
89
+ @@client = client
90
+ @@commands = commands
91
+ run
92
+ end
93
+
94
+ def setup
95
+ if File.exists? SETTINGS_FILE
96
+ settings = YAML::load_file SETTINGS_FILE
97
+ @cd = settings[:cd]
98
+ settings[:history].each { |h| Readline::HISTORY.push h }
99
+ else
100
+ @cd = '/'
101
+ end
102
+ end
103
+
104
+ def prompt_command
105
+ "#{client.settings[:tarkin_url]}#{@cd}> "
106
+ end
107
+
108
+ def command_missing(command, args)
109
+ write "tarkin: command not found: #{command}"
110
+ end
111
+
112
+ def handle_client_exception(exception)
113
+ write "tarkin: #{exception.message}"
114
+ end
115
+
116
+ def postloop
117
+ settings = { cd: @cd, history: Readline::HISTORY.to_a.last(HIST_LINES) }
118
+ File.open(SETTINGS_FILE, 'w') { |f| f.puts settings.to_yaml }
119
+ end
120
+
121
+ def tokenize_args(args)
122
+ a = if args then args.split else [] end
123
+ { args: a.select {|x| !x.start_with? '-'},
124
+ options: a.select {|x| x.start_with? '-'}.map {|x| x.sub(/^-/, '')}.join.split('').uniq,
125
+ original: args}
126
+ end
127
+
128
+ private
129
+ def commands
130
+ @@commands
131
+ end
132
+
133
+ def client
134
+ @@client
135
+ end
136
+
137
+ def full_dir(dir)
138
+ if dir.start_with? '/'
139
+ File.absolute_path(dir)
140
+ else
141
+ File.absolute_path(dir, @cd)
142
+ end
143
+ end
144
+
145
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tarkin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Tomek Gryszkiewicz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: highline
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.7.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.7'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.7.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: arest
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.9'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.9.1.1
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.9'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.9.1.1
53
+ - !ruby/object:Gem::Dependency
54
+ name: command_line_reporter
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '3.3'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 3.3.5
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.3'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 3.3.5
73
+ - !ruby/object:Gem::Dependency
74
+ name: rspec
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '3.2'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.0
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 3.2.0
93
+ - !ruby/object:Gem::Dependency
94
+ name: bundler
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '1.8'
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '1.8'
107
+ description: Tarkin Team Password Manager client, command line and shell
108
+ email: grych@tg.pl
109
+ executables:
110
+ - tarkin
111
+ extensions: []
112
+ extra_rdoc_files: []
113
+ files:
114
+ - bin/tarkin
115
+ - lib/cmd.rb
116
+ - lib/tarkin.rb
117
+ - lib/tarkin_commands.rb
118
+ - lib/tarkin_sh.rb
119
+ homepage: https://github.com/grych/tarkin-client
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.4.5
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Tarkin client
143
+ test_files: []