diff --git a/Gemfile.lock b/Gemfile.lock index dd332e9..afc103d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ PATH specs: github-auth (2.0.0) httparty (~> 0.11.0) + thor (~> 0.18) GEM remote: https://rubygems.org/ @@ -11,6 +12,7 @@ GEM parallel cane-hashcheck (1.2.0) cane + coderay (1.0.9) colorize (0.5.8) coveralls (0.6.7) colorize @@ -24,10 +26,15 @@ GEM httparty (0.11.0) multi_json (~> 1.0) multi_xml (>= 0.5.2) + method_source (0.8.2) mime-types (1.24) multi_json (1.7.9) multi_xml (0.5.5) parallel (0.9.0) + pry (0.9.12.2) + coderay (~> 1.0.5) + method_source (~> 0.8) + slop (~> 3.4) rack (1.5.2) rack-protection (1.5.0) rack @@ -50,6 +57,7 @@ GEM rack (~> 1.4) rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) + slop (3.4.6) thin (1.5.1) daemons (>= 1.0.9) eventmachine (>= 0.12.6) @@ -66,6 +74,7 @@ DEPENDENCIES cane-hashcheck (~> 1.2.0) coveralls (~> 0.6.7) github-auth! + pry (~> 0.9.12) rake (~> 10.1.0) rspec (~> 2.14) sinatra (~> 1.4.3) diff --git a/README.md b/README.md index 96b28bf..c1a9e33 100644 --- a/README.md +++ b/README.md @@ -23,37 +23,45 @@ After you've [installed](#installation) `gh-auth`, you can give me ssh access with: ```bash -$ gh-auth --add chrishunt +$ gh-auth add --users=chrishunt +Adding 2 key(s) to '/Users/chris/.ssh/authorized_keys' +``` + +If you'd like me to automatically connect to your existing tmux session, you +can do that with a custom ssh command: + +```bash +$ gh-auth add --users=chrishunt --command="tmux attach" Adding 2 key(s) to '/Users/chris/.ssh/authorized_keys' ``` That was easy! When we're done working, you can revoke my access with: ```bash -$ gh-auth --remove chrishunt +$ gh-auth remove --users=chrishunt Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys' ``` You can add and remove any number of users at the same time. ```bash -$ gh-auth --add chrishunt,zachmargolis +$ gh-auth add --users=chrishunt zachmargolis Adding 4 key(s) to '/Users/chris/.ssh/authorized_keys' -$ gh-auth --list -Added users: chrishunt, zachmargolis +$ gh-auth list +chrishunt zachmargolis -$ gh-auth --remove chrishunt +$ gh-auth remove --users=chrishunt Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys' -$ gh-auth --list -Added users: zachmargolis +$ gh-auth list +zachmargolis -$ gh-auth --remove zachmargolis +$ gh-auth remove --users=zachmargolis Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys' -$ gh-auth --list -Added users: +$ gh-auth list + ``` ## Sections @@ -80,14 +88,42 @@ Added users: `gh-auth` can be used from the command line after the gem has been installed. -```bash -usage: gh-auth [--version] [--list] [--add|--remove] +``` +$ gh-auth +Commands: + gh-auth add --users=one two three # Add GitHub users to authorized keys + gh-auth help [COMMAND] # Describe available commands or one specific command + gh-auth list # List all GitHub users already added to authorized keys + gh-auth remove --users=one two three # Remove GitHub users from authorized keys + gh-auth version # Show gh-auth version + +Options: + [--host=HOST] + [--path=PATH] +``` -options: - --add doug,sally Add GitHub users - --remove doug,sally Remove GitHub users - --list List all GitHub users added - --version Show version +Use the `help` command for help on a specific command. + +``` +$ gh-auth help add +Usage: + gh-auth add --users=one two three + +Options: + --users=one two three + [--command=COMMAND] + [--host=HOST] + [--path=PATH] + +Description: + `gh-auth add` is used to add one or more GitHub user's public SSH keys to ~/.ssh/authorized_keys. All keys stored on github.com for that user will be added. + + > $ gh-auth add --users=chrishunt zachmargolis + > Adding 6 key(s) to '/Users/chris/.ssh/authorized_keys' + + By default, users will be granted normal shell access. If you'd like to specify an ssh command that should execute when the user connects, use the `--command` option. + + > $ gh-auth add --users=chrishunt --command="tmux attach" ``` ### In Your Project @@ -99,10 +135,10 @@ too. require 'github/auth' # Add keys for GitHub user 'chrishunt' -Github::Auth::CLI.new.execute %w(--add chrishunt) +Github::Auth::CLI.start %w(add --users=chrishunt) # Remove keys for GitHub user 'chrishunt' -Github::Auth::CLI.new.execute %w(--remove chrishunt) +Github::Auth::CLI.start %w(remove --users=chrishunt) ``` ## Installation @@ -111,15 +147,6 @@ Install the `github-auth` gem: ```bash $ gem install github-auth - -$ gh-auth -usage: gh-auth [--version] [--list] [--add|--remove] - -options: - --add doug,sally Add GitHub users - --remove doug,sally Remove GitHub users - --list List all GitHub users added - --version Show version ``` ### SSH Public Key Authentication (Mac OS X) @@ -147,7 +174,7 @@ First, authorize yourself for ssh. (Make sure to replace 'chrishunt' with *your* GitHub username) ```bash -$ gh-auth --add chrishunt +$ gh-auth add --users=chrishunt Adding 2 key(s) to '/Users/chris/.ssh/authorized_keys' ``` @@ -163,7 +190,7 @@ $ ssh -o PreferredAuthentications=publickey localhost Next, remove your public keys from the keys file: ```bash -$ gh-auth --remove chrishunt +$ gh-auth remove --users=chrishunt Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys' ``` diff --git a/bin/gh-auth b/bin/gh-auth index ecf0893..0fef4d7 100755 --- a/bin/gh-auth +++ b/bin/gh-auth @@ -2,4 +2,4 @@ require 'github/auth' -Github::Auth::CLI.new.execute(ARGV) +Github::Auth::CLI.start ARGV diff --git a/github-auth.gemspec b/github-auth.gemspec index 7045c44..cf78ccb 100644 --- a/github-auth.gemspec +++ b/github-auth.gemspec @@ -24,8 +24,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'coveralls', '~> 0.6.7' spec.add_development_dependency 'rake', '~> 10.1.0' spec.add_development_dependency 'rspec', '~> 2.14' + spec.add_development_dependency 'pry', '~> 0.9.12' spec.add_development_dependency 'sinatra', '~> 1.4.3' spec.add_development_dependency 'thin', '~> 1.5.1' spec.add_runtime_dependency 'httparty', '~> 0.11.0' + spec.add_runtime_dependency 'thor', '~> 0.18' end diff --git a/lib/github/auth.rb b/lib/github/auth.rb index cd4b159..37946e2 100644 --- a/lib/github/auth.rb +++ b/lib/github/auth.rb @@ -2,5 +2,4 @@ require 'github/auth/key' require 'github/auth/keys_client' require 'github/auth/keys_file' -require 'github/auth/options' require 'github/auth/cli' diff --git a/lib/github/auth/cli.rb b/lib/github/auth/cli.rb index f5ecb69..1f17882 100644 --- a/lib/github/auth/cli.rb +++ b/lib/github/auth/cli.rb @@ -1,40 +1,77 @@ +require 'thor' + module Github::Auth # Command Line Interface for parsing and executing commands - class CLI - attr_reader :options + class CLI < Thor + class_option :host, type: :string + class_option :path, type: :string - def execute(args) - @options = Options.new.parse(args) - send options.command - end + option :users, type: :array, required: true + option :command, type: :string + desc 'add', 'Add GitHub users to authorized keys' + long_desc <<-LONGDESC + `gh-auth add` is used to add one or more GitHub user's public SSH keys + to ~/.ssh/authorized_keys. All keys stored on github.com for that + user will be added. - private + > $ gh-auth add --users=chrishunt zachmargolis + \x5> Adding 6 key(s) to '/Users/chris/.ssh/authorized_keys' + By default, users will be granted normal shell access. If you'd like to + specify an ssh command that should execute when the user connects, use + the `--command` option. + + > $ gh-auth add --users=chrishunt --command="tmux attach" + LONGDESC def add on_keys_file :write!, - "Adding #{keys.count} key(s) to '#{keys_file.path}'" + "Adding #{keys.count} key(s) to '#{keys_file.path}'", + { command: options[:command] } end + option :users, type: :array, required: true + desc 'remove', 'Remove GitHub users from authorized keys' + long_desc <<-LONGDESC + `gh-auth remove` is used to remove one or more GitHub user's public SSH + keys from ~/.ssh/authorized_keys. All keys stored on github.com for + that user will be removed. + + > $ gh-auth remove --users=chrishunt zachmargolis + \x5> Removing 6 key(s) to '/Users/chris/.ssh/authorized_keys' + LONGDESC def remove on_keys_file :delete!, "Removing #{keys.count} key(s) from '#{keys_file.path}'" end + desc 'list', 'List all GitHub users already added to authorized keys' + long_desc <<-LONGDESC + `gh-auth list` will list all GitHub users that have been added to + ~/.ssh/authorized_keys by `gh-auth`. + + > $ gh-auth list + \x5> chrishunt, zachmargolis + LONGDESC def list - puts "Added users: #{keys_file.github_users.join(', ')}" + puts keys_file.github_users.join(' ') end + desc 'version', 'Show gh-auth version' def version puts Github::Auth::VERSION end - def usage - puts options.usage + private + + def keys + @keys ||= begin + Array(options[:users]).map { |user| keys_for user }.flatten.compact + end end - def on_keys_file(action, message) + def on_keys_file(action, message, options = {}) puts message - rescue_keys_file_errors { keys_file.send action, keys } + rescue_keys_file_errors { keys_file(options).send action, keys } end def rescue_keys_file_errors @@ -51,10 +88,6 @@ def rescue_keys_file_errors puts " $ touch #{keys_file.path}" end - def keys - @keys ||= options.usernames.map { |user| keys_for user }.flatten.compact - end - def keys_for(username) Github::Auth::KeysClient.new( hostname: github_hostname, @@ -68,16 +101,17 @@ def keys_for(username) puts "https://status.github.com" end - def keys_file - Github::Auth::KeysFile.new path: keys_file_path + def keys_file(options = {}) + Github::Auth::KeysFile.new \ + options.merge path: keys_file_path end def keys_file_path - Github::Auth::KeysFile::DEFAULT_PATH + options[:path] || Github::Auth::KeysFile::DEFAULT_PATH end def github_hostname - Github::Auth::KeysClient::DEFAULT_HOSTNAME + options[:host] || Github::Auth::KeysClient::DEFAULT_HOSTNAME end end end diff --git a/lib/github/auth/keys_file.rb b/lib/github/auth/keys_file.rb index 25db857..a450468 100644 --- a/lib/github/auth/keys_file.rb +++ b/lib/github/auth/keys_file.rb @@ -1,7 +1,7 @@ module Github::Auth # Write and delete keys from the authorized_keys file class KeysFile - attr_reader :path + attr_reader :path, :command PermissionDeniedError = Class.new StandardError FileDoesNotExistError = Class.new StandardError @@ -10,6 +10,7 @@ class KeysFile def initialize(options = {}) @path = File.expand_path(options[:path] || DEFAULT_PATH) + @command = options[:command] end def write!(keys) @@ -19,7 +20,7 @@ def write!(keys) unless keys_file_content.empty? || keys_file_content.end_with?("\n") keys_file.write "\n" end - keys_file.write "#{key}\n" + keys_file.write "#{"command=\"#{command}\" " if command}#{key}\n" end end end @@ -62,7 +63,7 @@ def with_keys_file(mode, block) def keys_file_content_without(keys) keys_file_content.tap do |content| Array(keys).each do |key| - content.gsub! /#{Regexp.escape key.key}( .*)?$\n?/, '' + content.gsub! /(.*)?#{Regexp.escape key.key}(.*)?$\n?/, '' end content << "\n" unless content.empty? || content.end_with?("\n") diff --git a/lib/github/auth/options.rb b/lib/github/auth/options.rb deleted file mode 100644 index e90332e..0000000 --- a/lib/github/auth/options.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'optparse' - -module Github::Auth - # Parses command line options - class Options - attr_reader :parser - - def initialize - @parser = OptionParser.new do |opts| - opts.banner = [ - 'usage: gh-auth', - '[--version]', - '[--list]', - '[--add|--remove]', - '' - ].join(' ') - - opts.separator "\noptions:" - - opts.on( - '--add doug,sally', Array, 'Add GitHub users' - ) do |usernames| - raise OptionParser::MissingArgument if usernames.empty? - @command = 'add' - @usernames = usernames - end - - opts.on( - '--remove doug,sally', Array, 'Remove GitHub users' - ) do |usernames| - raise OptionParser::MissingArgument if usernames.empty? - @command = 'remove' - @usernames = usernames - end - - opts.on('--list', 'List all GitHub users added') do - @command = 'list' - end - - opts.on('--version', 'Show version') do - @command = 'version' - end - end - end - - def command - @command ||= 'usage' - end - - def usernames - @usernames ||= [] - end - - def usage - parser.help - end - - def parse(args) - parser.parse(args) - ensure - return self - end - end -end diff --git a/spec/acceptance/github/auth/cli_spec.rb b/spec/acceptance/github/auth/cli_spec.rb index 6a01b6d..889af5e 100644 --- a/spec/acceptance/github/auth/cli_spec.rb +++ b/spec/acceptance/github/auth/cli_spec.rb @@ -11,23 +11,22 @@ after { keys_file.unlink } - def cli - described_class.new.tap do |cli| - cli.stub( - github_hostname: hostname, - keys_file_path: keys_file.path - ) - end + def cli(args = []) + described_class.start \ + args + [ + "--path=#{keys_file.path}", + "--host=#{hostname}" + ] end it 'adds and removes keys from the keys file' do - cli.execute %w(--add chrishunt) + cli %w(add --users=chrishunt) keys_file.read.tap do |keys_file_content| keys.each { |key| expect(keys_file_content).to include key.to_s } end - cli.execute %w(--remove chrishunt) + cli %w(remove --users=chrishunt) expect(keys_file.read).to be_empty @@ -35,29 +34,32 @@ def cli end it 'lists users from the keys file' do - cli.execute %w(--add chrishunt) + cli %w(add --users=chrishunt) output = capture_stdout do - cli.execute %w(--list) + cli %w(list) end expect(output).to include('chrishunt') end + it 'supports ssh commands' do + cli %w(add --users=chrishunt) << '--command=tmux attach' + + expect(keys_file.read).to include 'command="tmux attach"' + + keys_file.rewind + cli %w(remove --users=chrishunt) + + expect(keys_file.read.strip).to be_empty + end + it 'prints version information' do output = capture_stdout do - cli.execute %w(--version) + cli %w(version) end expect(output).to include Github::Auth::VERSION end - - it 'prints usage for invalid arguments' do - [[], %w(invalid), %w(--add)].each do |invalid_arguments| - expect( - capture_stdout { cli.execute invalid_arguments } - ).to include 'usage: gh-auth' - end - end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 54a6989..0894e9b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,3 @@ +require 'pry' require 'coveralls' Coveralls.wear! diff --git a/spec/unit/github/auth/keys_file_spec.rb b/spec/unit/github/auth/keys_file_spec.rb index ce175da..c6d046c 100644 --- a/spec/unit/github/auth/keys_file_spec.rb +++ b/spec/unit/github/auth/keys_file_spec.rb @@ -4,9 +4,10 @@ require 'github/auth/keys_file' describe Github::Auth::KeysFile do - subject { described_class.new path: path } + subject { described_class.new(options) } let(:keys_file) { Tempfile.new 'authorized_keys' } + let(:options) {{ path: path }} let(:path) { keys_file.path } after { keys_file.unlink } # clean up, delete tempfile @@ -78,6 +79,19 @@ it_should_behave_like 'a successful key addition' end + context 'with an ssh command' do + let(:options) {{ path: path, command: 'tmux attach' }} + let(:keys) {[ Github::Auth::Key.new('chris', 'abc123') ]} + + it_should_behave_like 'a successful key addition' + + it 'prefixes the key with the ssh command' do + subject.write! keys + + expect(keys_file.read).to include 'command="tmux attach"' + end + end + context 'with existing keys in the keys file' do let(:existing_keys) { %w(abc123 def456 ghi789) } let(:keys) {[ Github::Auth::Key.new('chris', 'jkl012') ]}