github-auth 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 103b97306c0adbb26e02a782f02c8244557fb665
4
+ data.tar.gz: b95cbdc7fb511a1346bbf7bb222034c8a1103f60
5
+ SHA512:
6
+ metadata.gz: 4ad87cd1b81d17f581e209cfc4f190a5135f7e8b141fd421968e51ff221d8bf25083701efde8cf45b1affc6e1343e13cefc23c2be4099fc62aed2125bdeb0227
7
+ data.tar.gz: 03ec6d0356ecb5ba1c12f56e7edbd5ab284bfa151aab0d1453d001bc9acac248bce461b70749f17072e18ea87a428c9be9a824d0cc2ecb39bda9c5eacc406ba3
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.swp
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in github-auth.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Chris Hunt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # gh-auth
2
+ [![Travis CI](https://travis-ci.org/chrishunt/github-auth.png)](https://travis-ci.org/chrishunt/github-auth)
3
+ [![Coverage Status](https://coveralls.io/repos/chrishunt/github-auth/badge.png?branch=master)](https://coveralls.io/r/chrishunt/github-auth)
4
+ [![Code Climate](https://codeclimate.com/github/chrishunt/github-auth.png)](https://codeclimate.com/github/chrishunt/github-auth)
5
+
6
+ ## Description
7
+
8
+ If you decide to [`#pairwithme`](https://twitter.com/search?q=%23pairwithme),
9
+ we'll probably be SSHing into my laptop, your laptop, or some laptop in the
10
+ sky. Since I'd rather not send you a password over email or Skype, we'll use
11
+ public key authentication.
12
+
13
+ `gh-auth` allows you to easily add and remove any Github user's public ssh keys
14
+ from your [`authorized_keys`](http://en.wikipedia.org/wiki/Ssh-agent) file.
15
+
16
+ Let's say you'd like to pair with me, just run:
17
+
18
+ ```bash
19
+ $ gh-auth add chrishunt
20
+ Adding 2 key(s) to '/Users/chris/.ssh/authorized_keys'
21
+ ```
22
+
23
+ Now I can ssh into your machine! That was easy. When we're done working, you
24
+ can revoke my access with:
25
+
26
+ ```bash
27
+ $ gh-auth remove chrishunt
28
+ Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys'
29
+ ```
30
+
31
+ You can add and remove any number of users at the same time.
32
+
33
+ ```bash
34
+ $ gh-auth add chrishunt zachmargolis
35
+ Adding 4 key(s) to '/Users/chris/.ssh/authorized_keys'
36
+
37
+ $ gh-auth remove chrishunt
38
+ Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys'
39
+
40
+ $ gh-auth remove zachmargolis
41
+ Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys'
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```bash
47
+ usage: gh-auth [add|remove] <username>
48
+ ```
49
+
50
+ ## Installation
51
+
52
+ Install the `github-auth` gem:
53
+
54
+ ```bash
55
+ $ gem install github-auth
56
+ ```
57
+
58
+ ### SSH Public Key Authentication (Mac OS X)
59
+
60
+ Public key authentication works with Mac OS by default, but you'll need to get
61
+ your ssh server running. This is done by ticking 'Remote Login' in the
62
+ 'Sharing' panel of System Preferences.
63
+
64
+ ![]('https://raw.github.com/chrishunt/github-auth/master/img/mac-os-ssh-sharing.png')
65
+
66
+ Now that SSH is running, make sure you have the correct permissions set for
67
+ your authorized keys.
68
+
69
+ ```bash
70
+ $ chmod 700 ~/.ssh
71
+ $ chmod 600 ~/.ssh/authorized_keys
72
+ ```
73
+
74
+ ### Verification
75
+
76
+ If you'd like to verify that everything is working as expected, you can test
77
+ right from your machine.
78
+
79
+ First, authorized yourself for ssh. (Make sure to replace 'chrishunt' with
80
+ *your* Github username)
81
+
82
+ ```bash
83
+ $ gh-auth add chrishunt
84
+ Adding 2 key(s) to '/Users/chris/.ssh/authorized_keys'
85
+ ```
86
+
87
+ Next, open an SSH session to your machine with public key authentication. It
88
+ should work just fine.
89
+
90
+ ```bash
91
+ $ ssh -o PreferredAuthentications=publickey localhost
92
+
93
+ (localhost)$
94
+ ```
95
+
96
+ Now remove your public keys from the keys file:
97
+
98
+ ```bash
99
+ $ gh-auth remove chrishunt
100
+ Removing 2 key(s) from '/Users/chris/.ssh/authorized_keys'
101
+ ```
102
+
103
+ You should no longer be able to login to the machine since the keys have been
104
+ removed.
105
+
106
+ ```bash
107
+ $ ssh -o PreferredAuthentications=publickey localhost
108
+
109
+ > Permission denied (publickey,keyboard-interactive)
110
+ ```
111
+
112
+ ## Contributing
113
+
114
+ 1. Fork it
115
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
116
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
117
+ 4. Run the tests (`bundle exec rake spec`)
118
+ 5. Push to the branch (`git push origin my-new-feature`)
119
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run all tests'
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = '--color --order random'
7
+ end
8
+
9
+ task default: :spec
data/bin/gh-auth ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'github/auth'
4
+
5
+ Github::Auth::CLI.new(ARGV).execute
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'github/auth/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'github-auth'
8
+ spec.version = Github::Auth::VERSION
9
+ spec.authors = ['Chris Hunt']
10
+ spec.email = ['c@chrishunt.co']
11
+ spec.description = %q{SSH key management for Github users}
12
+ spec.summary = %q{SSH key management for Github users}
13
+ spec.homepage = 'https://github.com/chrishunt/github-auth'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'coveralls'
25
+ spec.add_development_dependency 'sinatra'
26
+ spec.add_development_dependency 'thin'
27
+
28
+ spec.add_runtime_dependency 'httparty'
29
+ end
Binary file
@@ -0,0 +1,4 @@
1
+ require 'github/auth/version'
2
+ require 'github/auth/keys_client'
3
+ require 'github/auth/keys_file'
4
+ require 'github/auth/cli'
@@ -0,0 +1,100 @@
1
+ module Github::Auth
2
+ class CLI
3
+ attr_reader :command, :usernames
4
+
5
+ COMMANDS = %w(add remove)
6
+
7
+ def initialize(argv)
8
+ @command = argv.shift
9
+ @usernames = argv
10
+ end
11
+
12
+ def execute
13
+ if COMMANDS.include?(command) && !usernames.empty?
14
+ send command
15
+ else
16
+ print_usage
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def add
23
+ on_keys_file :write!,
24
+ "Adding #{keys.count} key(s) to '#{keys_file.path}'"
25
+ end
26
+
27
+ def remove
28
+ on_keys_file :delete!,
29
+ "Removing #{keys.count} key(s) from '#{keys_file.path}'"
30
+ end
31
+
32
+ def on_keys_file(action, message)
33
+ puts message
34
+ rescue_keys_file_errors { keys_file.send action, keys }
35
+ end
36
+
37
+ def rescue_keys_file_errors
38
+ yield
39
+ rescue KeysFile::PermissionDeniedError
40
+ print_permission_denied
41
+ rescue KeysFile::FileDoesNotExistError
42
+ print_file_does_not_exist
43
+ end
44
+
45
+ def print_usage
46
+ puts "usage: gh-auth [#{COMMANDS.join '|'}] <username>"
47
+ end
48
+
49
+ def print_permission_denied
50
+ puts 'Permission denied!'
51
+ puts
52
+ puts "Make sure you have write permissions for '#{keys_file.path}'"
53
+ end
54
+
55
+ def print_file_does_not_exist
56
+ puts "Keys file does not exist!"
57
+ puts
58
+ puts "Create one now and try again:"
59
+ puts
60
+ puts " $ touch #{keys_file.path}"
61
+ end
62
+
63
+ def print_github_user_does_not_exist(username)
64
+ puts "Github user '#{username}' does not exist"
65
+ end
66
+
67
+ def print_github_unavailable
68
+ puts "Github appears to be unavailable :("
69
+ puts
70
+ puts "https://status.github.com"
71
+ end
72
+
73
+ def keys
74
+ @keys ||= usernames.map { |username| keys_for username }.flatten.compact
75
+ end
76
+
77
+ def keys_for(username)
78
+ Github::Auth::KeysClient.new(
79
+ hostname: github_hostname,
80
+ username: username
81
+ ).keys
82
+ rescue Github::Auth::KeysClient::GithubUserDoesNotExistError
83
+ print_github_user_does_not_exist username
84
+ rescue Github::Auth::KeysClient::GithubUnavailableError
85
+ print_github_unavailable
86
+ end
87
+
88
+ def keys_file
89
+ Github::Auth::KeysFile.new path: keys_file_path
90
+ end
91
+
92
+ def keys_file_path
93
+ Github::Auth::KeysFile::DEFAULT_PATH
94
+ end
95
+
96
+ def github_hostname
97
+ Github::Auth::KeysClient::DEFAULT_HOSTNAME
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,44 @@
1
+ require 'httparty'
2
+
3
+ module Github::Auth
4
+ class KeysClient
5
+ attr_reader :username, :hostname
6
+
7
+ UsernameRequiredError = Class.new StandardError
8
+ GithubUnavailableError = Class.new StandardError
9
+ GithubUserDoesNotExistError = Class.new StandardError
10
+
11
+ DEFAULT_HOSTNAME = 'https://api.github.com'
12
+
13
+ DEFAULT_OPTIONS = {
14
+ username: nil,
15
+ hostname: DEFAULT_HOSTNAME
16
+ }
17
+
18
+ def initialize(options = {})
19
+ options = DEFAULT_OPTIONS.merge options
20
+ raise UsernameRequiredError unless options.fetch :username
21
+
22
+ @username = options.fetch :username
23
+ @hostname = options.fetch :hostname
24
+ end
25
+
26
+ def keys
27
+ @keys ||= Array(github_response).map { |entry| entry.fetch 'key' }
28
+ end
29
+
30
+ private
31
+
32
+ def github_response
33
+ response = http_client.get "#{hostname}/users/#{username}/keys"
34
+ raise GithubUserDoesNotExistError if response.code == 404
35
+ response.parsed_response
36
+ rescue SocketError, Errno::ECONNREFUSED => e
37
+ raise GithubUnavailableError.new e
38
+ end
39
+
40
+ def http_client
41
+ HTTParty
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ module Github::Auth
2
+ class KeysFile
3
+ attr_reader :path
4
+
5
+ PermissionDeniedError = Class.new StandardError
6
+ FileDoesNotExistError = Class.new StandardError
7
+
8
+ DEFAULT_PATH = '~/.ssh/authorized_keys'
9
+
10
+ def initialize(options = {})
11
+ @path = File.expand_path(options[:path] || DEFAULT_PATH)
12
+ end
13
+
14
+ def write!(keys)
15
+ append_keys_file do |keys_file|
16
+ Array(keys).each do |key|
17
+ keys_file.write "\n#{key}" unless keys_file_content.include? key
18
+ end
19
+ end
20
+ end
21
+
22
+ def delete!(keys)
23
+ new_content = keys_file_content_without keys
24
+
25
+ write_keys_file { |keys_file| keys_file.write new_content }
26
+ end
27
+
28
+ private
29
+
30
+ def append_keys_file(&block)
31
+ with_keys_file 'a', block
32
+ end
33
+
34
+ def write_keys_file(&block)
35
+ with_keys_file 'w', block
36
+ end
37
+
38
+ def with_keys_file(mode, block)
39
+ File.open(path, mode) { |keys_file| block.call keys_file }
40
+ rescue Errno::EACCES => e
41
+ raise PermissionDeniedError.new e
42
+ rescue Errno::ENOENT => e
43
+ raise FileDoesNotExistError.new e
44
+ end
45
+
46
+ def keys_file_content
47
+ File.read path
48
+ end
49
+
50
+ def keys_file_content_without(keys)
51
+ keys_file_content.tap do |content|
52
+ Array(keys).each { |key| content.gsub! key, '' }
53
+ content.strip!
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ module Github
2
+ module Auth
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'support/mock_github_server'
3
+ require 'github/auth'
4
+
5
+ describe Github::Auth::CLI do
6
+ with_mock_github_server do |mock_server_hostname|
7
+ let(:hostname) { mock_server_hostname }
8
+ let(:keys_file) { Tempfile.new 'authorized_keys' }
9
+ let(:keys) { Github::Auth::MockGithubServer::KEYS }
10
+
11
+ after { keys_file.unlink }
12
+
13
+ def cli(argv)
14
+ described_class.new(argv).tap do |cli|
15
+ cli.stub(
16
+ github_hostname: hostname,
17
+ keys_file_path: keys_file.path
18
+ )
19
+ end
20
+ end
21
+
22
+ it 'adds and removes keys from the keys file' do
23
+ cli(%w(add chrishunt)).execute
24
+
25
+ keys_file.read.tap do |content|
26
+ keys.each { |key| expect(content).to include key }
27
+ end
28
+
29
+ cli(%w(remove chrishunt)).execute
30
+
31
+ expect(keys_file.read).to be_empty
32
+
33
+ keys_file.unlink
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'support/mock_github_server'
3
+ require 'github/auth'
4
+
5
+ describe Github::Auth::KeysClient do
6
+ it 'fetches all keys for the given github user' do
7
+ with_mock_github_server do |hostname, keys|
8
+ client = described_class.new(
9
+ username: 'chrishunt',
10
+ hostname: hostname
11
+ )
12
+
13
+ expect(client.keys).to eq keys
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ require 'tempfile'
2
+ require 'spec_helper'
3
+ require 'github/auth'
4
+
5
+ describe Github::Auth::KeysFile do
6
+ it 'writes and deletes keys from the keys file' do
7
+ tempfile = Tempfile.new 'authorized_keys'
8
+ keys_file = described_class.new path: tempfile.path
9
+ keys = %w(abc123 def456)
10
+
11
+ keys_file.write! keys
12
+ expect(tempfile.read).to include keys.join("\n")
13
+
14
+ keys_file.delete! keys.first
15
+ expect(tempfile.read).to_not include keys.first
16
+
17
+ keys_file.delete! keys.last
18
+ expect(tempfile.read).to_not include keys.last
19
+
20
+ expect(tempfile.read).to be_empty
21
+ end
22
+ end
@@ -0,0 +1,2 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
@@ -0,0 +1,40 @@
1
+ require 'httparty'
2
+ require 'sinatra/base'
3
+ require 'json'
4
+
5
+ module Github::Auth
6
+ class MockGithubServer < Sinatra::Base
7
+ KEYS = %w(abc234 def456)
8
+
9
+ set :port, 8001
10
+
11
+ get '/' do
12
+ 'success'
13
+ end
14
+
15
+ get '/users/chrishunt/keys' do
16
+ content_type :json
17
+
18
+ [
19
+ { 'id' => 123, 'key' => KEYS[0] },
20
+ { 'id' => 456, 'key' => KEYS[1] }
21
+ ].to_json
22
+ end
23
+ end
24
+ end
25
+
26
+ def with_mock_github_server
27
+ hostname = "http://localhost:#{Github::Auth::MockGithubServer.port}"
28
+ Thread.new { Github::Auth::MockGithubServer.run! }
29
+
30
+ while true
31
+ begin
32
+ HTTParty.get(hostname)
33
+ break
34
+ rescue Errno::ECONNREFUSED
35
+ # Do nothing, try again
36
+ end
37
+ end
38
+
39
+ yield hostname, Github::Auth::MockGithubServer::KEYS
40
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'github/auth/cli'
3
+
4
+ describe Github::Auth::CLI do
5
+ let(:argv) { [] }
6
+
7
+ subject { described_class.new argv }
8
+
9
+ describe '#execute' do
10
+ shared_examples_for 'a method that prints usage' do
11
+ it 'prints the usage' do
12
+ subject.should_receive(:print_usage)
13
+ subject.execute
14
+ end
15
+ end
16
+
17
+ context 'when missing a command' do
18
+ let(:argv) { [] }
19
+ it_should_behave_like 'a method that prints usage'
20
+ end
21
+
22
+ context 'with an invalid command' do
23
+ let(:argv) { ['invalid'] }
24
+ it_should_behave_like 'a method that prints usage'
25
+ end
26
+
27
+ context 'when no usernames are provide' do
28
+ let(:argv) { ['add'] }
29
+ it_should_behave_like 'a method that prints usage'
30
+ end
31
+
32
+ context 'with a valid action and usernames' do
33
+ let(:action) { 'add' }
34
+ let(:argv) { [action, 'chrishunt'] }
35
+
36
+ it 'calls the method matching the action name' do
37
+ subject.should_receive(action)
38
+ subject.execute
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'github/auth/keys_client'
3
+
4
+ describe Github::Auth::KeysClient do
5
+ subject { described_class.new username: username }
6
+
7
+ let(:username) { 'chrishunt' }
8
+ let(:http_client) { stub('HttpClient', get: response) }
9
+ let(:response_code) { 200 }
10
+ let(:parsed_response) { nil }
11
+ let(:response) {
12
+ stub('HTTParty::Response', {
13
+ code: response_code,
14
+ parsed_response: parsed_response
15
+ })
16
+ }
17
+
18
+ before { subject.stub(http_client: http_client) }
19
+
20
+ describe '#initialize' do
21
+ it 'requires a username' do
22
+ expect {
23
+ described_class.new
24
+ }.to raise_error Github::Auth::KeysClient::UsernameRequiredError
25
+
26
+ expect {
27
+ described_class.new username: nil
28
+ }.to raise_error Github::Auth::KeysClient::UsernameRequiredError
29
+ end
30
+
31
+ it 'saves the username' do
32
+ keys_client = described_class.new username: username
33
+ expect(keys_client.username).to eq username
34
+ end
35
+ end
36
+
37
+ describe '#keys' do
38
+ it 'requests keys from the Github API' do
39
+ http_client.should_receive(:get).with(
40
+ "https://api.github.com/users/#{username}/keys"
41
+ )
42
+ subject.keys
43
+ end
44
+
45
+ it 'memoizes the response' do
46
+ http_client.should_receive(:get).once
47
+ 2.times { subject.keys }
48
+ end
49
+
50
+ context 'when the github user has keys' do
51
+ let(:parsed_response) {[
52
+ { 'id' => 123, 'key' => 'BLAHBLAH' },
53
+ { 'id' => 456, 'key' => 'FLARBBLU' }
54
+ ]}
55
+
56
+ it 'returns the keys' do
57
+ expected_keys = parsed_response.map { |entry| entry.fetch 'key' }
58
+ expect(subject.keys).to eq expected_keys
59
+ end
60
+ end
61
+
62
+ context 'when the github user does not have keys' do
63
+ let(:parsed_response) { [] }
64
+
65
+ it 'returns an empty array' do
66
+ expect(subject.keys).to eq []
67
+ end
68
+ end
69
+
70
+ context 'when the github user does not exist' do
71
+ let(:response_code) { 404 }
72
+
73
+ it 'raises GithubUserDoesNotExistError' do
74
+ expect {
75
+ subject.keys
76
+ }.to raise_error Github::Auth::KeysClient::GithubUserDoesNotExistError
77
+ end
78
+ end
79
+
80
+ context 'when there is an issue connecting to Github' do
81
+ [SocketError, Errno::ECONNREFUSED].each do |exception|
82
+ before { http_client.stub(:get).and_raise exception }
83
+
84
+ it 'raises a GithubUnavailableError' do
85
+ expect {
86
+ subject.keys
87
+ }.to raise_error Github::Auth::KeysClient::GithubUnavailableError
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+ require 'github/auth/keys_file'
4
+
5
+ describe Github::Auth::KeysFile do
6
+ subject { described_class.new path: path }
7
+
8
+ let(:keys) { %w(abc123 def456) }
9
+ let(:keys_file) { Tempfile.new 'authorized_keys' }
10
+ let(:path) { keys_file.path }
11
+
12
+ after { keys_file.unlink } # clean up, delete tempfile
13
+
14
+ describe '#initialize' do
15
+ context 'without a path' do
16
+ let(:path) { nil }
17
+
18
+ it 'has a default path' do
19
+ expect(subject.path).to_not be_nil
20
+ end
21
+ end
22
+
23
+ context 'with a custom path' do
24
+ let(:path) { '/foo/bar/baz' }
25
+
26
+ it 'saves the custom path' do
27
+ expect(subject.path).to eq path
28
+ end
29
+ end
30
+
31
+ context 'with an unexpanded path' do
32
+ let(:path) { '~/my/home/dir' }
33
+
34
+ it 'expands the path' do
35
+ expect(subject.path).to_not include '~'
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#write!' do
41
+ it 'writes each key to the keys file' do
42
+ subject.write! keys
43
+ file_content = keys_file.read
44
+ keys.each { |key| expect(file_content).to include key }
45
+ end
46
+
47
+ context 'with a single key' do
48
+ let(:key) { 'abc123' }
49
+
50
+ it 'writes the single key to the keys file' do
51
+ subject.write! key
52
+ expect(keys_file.read).to include key
53
+ end
54
+ end
55
+
56
+ context 'with existing keys in the keys file' do
57
+ let(:existing_keys) { %w(ghi789 jkl123) }
58
+
59
+ before do
60
+ keys_file.write existing_keys.join("\n")
61
+ keys_file.rewind
62
+ end
63
+
64
+ it 'preserves the existing keys' do
65
+ subject.write! keys
66
+ file_lines = keys_file.readlines
67
+ existing_keys.each { |key| expect(file_lines).to include "#{key}\n" }
68
+ end
69
+
70
+ it 'does not write duplicate keys into the keys file' do
71
+ subject.write! existing_keys.first
72
+ expect(keys_file.readlines.count).to eq existing_keys.count
73
+ end
74
+ end
75
+
76
+ context 'when the keys file is readonly' do
77
+ before { File.chmod(0400, keys_file) }
78
+
79
+ it 'raises PermissionDeniedError' do
80
+ expect {
81
+ subject.write! keys
82
+ }.to raise_error Github::Auth::KeysFile::PermissionDeniedError
83
+ end
84
+ end
85
+
86
+ context 'when the keys file does not exist' do
87
+ let(:path) { 'not/a/real/file/path' }
88
+
89
+ it 'raises FileDoesNotExistError' do
90
+ expect {
91
+ subject.write! keys
92
+ }.to raise_error Github::Auth::KeysFile::FileDoesNotExistError
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#delete!' do
98
+ before do
99
+ keys_file.write keys.join("\n")
100
+ keys_file.rewind
101
+ end
102
+
103
+ context 'when the keys file has the key' do
104
+ let(:key) { keys[0] }
105
+ let(:other_key) { keys[1] }
106
+
107
+ it 'removes the key from the keys file' do
108
+ subject.delete! key
109
+ expect(keys_file.read).to_not include key
110
+ end
111
+
112
+ it 'does not remove the other key from the keys file' do
113
+ subject.delete! key
114
+ expect(keys_file.read).to include other_key
115
+ end
116
+
117
+ it 'does not leave blank lines' do
118
+ subject.delete! [key, other_key]
119
+ blank_lines = keys_file.readlines.select { |line| line =~ /^$\n/ }
120
+
121
+ expect(blank_lines).to be_empty
122
+ end
123
+ end
124
+
125
+ context 'when the keys file does not have the key' do
126
+ let(:key) { 'not-in-the-keys-file' }
127
+
128
+ it 'does not modify the keys file' do
129
+ original_keys_file = keys_file.read
130
+ keys_file.rewind
131
+
132
+ subject.delete! key
133
+
134
+ expect(keys_file.read).to eq original_keys_file
135
+ end
136
+ end
137
+ end
138
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github-auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hunt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: thin
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: httparty
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: SSH key management for Github users
112
+ email:
113
+ - c@chrishunt.co
114
+ executables:
115
+ - gh-auth
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - .travis.yml
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/gh-auth
126
+ - github-auth.gemspec
127
+ - img/mac-os-ssh-sharing.png
128
+ - lib/github/auth.rb
129
+ - lib/github/auth/cli.rb
130
+ - lib/github/auth/keys_client.rb
131
+ - lib/github/auth/keys_file.rb
132
+ - lib/github/auth/version.rb
133
+ - spec/acceptance/github/auth/cli_spec.rb
134
+ - spec/acceptance/github/auth/keys_client_spec.rb
135
+ - spec/acceptance/github/auth/keys_file_spec.rb
136
+ - spec/spec_helper.rb
137
+ - spec/support/mock_github_server.rb
138
+ - spec/unit/github/auth/cli_spec.rb
139
+ - spec/unit/github/auth/keys_client_spec.rb
140
+ - spec/unit/github/auth/keys_file_spec.rb
141
+ homepage: https://github.com/chrishunt/github-auth
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.0.3
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: SSH key management for Github users
165
+ test_files:
166
+ - spec/acceptance/github/auth/cli_spec.rb
167
+ - spec/acceptance/github/auth/keys_client_spec.rb
168
+ - spec/acceptance/github/auth/keys_file_spec.rb
169
+ - spec/spec_helper.rb
170
+ - spec/support/mock_github_server.rb
171
+ - spec/unit/github/auth/cli_spec.rb
172
+ - spec/unit/github/auth/keys_client_spec.rb
173
+ - spec/unit/github/auth/keys_file_spec.rb