gas2 0.1.7b
Sign up to get free protection for your applications and to get access to all the features.
- 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
|