tarkin 0.9.3.0

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