gas 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +10 -4
- data/bin/gas +5 -0
- data/lib/gas.rb +76 -12
- data/lib/gas/config.rb +54 -2
- data/lib/gas/gitconfig.rb +21 -3
- data/lib/gas/ssh.rb +630 -0
- data/lib/gas/user.rb +4 -1
- data/lib/gas/version.rb +1 -1
- data/spec/gas/config_spec.rb +9 -1
- data/spec/gas/gitconfig_spec.rb +52 -11
- data/spec/gas/ssh_spec.rb +328 -0
- data/spec/gas/user_spec.rb +2 -2
- data/spec/spec_helper.rb +73 -0
- metadata +45 -16
data/README.textile
CHANGED
@@ -4,7 +4,7 @@ h1. gas - manage your git author accounts
|
|
4
4
|
|
5
5
|
h2. Description
|
6
6
|
|
7
|
-
Gas is a utility
|
7
|
+
Gas is a mighty utility that helps you keep track of your multiple git authors. Add them to gas and switch between them instantly! Great if you use one author at work and one at home or if you are doing pair programming.
|
8
8
|
|
9
9
|
h2. Installation
|
10
10
|
|
@@ -20,19 +20,19 @@ $ rake install
|
|
20
20
|
|
21
21
|
h2. Running
|
22
22
|
|
23
|
-
|
23
|
+
The default task is to list authors
|
24
24
|
|
25
25
|
bc. $ gas
|
26
26
|
|
27
27
|
bc. $ gas list
|
28
28
|
|
29
|
-
This lists the authors that are set up.
|
29
|
+
This lists the authors that are set up in the ~/.gas/gas.authors file.
|
30
30
|
|
31
31
|
You can import your current user by giving it a nickname
|
32
32
|
|
33
33
|
bc. $ gas import current_user
|
34
34
|
|
35
|
-
To add
|
35
|
+
To add an author use, add
|
36
36
|
|
37
37
|
bc. $ gas add walle "Fredrik Wallgren" fredrik.wallgren@gmail.com
|
38
38
|
|
@@ -44,4 +44,10 @@ To delete it again use, delete
|
|
44
44
|
|
45
45
|
bc. $ gas delete walle
|
46
46
|
|
47
|
+
Gas can also juggle your id_rsa ssh keys, which is helpful for uploading to github between multiple accounts. Indespensible for teachers who need to instruct their students on how to use github!
|
48
|
+
|
49
|
+
bc. $ gas add Njax NotarySojac no@mail.com
|
50
|
+
Do you want gas to handle switching rsa keys for this user?
|
51
|
+
[y/n]
|
52
|
+
|
47
53
|
View @gas -h@ to see all options.
|
data/bin/gas
CHANGED
@@ -36,6 +36,11 @@ class GasRunner < Thor
|
|
36
36
|
def delete(nickname)
|
37
37
|
Gas.delete nickname
|
38
38
|
end
|
39
|
+
|
40
|
+
desc "ssh", "Creates a new ssh key for an existing gas author"
|
41
|
+
def ssh(nickname=nil)
|
42
|
+
Gas.ssh nickname
|
43
|
+
end
|
39
44
|
|
40
45
|
desc "version", "Prints Gas's version"
|
41
46
|
def version
|
data/lib/gas.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
+
GAS_DIRECTORY = File.expand_path('~/.gas')
|
2
|
+
SSH_DIRECTORY = File.expand_path('~/.ssh')
|
3
|
+
GITHUB_SERVER = 'api.github.com'
|
4
|
+
|
5
|
+
|
6
|
+
require 'sshkey' #external
|
7
|
+
|
1
8
|
require 'gas/version'
|
9
|
+
require 'gas/ssh'
|
2
10
|
require 'gas/user'
|
3
11
|
require 'gas/config'
|
4
12
|
require 'gas/gitconfig'
|
5
13
|
|
14
|
+
|
6
15
|
module Gas
|
7
16
|
|
8
17
|
@config = Config.new
|
@@ -10,10 +19,13 @@ module Gas
|
|
10
19
|
|
11
20
|
# Lists all authors
|
12
21
|
def self.list
|
22
|
+
puts
|
13
23
|
puts 'Available users:'
|
24
|
+
puts
|
14
25
|
puts @config
|
26
|
+
puts
|
15
27
|
|
16
|
-
self.show
|
28
|
+
# self.show # XXX: get rid of
|
17
29
|
end
|
18
30
|
|
19
31
|
# Shows the current user
|
@@ -33,9 +45,9 @@ module Gas
|
|
33
45
|
def self.use(nickname)
|
34
46
|
self.no_user? nickname
|
35
47
|
user = @config[nickname]
|
36
|
-
|
37
|
-
@gitconfig.change_user user
|
38
|
-
|
48
|
+
|
49
|
+
@gitconfig.change_user user # daring change made here! Heads up Walle
|
50
|
+
|
39
51
|
self.show
|
40
52
|
end
|
41
53
|
|
@@ -44,14 +56,54 @@ module Gas
|
|
44
56
|
# @param [String] name The name of the author
|
45
57
|
# @param [String] email The email of the author
|
46
58
|
def self.add(nickname, name, email)
|
47
|
-
self.has_user?
|
59
|
+
return false if self.has_user?(nickname)
|
48
60
|
user = User.new name, email, nickname
|
49
61
|
@config.add user
|
50
62
|
@config.save!
|
51
|
-
|
52
|
-
|
63
|
+
|
64
|
+
using_ssh = Ssh.setup_ssh_keys user
|
65
|
+
|
66
|
+
Ssh.upload_public_key_to_github(user) if using_ssh
|
67
|
+
|
68
|
+
puts 'Added new author'
|
53
69
|
puts user
|
54
70
|
end
|
71
|
+
|
72
|
+
|
73
|
+
# Adds an ssh key for the specified user
|
74
|
+
def self.ssh(nickname)
|
75
|
+
if nickname.nil?
|
76
|
+
puts "Oh, so you'd like an elaborate explanation on how ssh key juggling works? Well pull up a chair!"
|
77
|
+
puts
|
78
|
+
puts "Gas can juggle ssh keys for you. It works best in a unix based environment (so at least use git bash or cygwin on a windows platform)."
|
79
|
+
puts "You will be prompted if you would like to handle SSH keys when you create a new user."
|
80
|
+
puts "If you are a long time user of gas, you can add ssh to an author by the command..."
|
81
|
+
puts "\$ gas ssh NICKNAME"
|
82
|
+
puts
|
83
|
+
puts "Your ssh keys will be stored in ~/.gas/NICKNAME_id_rsa and automatically copied to ~/.ssh/id_rsa when you use the command..."
|
84
|
+
puts "\$ gas use NICKNAME"
|
85
|
+
puts "If ~/.ssh/id_rsa already exists, you will be prompted UNLESS that rsa file is already backed up in the .gas directory (I'm so sneaky, huh?)"
|
86
|
+
puts
|
87
|
+
puts "The unix command ssh-add is used in order to link up your rsa keys when you attempt to make an ssh connection (git push uses ssh keys of course)"
|
88
|
+
puts
|
89
|
+
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
|
+
else
|
91
|
+
user = @config[nickname]
|
92
|
+
|
93
|
+
|
94
|
+
# Prompt Remake this user's ssh keys?
|
95
|
+
|
96
|
+
# check for ssh keys
|
97
|
+
if !Ssh.corresponding_rsa_files_exist?(nickname)
|
98
|
+
Ssh.setup_ssh_keys user
|
99
|
+
Ssh.upload_public_key_to_github user
|
100
|
+
else
|
101
|
+
Ssh.setup_ssh_keys user
|
102
|
+
Ssh.upload_public_key_to_github user
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
55
107
|
|
56
108
|
# Imports current user from .gitconfig to .gas
|
57
109
|
# @param [String] nickname The nickname to give to the new user
|
@@ -72,14 +124,24 @@ module Gas
|
|
72
124
|
end
|
73
125
|
end
|
74
126
|
|
75
|
-
# Deletes
|
127
|
+
# Deletes an author from the config using nickname
|
76
128
|
# @param [String] nickname The nickname of the author
|
77
129
|
def self.delete(nickname)
|
78
|
-
|
130
|
+
|
131
|
+
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 "is_user?" now maybe?
|
133
|
+
|
134
|
+
Ssh.delete nickname # XXX: there are 2 calls to this in the method!
|
135
|
+
|
136
|
+
|
137
|
+
# exit
|
79
138
|
@config.delete nickname
|
80
139
|
@config.save!
|
140
|
+
|
141
|
+
#Ssh.delete nickname # TODO: delete this duplicate after...
|
81
142
|
|
82
143
|
puts "Deleted author #{nickname}"
|
144
|
+
return true
|
83
145
|
end
|
84
146
|
|
85
147
|
# Prints the current version
|
@@ -92,17 +154,19 @@ module Gas
|
|
92
154
|
def self.no_user?(nickname)
|
93
155
|
if !@config.exists? nickname
|
94
156
|
puts "Nickname #{nickname} does not exist"
|
95
|
-
|
157
|
+
return false
|
96
158
|
end
|
159
|
+
return true
|
97
160
|
end
|
98
161
|
|
99
162
|
# Checks if the user exists and gives error and exit if so
|
100
163
|
# @param [String] nickname
|
101
164
|
def self.has_user?(nickname)
|
102
165
|
if @config.exists? nickname
|
103
|
-
puts "Nickname #{nickname}
|
104
|
-
|
166
|
+
puts "Nickname #{nickname} already exists"
|
167
|
+
return true
|
105
168
|
end
|
169
|
+
return false
|
106
170
|
end
|
107
171
|
|
108
172
|
end
|
data/lib/gas/config.rb
CHANGED
@@ -6,15 +6,42 @@ module Gas
|
|
6
6
|
class Config
|
7
7
|
attr_reader :users
|
8
8
|
|
9
|
+
# This function checks for a ~/.gas FILE and if it exists, it puts it into memory and deletes it from the HDD
|
10
|
+
# then it creates the ~/.gas FOLDER and saves the old .gas file as ~/git.conf
|
11
|
+
#
|
12
|
+
def migrate_to_gas_dir!
|
13
|
+
old_config_file = File.expand_path('~/.gas')
|
14
|
+
config_dir = File.expand_path('~/.gas')
|
15
|
+
new_config_file = File.expand_path('~/.gas') + "/gas.authors"
|
16
|
+
|
17
|
+
if File.file? old_config_file
|
18
|
+
file = File.open(old_config_file, "rb")
|
19
|
+
contents = file.read
|
20
|
+
file.close
|
21
|
+
|
22
|
+
File.delete old_config_file
|
23
|
+
|
24
|
+
Dir::mkdir(config_dir)
|
25
|
+
|
26
|
+
file = File.new(new_config_file, "w")
|
27
|
+
file.puts contents
|
28
|
+
file.close
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
9
33
|
# Initializes the object. If no users are supplied we look for a config file, if none then create it, and parse it to load users
|
10
34
|
# @param [Array<User>] users The override users
|
11
35
|
# @param [String] config The override config
|
12
36
|
def initialize(users = nil, config = nil)
|
13
|
-
|
37
|
+
migrate_to_gas_dir! # Migrates old users to the new configuration file location, how thoughtful of me, I know
|
38
|
+
@config_file = File.expand_path('~/.gas/gas.authors')
|
39
|
+
@gas_dir = File.expand_path('~/.gas')
|
14
40
|
@config = ''
|
15
41
|
|
16
42
|
if config.nil?
|
17
43
|
if !File.exists? @config_file
|
44
|
+
Dir::mkdir(@gas_dir)
|
18
45
|
FileUtils.touch @config_file
|
19
46
|
end
|
20
47
|
|
@@ -87,9 +114,34 @@ module Gas
|
|
87
114
|
end
|
88
115
|
end
|
89
116
|
|
117
|
+
|
90
118
|
# Override to_s to output correct format
|
91
119
|
def to_s
|
92
|
-
|
120
|
+
gc = Gitconfig.new
|
121
|
+
|
122
|
+
users = @users.map do |user|
|
123
|
+
if is_current_user(gc.current_user_object[:name], gc.current_user_object[:email], user.to_s)
|
124
|
+
" ==>" + user.to_s[5,user.to_s.length]
|
125
|
+
else
|
126
|
+
user.to_s
|
127
|
+
end
|
128
|
+
end.join("\n")
|
129
|
+
|
130
|
+
return users
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# Scans the @users (a string containing info formatted identical to the gas.author file)
|
135
|
+
# ...and checks to see if it's name and email match what you're looking for
|
136
|
+
def is_current_user(name, email, object)
|
137
|
+
object.scan(/\[(.+)\]\s+name = (.+)\s+email = (.+)/) do |nicknamec, namec, emailc|
|
138
|
+
if namec == name and emailc == email
|
139
|
+
# check if ssh is active
|
140
|
+
# TODO: Check if its SSH key is setup, and indicate SSH ACTIVE
|
141
|
+
return true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
return false # could not get a current user's nickname
|
93
145
|
end
|
94
146
|
|
95
147
|
end
|
data/lib/gas/gitconfig.rb
CHANGED
@@ -2,7 +2,7 @@ module Gas
|
|
2
2
|
|
3
3
|
# Class that class that interacts with the git config
|
4
4
|
class Gitconfig
|
5
|
-
|
5
|
+
@@nickname = ''
|
6
6
|
# Parse out the current user from the gitconfig
|
7
7
|
# @param [String] gitconfig The git configuration
|
8
8
|
# @return [User] The current user or nil if not present
|
@@ -12,15 +12,33 @@ module Gas
|
|
12
12
|
|
13
13
|
return nil if name.nil? && email.nil?
|
14
14
|
|
15
|
-
User.new name.delete("\n"), email.delete("\n") # git cli returns the name and email with \n at the end
|
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}
|
16
26
|
end
|
17
27
|
|
18
28
|
# Changes the user
|
19
29
|
# @param [String] name The new name
|
20
30
|
# @param [String] email The new email
|
21
|
-
def change_user(
|
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
|
+
|
22
37
|
`git config --global user.name "#{name}"`
|
23
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
|
24
42
|
end
|
25
43
|
|
26
44
|
end
|
data/lib/gas/ssh.rb
ADDED
@@ -0,0 +1,630 @@
|
|
1
|
+
module Gas
|
2
|
+
|
3
|
+
class Ssh
|
4
|
+
require 'highline/import'
|
5
|
+
require 'net/https'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
|
9
|
+
# If the user says 'f', the system will
|
10
|
+
# report that there isn't an id_rsa already in gas. This causes a new key to overwrite automatically down the road.
|
11
|
+
# This is for checking if a .gas/rsa file already exists for a nickname which is being registered
|
12
|
+
# If the rsa exists, then we're goona need to ask if we should use it, or if we should delete it
|
13
|
+
#
|
14
|
+
# Returns true to indicate that the user would like to use the rsa file already in .gas ()
|
15
|
+
# Returns false when there is no naming conflicts.
|
16
|
+
def self.user_wants_to_use_key_already_in_gas?
|
17
|
+
if corresponding_rsa_files_exist?
|
18
|
+
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?"
|
19
|
+
puts "Keep current key? [y/n]"
|
20
|
+
|
21
|
+
while true
|
22
|
+
keep_current_file = STDIN.gets.strip
|
23
|
+
|
24
|
+
case keep_current_file
|
25
|
+
|
26
|
+
when "y"
|
27
|
+
return true # keep the files already in .gas, skip making key.
|
28
|
+
when "n"
|
29
|
+
return false
|
30
|
+
else
|
31
|
+
puts "please respond 'y' or 'n'"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
else # no need to do anything if files don't exist
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def self.corresponding_rsa_files_exist?(nickname = '')
|
42
|
+
nickname = @uid if nickname == ''
|
43
|
+
return true if File.exists? "#{GAS_DIRECTORY}/#{nickname}_id_rsa" and File.exists? "#{GAS_DIRECTORY}/#{nickname}_id_rsa.pub"
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Copies a key pair from ~/.ssh to .gas/Nickname*
|
49
|
+
def self.use_current_rsa_files_for_this_user(test = nil)
|
50
|
+
@uid = test unless test.nil?
|
51
|
+
cmd_result = `cp ~/.ssh/id_rsa ~/.gas/#{@uid}_id_rsa`
|
52
|
+
cmd_result = `cp ~/.ssh/id_rsa.pub ~/.gas/#{@uid}_id_rsa.pub`
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def self.ssh_dir_contains_rsa?
|
58
|
+
return true if File.exists?(SSH_DIRECTORY + "/id_rsa") or File.exists?(SSH_DIRECTORY + "/id_rsa.pub")
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
# Checks if the ~/.ssh directroy contains id_rsa and id_rsa.pub
|
65
|
+
# 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.
|
66
|
+
#
|
67
|
+
def self.user_wants_to_use_key_already_in_ssh?
|
68
|
+
return false unless ssh_dir_contains_rsa?
|
69
|
+
|
70
|
+
#puts "Gas has detected that an ~/.ssh/id_rsa file already exists. Would you like to use this as your ssh key to connect with github? Otherwise a new key will be generated and stored in ~/.gas (no overwrite concerns until you 'gas use nickname')"
|
71
|
+
puts "Generate a brand new ssh key pair? (Choose 'n' to use key in ~/.ssh/id_rsa)"
|
72
|
+
puts "Default: 'y'"
|
73
|
+
puts "[Y/n]"
|
74
|
+
|
75
|
+
while true
|
76
|
+
generate_new_rsa = STDIN.gets.strip.downcase
|
77
|
+
case generate_new_rsa
|
78
|
+
when "y", ""
|
79
|
+
return false
|
80
|
+
when "n"
|
81
|
+
return true # return true if we aren't generating a new key
|
82
|
+
else
|
83
|
+
puts "plz answer 'y' or 'n'"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
# Generates a new sshkey putting it in ~/.gas/nickname_id_rsa
|
91
|
+
# This function can get a little tricky. It's best to strip off the comment here,
|
92
|
+
# because github API doesn't retain the comment...
|
93
|
+
def self.generate_new_rsa_keys_in_gas_dir
|
94
|
+
puts "Generating new ssh key..."
|
95
|
+
# TODO: Prompt user if they'd like to use a more secure password if physical security to their computer is not possible (dumb imo)... Unless we do that thing where we store keys on github!
|
96
|
+
|
97
|
+
# Old ssh key method (relies on unix environment)
|
98
|
+
# puts `ssh-keygen -f ~/.gas/#{@uid}_id_rsa -t rsa -C "#{@email}" -N ""` # ssh-keygen style key creation
|
99
|
+
|
100
|
+
# new sshkey gem method...
|
101
|
+
begin
|
102
|
+
k = SSHKey.generate() # (:comment => "#{@email}")
|
103
|
+
|
104
|
+
publ = k.ssh_public_key
|
105
|
+
privl = k.private_key
|
106
|
+
|
107
|
+
my_file_privl = File.open(GAS_DIRECTORY + "/#{@uid}_id_rsa",'w',0700)
|
108
|
+
my_file_privl.write(privl)
|
109
|
+
my_file_privl.close
|
110
|
+
|
111
|
+
my_file_publ = File.open(GAS_DIRECTORY + "/#{@uid}_id_rsa.pub",'w',0700)
|
112
|
+
my_file_publ.write(publ)
|
113
|
+
my_file_publ.close
|
114
|
+
|
115
|
+
return true
|
116
|
+
rescue
|
117
|
+
puts "Fatal Error: Something unexpected happened while writing to #{GAS_DIRECTORY}/#{@uid}_id_rsa"
|
118
|
+
puts "SSH key not saved."
|
119
|
+
return false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
def self.user_wants_gas_to_handle_rsa_keys?
|
126
|
+
puts "Do you want gas to handle switching rsa keys for this user?"
|
127
|
+
puts "[Y/n]"
|
128
|
+
|
129
|
+
while true
|
130
|
+
handle_rsa = STDIN.gets.strip
|
131
|
+
|
132
|
+
case handle_rsa
|
133
|
+
when "y", ""
|
134
|
+
return true
|
135
|
+
when "n"
|
136
|
+
puts
|
137
|
+
# check if ~/.gas/rsa exists, if it does, promt the user
|
138
|
+
if corresponding_rsa_files_exist? #in ~/.gas/
|
139
|
+
|
140
|
+
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)"
|
141
|
+
puts "Just let gas handle ssh key for this user? [y/n]"
|
142
|
+
|
143
|
+
while true
|
144
|
+
keep_file = STDIN.gets.strip
|
145
|
+
|
146
|
+
case keep_file
|
147
|
+
when "n"
|
148
|
+
delete "~/.gas/#{@uid}_id_rsa", "~/.gas/#{@uid}_id_rsa.pub"
|
149
|
+
return false
|
150
|
+
when "y"
|
151
|
+
puts "Excelent! Gas will handle rsa keys for this user."
|
152
|
+
return nil
|
153
|
+
else
|
154
|
+
puts "Please use 'y' or 'n' or enter to choose default."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
return false
|
159
|
+
|
160
|
+
else
|
161
|
+
puts "Please use 'y' or 'n'"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
# This function creates the ssh keys if needed and puts them in ~/.gas/NICKNAME_id_rsa and ...rsa.pub
|
169
|
+
#
|
170
|
+
#
|
171
|
+
def self.setup_ssh_keys(user)
|
172
|
+
@uid = user.nickname
|
173
|
+
@email = user.email
|
174
|
+
|
175
|
+
wants_gas_handling_keys = user_wants_gas_to_handle_rsa_keys?
|
176
|
+
|
177
|
+
if wants_gas_handling_keys
|
178
|
+
puts
|
179
|
+
|
180
|
+
if user_wants_to_use_key_already_in_gas?
|
181
|
+
return true # We don't need to do anything because the .gas directory is already setup
|
182
|
+
elsif user_wants_to_use_key_already_in_ssh? # Check ~/.ssh for a current id_rsa file, if yes, "Do you want to use the current id_rsa file to be used as your key?"
|
183
|
+
use_current_rsa_files_for_this_user # copies the keys from ~/.ssh instead of generating new keys if desired/possible
|
184
|
+
return true
|
185
|
+
else
|
186
|
+
return generate_new_rsa_keys_in_gas_dir
|
187
|
+
end
|
188
|
+
|
189
|
+
elsif wants_gas_handling_keys.nil?
|
190
|
+
return true # if user_wants_gas_to_handle_rsa_keys? returns nill that means the user actually had ssh keys already in .gas, and they would like to use those.
|
191
|
+
else
|
192
|
+
return false # if user doesn't want gas to use ssh keys, that's fine too.
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
# This huge method handles the swapping of id_rsa files on the hdd
|
199
|
+
#
|
200
|
+
def self.swap_in_rsa(nickname)
|
201
|
+
@uid = nickname # woah, this is extremely sloppy I think... in order to use any other class methods,
|
202
|
+
# I need to write to @uid or it will
|
203
|
+
# Have the dumb information from the last time it registered a new git author?
|
204
|
+
|
205
|
+
if Ssh.corresponding_rsa_files_exist?
|
206
|
+
|
207
|
+
if ssh_dir_contains_rsa?
|
208
|
+
if current_key_already_backed_up?
|
209
|
+
write_to_ssh_dir!
|
210
|
+
else
|
211
|
+
puts "~/.ssh/id_rsa already exists. Overwrite?"
|
212
|
+
puts "[y/n]"
|
213
|
+
|
214
|
+
while true
|
215
|
+
overwrite = STDIN.gets.strip
|
216
|
+
case overwrite
|
217
|
+
when "y"
|
218
|
+
write_to_ssh_dir!
|
219
|
+
break
|
220
|
+
when "n"
|
221
|
+
puts "Proceeding without swapping rsa keys."
|
222
|
+
break
|
223
|
+
else
|
224
|
+
puts "please respond 'y' or 'n'"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
else # if no ~/.ssh/id_rsa exists... no overwrite potential
|
231
|
+
write_to_ssh_dir!
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
def self.write_to_ssh_dir!
|
239
|
+
# remove the current key from the ssh-agent session (key will no longer be used with github)
|
240
|
+
system('ssh-add -d ~/.ssh/id_rsa > /dev/null 2>&1') if is_ssh_agent_there?
|
241
|
+
|
242
|
+
FileUtils.cp(GAS_DIRECTORY + "/#{@uid}_id_rsa", SSH_DIRECTORY + "/id_rsa")
|
243
|
+
FileUtils.cp(GAS_DIRECTORY + "/#{@uid}_id_rsa.pub", SSH_DIRECTORY + "/id_rsa.pub")
|
244
|
+
|
245
|
+
FileUtils.chmod(0700, SSH_DIRECTORY + "/id_rsa")
|
246
|
+
FileUtils.chmod(0700, SSH_DIRECTORY + "/id_rsa.pub")
|
247
|
+
|
248
|
+
if is_ssh_agent_there?
|
249
|
+
`ssh-add ~/.ssh/id_rsa > /dev/null 2>&1` # you need to run this command to get the private key to be set to active on unix based machines. Not sure what to do for windows yet...
|
250
|
+
if $?.exitstatus != 0
|
251
|
+
raise "Exit code on ssh-add command line was not zero!"
|
252
|
+
puts "Looks like there may have been a fatal error in registering the rsa key with ssh-agent. Might be worth looking into" if result != true
|
253
|
+
end
|
254
|
+
else
|
255
|
+
puts "Slight Error: The key should now be in ~/.ssh so that's good, BUT ssh-add could not be found. If you're using windows, you'll need to use git bash or cygwin to emulate this unix command and actually do uploads."
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
# This function scans each file in a directory to check to see if it is the same file which it's being compared against
|
261
|
+
# dir_to_scan The target directory you'd like to scan
|
262
|
+
# file_to_compare The file's path that you're expecting to find
|
263
|
+
def self.scan_for_file_match(file_to_compare, dir_to_scan)
|
264
|
+
require 'digest/md5'
|
265
|
+
|
266
|
+
pattern = get_md5_hash(file_to_compare)
|
267
|
+
|
268
|
+
@files = Dir.glob(dir_to_scan + "/*" + file_to_compare.split(//).last(1).to_s)
|
269
|
+
|
270
|
+
@files.each do |file|
|
271
|
+
return true if get_md5_hash(file) == pattern
|
272
|
+
end
|
273
|
+
|
274
|
+
return false
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
def self.current_key_already_backed_up?
|
279
|
+
if scan_for_file_match(SSH_DIRECTORY + "/id_rsa", GAS_DIRECTORY) and scan_for_file_match(SSH_DIRECTORY + "/id_rsa.pub", GAS_DIRECTORY)
|
280
|
+
return true
|
281
|
+
else
|
282
|
+
return false
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.get_md5_hash(file_path)
|
287
|
+
if File.exists? file_path
|
288
|
+
return Digest::MD5.hexdigest(File.open(file_path, "rb").read)
|
289
|
+
end
|
290
|
+
return nil
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
def self.user_wants_to_install_key_to_github?
|
295
|
+
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)"
|
296
|
+
puts "[Y/n]"
|
297
|
+
|
298
|
+
while true
|
299
|
+
upload_key = STDIN.gets.strip.downcase
|
300
|
+
case upload_key
|
301
|
+
when "y", ""
|
302
|
+
return true
|
303
|
+
when "n"
|
304
|
+
return false
|
305
|
+
else
|
306
|
+
puts "Plz respond 'y' or 'n'"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
def self.upload_public_key_to_github(user)
|
313
|
+
@uid = user.nickname
|
314
|
+
|
315
|
+
if user_wants_to_install_key_to_github?
|
316
|
+
key_installation_routine!
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
def self.key_installation_routine!(user = nil, rsa_test = nil)
|
322
|
+
@uid = user.nickname unless user.nil? # allows for easy testing
|
323
|
+
|
324
|
+
rsa_key = get_associated_rsa_key(@uid)
|
325
|
+
rsa_key = rsa_test unless rsa_test.nil?
|
326
|
+
return false if rsa_key.nil?
|
327
|
+
|
328
|
+
# TODO: Impliment a key ring system where you store your key on your github in a repository, only it's encrypted. And to decrypt it, there is
|
329
|
+
# A file in your .gas folder!!! That sounds SO fun!
|
330
|
+
credentials = get_username_and_password_diligently
|
331
|
+
|
332
|
+
if !credentials
|
333
|
+
puts "Invalid credentials. Skipping upload of keys to github. "
|
334
|
+
puts "To try again, type $ gas ssh #{@uid}"
|
335
|
+
return false
|
336
|
+
end
|
337
|
+
|
338
|
+
result = post_key!(credentials, @uid, rsa_key)
|
339
|
+
if result
|
340
|
+
puts "Key uploaded successfully!"
|
341
|
+
return true
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
# Get's the ~/.gas/user_id_rsa associated with the specified user and returns it as a string
|
347
|
+
def self.get_associated_rsa_key(nickname)
|
348
|
+
file_path = "#{GAS_DIRECTORY}/#{nickname}_id_rsa.pub"
|
349
|
+
|
350
|
+
if File.exists? file_path
|
351
|
+
rsa = File.open(file_path, "rb").read.strip
|
352
|
+
if rsa.count(' ') == 2 # special trick to split off the trailing comment text because github API won't store it.
|
353
|
+
rsa = rsa.split(" ")
|
354
|
+
rsa = "#{rsa[0]} #{rsa[1]}"
|
355
|
+
end
|
356
|
+
|
357
|
+
return rsa
|
358
|
+
end
|
359
|
+
return nil
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
def self.get_username_and_password_and_authenticate
|
364
|
+
puts "Type your github.com user name:"
|
365
|
+
print "User: "
|
366
|
+
username = STDIN.gets.strip
|
367
|
+
|
368
|
+
puts "Type your github password:"
|
369
|
+
password = ask("Password: ") { |q| q.echo = false }
|
370
|
+
puts
|
371
|
+
|
372
|
+
credentials = {:username => username, :password => password}
|
373
|
+
|
374
|
+
if valid_github_username_and_pass?(credentials[:username], credentials[:password])
|
375
|
+
return credentials
|
376
|
+
else
|
377
|
+
return false
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Get's the username and password from the user, then authenticates. If it fails, it asks them if they'd like to try again.
|
382
|
+
# Returns false if aborted
|
383
|
+
def self.get_username_and_password_diligently
|
384
|
+
while true
|
385
|
+
credentials = get_username_and_password_and_authenticate
|
386
|
+
if !credentials
|
387
|
+
puts "Could not authenticate, try again?"
|
388
|
+
puts "y/n"
|
389
|
+
|
390
|
+
again = STDIN.gets.strip
|
391
|
+
case again.downcase
|
392
|
+
when "y"
|
393
|
+
when "n"
|
394
|
+
return false
|
395
|
+
end
|
396
|
+
else
|
397
|
+
return credentials
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
def self.valid_github_username_and_pass?(username, password)
|
404
|
+
path = '/user'
|
405
|
+
|
406
|
+
http = Net::HTTP.new(GITHUB_SERVER,443)
|
407
|
+
http.use_ssl = true
|
408
|
+
|
409
|
+
req = Net::HTTP::Get.new(path)
|
410
|
+
req.basic_auth username, password
|
411
|
+
response = http.request(req)
|
412
|
+
|
413
|
+
result = JSON.parse(response.body)["message"]
|
414
|
+
|
415
|
+
return false if result == "Bad credentials"
|
416
|
+
return true
|
417
|
+
end
|
418
|
+
|
419
|
+
|
420
|
+
def self.post_key!(credentials, nickname, rsa)
|
421
|
+
puts "Posting key to GitHub.com..."
|
422
|
+
rsa_key = rsa
|
423
|
+
username = credentials[:username]
|
424
|
+
password = credentials[:password]
|
425
|
+
|
426
|
+
|
427
|
+
# find key...
|
428
|
+
if has_key(username, password, rsa_key)
|
429
|
+
puts "Key already installed."
|
430
|
+
return false
|
431
|
+
end
|
432
|
+
title = "GAS: #{nickname}"
|
433
|
+
install_key(username, password, title, rsa_key)
|
434
|
+
end
|
435
|
+
|
436
|
+
|
437
|
+
def self.remove_key_by_id!(username, password, id)
|
438
|
+
server = 'api.github.com'
|
439
|
+
path = "/user/keys/#{id}"
|
440
|
+
|
441
|
+
|
442
|
+
http = Net::HTTP.new(server,443)
|
443
|
+
http.use_ssl = true
|
444
|
+
req = Net::HTTP::Delete.new(path)
|
445
|
+
req.basic_auth username, password
|
446
|
+
|
447
|
+
response = http.request(req)
|
448
|
+
|
449
|
+
return true if response.body.nil?
|
450
|
+
end
|
451
|
+
|
452
|
+
# Cycles through github, looking to see if rsa exists as a public key, then deletes it if it does
|
453
|
+
def self.has_key(username, password, rsa)
|
454
|
+
# get all keys
|
455
|
+
keys = get_keys(username, password)
|
456
|
+
# loop through arrays checking against 'key'
|
457
|
+
keys.each do |key|
|
458
|
+
if key["key"] == rsa
|
459
|
+
return true
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
return false # key not found
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
# Cycles through github, looking to see if rsa exists as a public key, then deletes it if it does
|
468
|
+
def self.remove_key!(username, password, rsa)
|
469
|
+
# get all keys
|
470
|
+
keys = get_keys(username, password)
|
471
|
+
# loop through arrays checking against 'key'
|
472
|
+
keys.each do |key|
|
473
|
+
if key["key"] == rsa
|
474
|
+
return remove_key_by_id!(username, password, key["id"])
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
return false # key not found
|
479
|
+
end
|
480
|
+
|
481
|
+
def self.get_keys(username, password)
|
482
|
+
server = 'api.github.com'
|
483
|
+
path = '/user/keys'
|
484
|
+
|
485
|
+
|
486
|
+
http = Net::HTTP.new(server,443)
|
487
|
+
req = Net::HTTP::Get.new(path)
|
488
|
+
http.use_ssl = true
|
489
|
+
req.basic_auth username, password
|
490
|
+
response = http.request(req)
|
491
|
+
|
492
|
+
return JSON.parse(response.body)
|
493
|
+
end
|
494
|
+
|
495
|
+
|
496
|
+
def self.install_key(username, password, title, rsa_key)
|
497
|
+
server = 'api.github.com'
|
498
|
+
path = '/user/keys'
|
499
|
+
|
500
|
+
http = Net::HTTP.new(server, 443) # 443 for ssl
|
501
|
+
http.use_ssl = true
|
502
|
+
|
503
|
+
req = Net::HTTP::Post.new(path)
|
504
|
+
req.basic_auth username, password
|
505
|
+
req.body = "{\"title\":\"#{title}\", \"key\":\"#{rsa_key}\"}"
|
506
|
+
|
507
|
+
response = http.start {|http| http.request(req) }
|
508
|
+
the_code = response.code
|
509
|
+
|
510
|
+
keys_end = get_keys(username, password).length
|
511
|
+
|
512
|
+
return true if the_code == "201"
|
513
|
+
|
514
|
+
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"
|
515
|
+
|
516
|
+
# currently.. I think it always returns "already_exists" even if successful. API bug.
|
517
|
+
puts "Something may have gone wrong. Either github fixed their API, or your key couldn't be installed." if the_code != "already_exists"
|
518
|
+
|
519
|
+
#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
|
520
|
+
puts "Server Response: #{response.body}"
|
521
|
+
return false
|
522
|
+
end
|
523
|
+
|
524
|
+
|
525
|
+
# Cross-platform way of finding an executable in the $PATH.
|
526
|
+
# returns nil if command not present
|
527
|
+
#
|
528
|
+
# which('ruby') #=> /usr/bin/ruby
|
529
|
+
def self.which(cmd)
|
530
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
531
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
532
|
+
exts.each { |ext|
|
533
|
+
exe = "#{path}/#{cmd}#{ext}"
|
534
|
+
return exe if File.executable? exe
|
535
|
+
}
|
536
|
+
end
|
537
|
+
return nil
|
538
|
+
end
|
539
|
+
|
540
|
+
def self.is_ssh_agent_there?
|
541
|
+
return false if which("ssh-add").nil?
|
542
|
+
return true
|
543
|
+
end
|
544
|
+
|
545
|
+
# deletes the ssh keys associated with a user
|
546
|
+
def self.delete(nickname)
|
547
|
+
return false unless user_has_ssh_keys?(nickname) # return if no keys
|
548
|
+
|
549
|
+
case user_wants_to_delete_all_ssh_data?
|
550
|
+
when "a"
|
551
|
+
delete_associated_github_keys!(nickname)
|
552
|
+
delete_associated_local_keys!(nickname)
|
553
|
+
when "l"
|
554
|
+
delete_associated_local_keys!(nickname)
|
555
|
+
when "g"
|
556
|
+
delete_associated_github_keys!(nickname)
|
557
|
+
when "n"
|
558
|
+
return false
|
559
|
+
end
|
560
|
+
|
561
|
+
end
|
562
|
+
|
563
|
+
def self.user_has_ssh_keys?(nickname)
|
564
|
+
return false if get_associated_rsa_key(nickname).nil?
|
565
|
+
return true
|
566
|
+
end
|
567
|
+
|
568
|
+
|
569
|
+
def self.delete_associated_github_keys!(nickname)
|
570
|
+
rsa = get_associated_rsa_key(nickname)
|
571
|
+
credentials = get_username_and_password_diligently
|
572
|
+
if !credentials
|
573
|
+
return false
|
574
|
+
end
|
575
|
+
result = remove_key!(credentials[:username], credentials[:password], rsa)
|
576
|
+
puts "The key for this user was not in the specified github account's public keys section." if !result
|
577
|
+
end
|
578
|
+
|
579
|
+
|
580
|
+
def self.delete_associated_local_keys!(nickname)
|
581
|
+
puts "Removing associated keys from local machine..."
|
582
|
+
puts
|
583
|
+
|
584
|
+
ssh_file = get_md5_hash("#{SSH_DIRECTORY}/id_rsa")
|
585
|
+
gas_file = get_md5_hash("#{GAS_DIRECTORY}/#{nickname}_id_rsa")
|
586
|
+
|
587
|
+
return false if gas_file.nil? # if the gas file doesn't exist, return from this function safely, otherwise both objects could be nil, pass this check, and then fuck up our interpreter with file not found errors
|
588
|
+
|
589
|
+
if ssh_file == gas_file
|
590
|
+
File.delete("#{SSH_DIRECTORY}/id_rsa")
|
591
|
+
File.delete("#{SSH_DIRECTORY}/id_rsa.pub")
|
592
|
+
end
|
593
|
+
|
594
|
+
File.delete("#{GAS_DIRECTORY}/#{nickname}_id_rsa")
|
595
|
+
File.delete("#{GAS_DIRECTORY}/#{nickname}_id_rsa.pub")
|
596
|
+
end
|
597
|
+
|
598
|
+
# This is another prompt function, but it returns a more complicated lexicon
|
599
|
+
#
|
600
|
+
# returns "a", "l", "g", or "n"
|
601
|
+
def self.user_wants_to_delete_all_ssh_data?
|
602
|
+
puts "Would you like to remove all ssh keys too!?! (github account keys can be removed as well!)"
|
603
|
+
puts "a: All, the local copy, and checks github too."
|
604
|
+
puts "l: Remove local keys only."
|
605
|
+
puts "g: Removes keys from github.com only."
|
606
|
+
puts "n: Don't remove this users keys."
|
607
|
+
puts "Default: l"
|
608
|
+
|
609
|
+
while true
|
610
|
+
delete_all_keys = STDIN.gets.strip
|
611
|
+
|
612
|
+
case delete_all_keys.downcase
|
613
|
+
when "a"
|
614
|
+
return "a"
|
615
|
+
when "l", ""
|
616
|
+
return "l"
|
617
|
+
when "g"
|
618
|
+
return "g"
|
619
|
+
when "n"
|
620
|
+
return "n"
|
621
|
+
else
|
622
|
+
puts "please use 'a', 'l', 'g' or 'n' for NONE."
|
623
|
+
end
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
|