gas 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/gas.rb +27 -28
- data/lib/gas/config.rb +5 -5
- data/lib/gas/github_speaker.rb +219 -0
- data/lib/gas/prompter.rb +169 -0
- data/lib/gas/settings.rb +28 -0
- data/lib/gas/ssh.rb +86 -411
- data/lib/gas/version.rb +1 -1
- data/spec/integration/ssh_spec.rb +338 -0
- data/spec/spec_helper.rb +110 -34
- data/spec/{gas → unit}/config_spec.rb +15 -15
- data/spec/{gas → unit}/gitconfig_spec.rb +10 -10
- data/spec/unit/github_speaker_spec.rb +107 -0
- data/spec/unit/settings_spec.rb +56 -0
- data/spec/{gas → unit}/user_spec.rb +0 -0
- metadata +116 -27
- data/spec/gas/ssh_spec.rb +0 -328
data/lib/gas.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
GAS_DIRECTORY = "#{ENV['HOME']}/.gas" # File.expand_path('~/.gas')
|
4
|
+
SSH_DIRECTORY = "#{ENV['HOME']}/.ssh" # File.expand_path('~/.ssh')
|
3
5
|
GITHUB_SERVER = 'api.github.com'
|
6
|
+
IS_WINDOWS = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
|
4
7
|
|
5
8
|
|
6
9
|
require 'sshkey' #external
|
7
10
|
|
8
11
|
require 'gas/version'
|
12
|
+
require 'gas/prompter'
|
9
13
|
require 'gas/ssh'
|
10
14
|
require 'gas/user'
|
11
15
|
require 'gas/config'
|
12
16
|
require 'gas/gitconfig'
|
17
|
+
require 'gas/settings'
|
18
|
+
require 'gas/github_speaker'
|
13
19
|
|
14
20
|
|
15
21
|
module Gas
|
@@ -24,8 +30,6 @@ module Gas
|
|
24
30
|
puts
|
25
31
|
puts @config
|
26
32
|
puts
|
27
|
-
|
28
|
-
# self.show # XXX: get rid of
|
29
33
|
end
|
30
34
|
|
31
35
|
# Shows the current user
|
@@ -43,11 +47,11 @@ module Gas
|
|
43
47
|
# Sets _nickname_ as current user
|
44
48
|
# @param [String] nickname The nickname to use
|
45
49
|
def self.use(nickname)
|
46
|
-
self.no_user?
|
50
|
+
return false unless self.no_user?(nickname)
|
47
51
|
user = @config[nickname]
|
48
|
-
|
52
|
+
|
49
53
|
@gitconfig.change_user user # daring change made here! Heads up Walle
|
50
|
-
|
54
|
+
|
51
55
|
self.show
|
52
56
|
end
|
53
57
|
|
@@ -55,21 +59,21 @@ module Gas
|
|
55
59
|
# @param [String] nickname The nickname of the author
|
56
60
|
# @param [String] name The name of the author
|
57
61
|
# @param [String] email The email of the author
|
58
|
-
def self.add(nickname, name, email)
|
62
|
+
def self.add(nickname, name, email, github_speaker = nil)
|
59
63
|
return false if self.has_user?(nickname)
|
60
64
|
user = User.new name, email, nickname
|
61
65
|
@config.add user
|
62
66
|
@config.save!
|
63
|
-
|
67
|
+
|
64
68
|
using_ssh = Ssh.setup_ssh_keys user
|
65
69
|
|
66
|
-
Ssh.upload_public_key_to_github(user) if using_ssh
|
67
|
-
|
70
|
+
Ssh.upload_public_key_to_github(user, github_speaker) if using_ssh
|
71
|
+
|
68
72
|
puts 'Added new author'
|
69
73
|
puts user
|
70
74
|
end
|
71
|
-
|
72
|
-
|
75
|
+
|
76
|
+
|
73
77
|
# Adds an ssh key for the specified user
|
74
78
|
def self.ssh(nickname)
|
75
79
|
if nickname.nil?
|
@@ -89,26 +93,26 @@ module Gas
|
|
89
93
|
puts "The ssh feature of gas offers you and the world ease of use, and even marginally enhanced privacy against corporate databases. Did you know that IBM built one of the first automated database systems? These ancient database machines (called tabulators) were used to facilitate the holocaust =("
|
90
94
|
else
|
91
95
|
user = @config[nickname]
|
92
|
-
|
93
|
-
|
96
|
+
|
97
|
+
|
94
98
|
# Prompt Remake this user's ssh keys?
|
95
|
-
|
99
|
+
|
96
100
|
# check for ssh keys
|
97
101
|
if !Ssh.corresponding_rsa_files_exist?(nickname)
|
98
102
|
Ssh.setup_ssh_keys user
|
99
103
|
Ssh.upload_public_key_to_github user
|
100
|
-
else
|
104
|
+
else
|
101
105
|
Ssh.setup_ssh_keys user
|
102
106
|
Ssh.upload_public_key_to_github user
|
103
107
|
end
|
104
108
|
end
|
105
109
|
end
|
106
|
-
|
110
|
+
|
107
111
|
|
108
112
|
# Imports current user from .gitconfig to .gas
|
109
113
|
# @param [String] nickname The nickname to give to the new user
|
110
114
|
def self.import(nickname)
|
111
|
-
self.has_user?
|
115
|
+
return false if self.has_user?(nickname)
|
112
116
|
user = @gitconfig.current_user
|
113
117
|
|
114
118
|
if user
|
@@ -127,18 +131,13 @@ module Gas
|
|
127
131
|
# Deletes an author from the config using nickname
|
128
132
|
# @param [String] nickname The nickname of the author
|
129
133
|
def self.delete(nickname)
|
130
|
-
|
134
|
+
|
131
135
|
return false unless self.no_user? nickname # I re-engineered this section so I could use Gas.delete in a test even when that author didn't exist
|
132
|
-
# TODO: The name no_user? is now very confusing. It should be changed to something like "
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
# exit
|
136
|
+
# TODO: The name no_user? is now very confusing. It should be changed to something like "user_exists?" now maybe?
|
137
|
+
Ssh.delete nickname
|
138
|
+
|
138
139
|
@config.delete nickname
|
139
140
|
@config.save!
|
140
|
-
|
141
|
-
#Ssh.delete nickname # TODO: delete this duplicate after...
|
142
141
|
|
143
142
|
puts "Deleted author #{nickname}"
|
144
143
|
return true
|
data/lib/gas/config.rb
CHANGED
@@ -10,9 +10,9 @@ module Gas
|
|
10
10
|
# then it creates the ~/.gas FOLDER and saves the old .gas file as ~/git.conf
|
11
11
|
#
|
12
12
|
def migrate_to_gas_dir!
|
13
|
-
old_config_file =
|
14
|
-
config_dir =
|
15
|
-
new_config_file =
|
13
|
+
old_config_file = GAS_DIRECTORY
|
14
|
+
config_dir = GAS_DIRECTORY
|
15
|
+
new_config_file = GAS_DIRECTORY + "/gas.authors"
|
16
16
|
|
17
17
|
if File.file? old_config_file
|
18
18
|
file = File.open(old_config_file, "rb")
|
@@ -35,8 +35,8 @@ module Gas
|
|
35
35
|
# @param [String] config The override config
|
36
36
|
def initialize(users = nil, config = nil)
|
37
37
|
migrate_to_gas_dir! # Migrates old users to the new configuration file location, how thoughtful of me, I know
|
38
|
-
@config_file =
|
39
|
-
@gas_dir =
|
38
|
+
@config_file = "#{GAS_DIRECTORY}/gas.authors"
|
39
|
+
@gas_dir = GAS_DIRECTORY
|
40
40
|
@config = ''
|
41
41
|
|
42
42
|
if config.nil?
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module Gas
|
2
|
+
# A beautiful class that makes working with the github API an enjoyable experience
|
3
|
+
class GithubSpeaker
|
4
|
+
attr_reader :user, :account_name, :password, :keys, :status
|
5
|
+
attr_accessor :server
|
6
|
+
|
7
|
+
def keys
|
8
|
+
refresh_keys if @keys.nil?
|
9
|
+
@keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(user, account_name=nil, password=nil, server = 'api.github.com')
|
13
|
+
@user = user
|
14
|
+
@server = server
|
15
|
+
@keys = nil
|
16
|
+
|
17
|
+
# sort out username and password... Make it's own function?
|
18
|
+
if account_name.nil? and password.nil?
|
19
|
+
# Prompt for username and password
|
20
|
+
credentials = get_username_and_password_diligently
|
21
|
+
@account_name = credentials[:account_name] # this overwrite happens twice to make testing easier... Stub on get_username, kekeke
|
22
|
+
@password = credentials[:password]
|
23
|
+
else
|
24
|
+
@account_name = account_name
|
25
|
+
@password = password
|
26
|
+
authenticate
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def post_key!(rsa)
|
33
|
+
refresh_keys if @keys.nil?
|
34
|
+
|
35
|
+
puts "Posting key to GitHub.com..."
|
36
|
+
|
37
|
+
# find key...
|
38
|
+
if has_key(rsa)
|
39
|
+
puts "Key already installed."
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
title = "GAS: #{@user.nickname}"
|
43
|
+
result = install_key(rsa)
|
44
|
+
|
45
|
+
if result != false
|
46
|
+
@keys << result
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def refresh_keys
|
53
|
+
raise "Attempted to update keys when unable to authenticate credentials with github" if @status != :authenticated
|
54
|
+
|
55
|
+
path = '/user/keys'
|
56
|
+
|
57
|
+
http = Net::HTTP.new(@server,443)
|
58
|
+
req = Net::HTTP::Get.new(path)
|
59
|
+
http.use_ssl = true
|
60
|
+
req.basic_auth @account_name, @password
|
61
|
+
response = http.request(req)
|
62
|
+
|
63
|
+
@keys = JSON.parse(response.body)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# Cycles through github, looking to see if rsa exists as a public key, then deletes it if it does
|
68
|
+
def remove_key!(rsa)
|
69
|
+
refresh_keys
|
70
|
+
|
71
|
+
# loop through arrays checking against 'key'
|
72
|
+
@keys.each do |key|
|
73
|
+
if key["key"] == rsa
|
74
|
+
return remove_key_by_id!(key["id"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
return false # key not found
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
private
|
83
|
+
def authenticate
|
84
|
+
path = '/user'
|
85
|
+
|
86
|
+
http = Net::HTTP.new(@server,443)
|
87
|
+
http.use_ssl = true
|
88
|
+
|
89
|
+
req = Net::HTTP::Get.new(path)
|
90
|
+
req.basic_auth @account_name, @password
|
91
|
+
response = http.request(req)
|
92
|
+
|
93
|
+
result = JSON.parse(response.body)["message"]
|
94
|
+
|
95
|
+
if result == "Bad credentials"
|
96
|
+
@status = :bad_credentials
|
97
|
+
return false
|
98
|
+
else
|
99
|
+
@status = :authenticated
|
100
|
+
return true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_username_and_password_and_authenticate
|
105
|
+
puts "Type your github.com user name:"
|
106
|
+
print "User: "
|
107
|
+
account_name = STDIN.gets.strip
|
108
|
+
|
109
|
+
puts "Type your github password:"
|
110
|
+
password = ask("Password: ") { |q| q.echo = false }
|
111
|
+
puts
|
112
|
+
|
113
|
+
credentials = {:account_name => account_name, :password => password}
|
114
|
+
#p credentials
|
115
|
+
@account_name = account_name
|
116
|
+
@password = password
|
117
|
+
|
118
|
+
if authenticate
|
119
|
+
return credentials
|
120
|
+
else
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get's the username and password from the user, then authenticates. If it fails, it asks them if they'd like to try again.
|
126
|
+
# Returns false if aborted
|
127
|
+
def get_username_and_password_diligently
|
128
|
+
while true
|
129
|
+
credentials = get_username_and_password_and_authenticate
|
130
|
+
if credentials == false
|
131
|
+
puts "Could not authenticate, try again?"
|
132
|
+
puts "y/n"
|
133
|
+
|
134
|
+
again = STDIN.gets.strip
|
135
|
+
case again.downcase
|
136
|
+
when "y"
|
137
|
+
when "n"
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
else
|
141
|
+
return credentials
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def install_key(rsa)
|
147
|
+
require "socket"
|
148
|
+
host_name = Socket.gethostname
|
149
|
+
|
150
|
+
title = "GAS: #{@user.nickname} \-#{host_name}"
|
151
|
+
|
152
|
+
path = '/user/keys'
|
153
|
+
|
154
|
+
http = Net::HTTP.new(@server, 443) # 443 for ssl
|
155
|
+
http.use_ssl = true
|
156
|
+
|
157
|
+
req = Net::HTTP::Post.new(path)
|
158
|
+
req.basic_auth @account_name, @password
|
159
|
+
req.body = "{\"title\":\"#{title}\", \"key\":\"#{rsa}\"}"
|
160
|
+
|
161
|
+
response = http.start {|m_http| m_http.request(req) }
|
162
|
+
the_code = response.code
|
163
|
+
|
164
|
+
key_json = JSON.parse(response.body)
|
165
|
+
|
166
|
+
return key_json if the_code == "201"
|
167
|
+
|
168
|
+
puts "The key you are trying to use already exists in another github user's account. You need to use another key." if the_code == "already_exists"
|
169
|
+
|
170
|
+
# currently.. I think it always returns "already_exists" even if successful. API bug.
|
171
|
+
puts "Something may have gone wrong. Either github changed their API, or your key couldn't be installed." if the_code != "already_exists"
|
172
|
+
|
173
|
+
#return true if my_hash.key?("errors") # this doesn't work due to it being a buggy API atm # false change me to false when they fix their API
|
174
|
+
puts "Server Response: #{response.body}"
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
|
178
|
+
# Cycles through github, looking to see if rsa exists as a public key, then deletes it if it does
|
179
|
+
def has_key(rsa)
|
180
|
+
refresh_keys if @keys.nil?
|
181
|
+
return false if @keys.empty?
|
182
|
+
|
183
|
+
# loop through arrays checking against 'key'
|
184
|
+
@keys.each do |key|
|
185
|
+
return true if key["key"] == rsa
|
186
|
+
end
|
187
|
+
|
188
|
+
return false # key not found
|
189
|
+
end
|
190
|
+
|
191
|
+
def remove_key_by_id!(id)
|
192
|
+
path = "/user/keys/#{id}"
|
193
|
+
|
194
|
+
http = Net::HTTP.new(@server,443)
|
195
|
+
http.use_ssl = true
|
196
|
+
req = Net::HTTP::Delete.new(path)
|
197
|
+
req.basic_auth @account_name, @password
|
198
|
+
|
199
|
+
response = http.request(req)
|
200
|
+
|
201
|
+
if response.body.nil?
|
202
|
+
#@keys = nil # lame hack! sooo lazy of me. I should learn how to remove the proper key from the @keys hash...
|
203
|
+
remove_key_from_keys id
|
204
|
+
return true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def remove_key_from_keys(id_to_delete)
|
210
|
+
@keys.each.with_index do |key, i|
|
211
|
+
@keys.delete_at(i) if key['id'].to_i == id_to_delete.to_i
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
data/lib/gas/prompter.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
module Gas
|
2
|
+
module Prompter
|
3
|
+
@invalid_input_response_with_default = "Please use 'y' or 'n' or enter for default."
|
4
|
+
# If the user says 'f', the system will
|
5
|
+
# report that there isn't an id_rsa already in gas. This causes a new key to overwrite automatically down the road.
|
6
|
+
# This is for checking if a .gas/rsa file already exists for a nickname which is being registered
|
7
|
+
# If the rsa exists, then we're goona need to ask if we should use it, or if we should delete it
|
8
|
+
#
|
9
|
+
# Returns true to indicate that the user would like to use the rsa file already in .gas ()
|
10
|
+
# Returns false when there are no naming conflicts.
|
11
|
+
def self.user_wants_to_use_key_already_in_gas?(uid = '')
|
12
|
+
puts "Gas has detected a key in its archive directory ~/.gas/#{uid}_id_rsa. Should gas use this key or overwrite this key with a brand new one?"
|
13
|
+
puts "Keep current key? [y/n]"
|
14
|
+
|
15
|
+
while true
|
16
|
+
keep_current_file = clean_gets
|
17
|
+
|
18
|
+
case keep_current_file
|
19
|
+
|
20
|
+
when "y"
|
21
|
+
return true # keep the files already in .gas, skip making key.
|
22
|
+
when "n"
|
23
|
+
return false
|
24
|
+
else
|
25
|
+
puts "please respond 'y' or 'n'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Checks if the ~/.ssh directroy contains id_rsa and id_rsa.pub
|
31
|
+
# if it does, it asks the user if they would like to use that as their ssh key, instead of generating a new key pair.
|
32
|
+
#
|
33
|
+
def self.user_wants_to_use_key_already_in_ssh?
|
34
|
+
puts "Generate a brand new ssh key pair? (Choose 'n' to use key in ~/.ssh/id_rsa)"
|
35
|
+
puts "Default: 'y'"
|
36
|
+
puts "[Y/n]"
|
37
|
+
|
38
|
+
while true
|
39
|
+
generate_new_rsa = clean_gets.downcase
|
40
|
+
case generate_new_rsa
|
41
|
+
when "y", ""
|
42
|
+
return false
|
43
|
+
when "n"
|
44
|
+
return true # return true if we aren't generating a new key
|
45
|
+
else
|
46
|
+
puts @invalid_input_response_with_default
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.user_wants_gas_to_handle_rsa_keys?
|
52
|
+
puts
|
53
|
+
puts "Do you want gas to handle switching rsa keys for this user?"
|
54
|
+
puts "[Y/n]"
|
55
|
+
|
56
|
+
while true
|
57
|
+
handle_rsa = clean_gets
|
58
|
+
|
59
|
+
case handle_rsa
|
60
|
+
when "y", ""
|
61
|
+
return true
|
62
|
+
when "n"
|
63
|
+
return false
|
64
|
+
else
|
65
|
+
puts "Please use 'y' or 'n'"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.user_wants_to_remove_the_keys_that_already_exist_for_this_user?(uid)
|
71
|
+
puts
|
72
|
+
puts "Well... there's already a ~/.gas/#{uid}_id_rsa configured and ready to go. Are you sure you don't want gas to handle rsa switching? (Clicking no will delete the key from the gas directory)"
|
73
|
+
puts "Just let gas handle ssh key for this user? [y/n]"
|
74
|
+
|
75
|
+
while true
|
76
|
+
keep_file = clean_gets
|
77
|
+
|
78
|
+
case keep_file
|
79
|
+
when "n"
|
80
|
+
return true
|
81
|
+
when "y"
|
82
|
+
puts "Excelent! Gas will handle rsa keys for this user."
|
83
|
+
return false
|
84
|
+
else
|
85
|
+
puts @invalid_input_response_with_default
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# This is another prompt function, but it returns a more complicated lexicon
|
91
|
+
#
|
92
|
+
# returns "a", "l", "g", or "n"
|
93
|
+
def self.user_wants_to_delete_all_ssh_data?
|
94
|
+
puts "Would you like to remove all of this user's ssh keys too!?!"
|
95
|
+
puts "(github account keys can be removed as well!)"
|
96
|
+
puts
|
97
|
+
puts "a: All, the local copy, and checks github too."
|
98
|
+
puts "l: Remove local key only."
|
99
|
+
puts "g: Removes key from github.com only."
|
100
|
+
puts "n: Don't remove this user's keys."
|
101
|
+
puts "Default: l"
|
102
|
+
|
103
|
+
while true
|
104
|
+
delete_all_keys = clean_gets
|
105
|
+
|
106
|
+
case delete_all_keys.downcase
|
107
|
+
when "a"
|
108
|
+
return "a"
|
109
|
+
when "l", ""
|
110
|
+
return "l"
|
111
|
+
when "g"
|
112
|
+
return "g"
|
113
|
+
when "n"
|
114
|
+
return "n"
|
115
|
+
else
|
116
|
+
puts "please use 'a', 'l', 'g' or 'n' for NONE."
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.user_wants_to_install_key_to_github?
|
122
|
+
puts "Gas can automatically install this ssh key into the github account of your choice. Would you like gas to do this for you? (Requires inputting github username and password)"
|
123
|
+
puts "[Y/n]"
|
124
|
+
|
125
|
+
while true
|
126
|
+
upload_key = clean_gets.downcase
|
127
|
+
case upload_key
|
128
|
+
when "y", ""
|
129
|
+
return true
|
130
|
+
when "n"
|
131
|
+
return false
|
132
|
+
else
|
133
|
+
puts @invalid_input_response_with_default
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.user_wants_to_overwrite_existing_rsa_key?
|
139
|
+
puts "~/.ssh/id_rsa already exists. Overwrite?"
|
140
|
+
puts "[y/n]"
|
141
|
+
|
142
|
+
while true
|
143
|
+
overwrite = clean_gets
|
144
|
+
case overwrite
|
145
|
+
when "y"
|
146
|
+
return true
|
147
|
+
when "n"
|
148
|
+
return false
|
149
|
+
else
|
150
|
+
puts "please respond 'y' or 'n'"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# If the user hits ctrl+c with this, it will exit cleanly
|
156
|
+
def self.clean_gets
|
157
|
+
begin
|
158
|
+
getit = STDIN.gets.strip
|
159
|
+
rescue SystemExit, Interrupt # catch if they hit ctrl+c
|
160
|
+
puts
|
161
|
+
puts "Safely aborting operation..." # reassure user that ctrl+c is fine to use.
|
162
|
+
exit
|
163
|
+
end
|
164
|
+
|
165
|
+
return getit
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|