gas2 0.1.7b
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.
- data/LICENSE +20 -0
- data/README.textile +66 -0
- data/bin/gas +53 -0
- data/lib/gas.rb +171 -0
- data/lib/gas/config.rb +148 -0
- data/lib/gas/gitconfig.rb +46 -0
- 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 +305 -0
- data/lib/gas/user.rb +33 -0
- data/lib/gas/version.rb +6 -0
- data/spec/integration/ssh_spec.rb +338 -0
- data/spec/spec_helper.rb +148 -0
- data/spec/unit/config_spec.rb +83 -0
- data/spec/unit/gitconfig_spec.rb +85 -0
- data/spec/unit/github_speaker_spec.rb +107 -0
- data/spec/unit/settings_spec.rb +56 -0
- data/spec/unit/user_spec.rb +28 -0
- metadata +233 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module Gas
|
2
|
+
|
3
|
+
# Class that class that interacts with the git config
|
4
|
+
class Gitconfig
|
5
|
+
@@nickname = ''
|
6
|
+
# Parse out the current user from the gitconfig
|
7
|
+
# @param [String] gitconfig The git configuration
|
8
|
+
# @return [User] The current user or nil if not present
|
9
|
+
def current_user
|
10
|
+
name = `git config --global --get user.name`
|
11
|
+
email = `git config --global --get user.email`
|
12
|
+
|
13
|
+
return nil if name.nil? && email.nil?
|
14
|
+
|
15
|
+
User.new name.delete("\n"), email.delete("\n"), @@nickname # git cli returns the name and email with \n at the end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get current user
|
19
|
+
def current_user_object
|
20
|
+
name = `git config --global --get user.name`
|
21
|
+
email = `git config --global --get user.email`
|
22
|
+
|
23
|
+
return nil if name.nil? && email.nil?
|
24
|
+
|
25
|
+
return {:name => name.strip, :email => email.strip}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Changes the user
|
29
|
+
# @param [String] name The new name
|
30
|
+
# @param [String] email The new email
|
31
|
+
def change_user(user)
|
32
|
+
nickname = user.nickname
|
33
|
+
@@nickname = nickname # maybe we should make nickname a class variable?
|
34
|
+
name = user.name
|
35
|
+
email = user.email
|
36
|
+
|
37
|
+
`git config --global user.name "#{name}"`
|
38
|
+
`git config --global user.email "#{email}"`
|
39
|
+
|
40
|
+
# confirm that this user has an ssh and if so, swap it in safely
|
41
|
+
Ssh.swap_in_rsa nickname
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -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
|
data/lib/gas/settings.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Gas
|
2
|
+
|
3
|
+
# Class that contains settings for the app
|
4
|
+
class Settings
|
5
|
+
attr_accessor :base_dir, :github_server
|
6
|
+
attr_reader :gas_dir, :ssh_dir
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@base_dir = '~'
|
10
|
+
@gas_dir = "#{@base_dir}/.gas"
|
11
|
+
@ssh_dir = "#{@base_dir}/.ssh"
|
12
|
+
@github_server = 'api.github.com'
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def gas_dir=(value)
|
20
|
+
@gas_dir = "#{@base_dir}/#{value}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def ssh_dir=(value)
|
24
|
+
@ssh_dir = "#{@base_dir}/#{value}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|