simple_gas 0.1.9

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.
@@ -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
data/lib/gas/ssh.rb ADDED
@@ -0,0 +1,305 @@
1
+ module Gas
2
+ class Ssh
3
+ require 'highline/import'
4
+ require 'net/https'
5
+ require 'json'
6
+ require 'digest/md5'
7
+ extend Prompter
8
+
9
+ def self.corresponding_rsa_files_exist?(nickname = '')
10
+ nickname = @uid if nickname == ''
11
+ return true if File.exists? "#{GAS_DIRECTORY}/#{nickname}_id_rsa" and File.exists? "#{GAS_DIRECTORY}/#{nickname}_id_rsa.pub"
12
+ false
13
+ end
14
+
15
+ # Copies a key pair from ~/.ssh to .gas/Nickname*
16
+ def self.use_current_rsa_files_for_this_user(test = nil)
17
+ @uid = test unless test.nil?
18
+ FileUtils.cp("#{SSH_DIRECTORY}/id_rsa", "#{GAS_DIRECTORY}/#{@uid}_id_rsa")
19
+ FileUtils.cp("#{SSH_DIRECTORY}/id_rsa.pub", "#{GAS_DIRECTORY}/#{@uid}_id_rsa.pub")
20
+ FileUtils.chmod 0700, "#{GAS_DIRECTORY}/#{@uid}_id_rsa"
21
+ FileUtils.chmod 0700, "#{GAS_DIRECTORY}/#{@uid}_id_rsa.pub"
22
+ return true
23
+ end
24
+
25
+ def self.ssh_dir_contains_rsa?
26
+ return true if File.exists?(SSH_DIRECTORY + "/id_rsa") or File.exists?(SSH_DIRECTORY + "/id_rsa.pub")
27
+ return false
28
+ end
29
+
30
+ # Generates a new sshkey putting it in ~/.gas/nickname_id_rsa
31
+ # This function can get a little tricky. It's best to strip off the comment here,
32
+ # because github API doesn't retain the comment...
33
+ def self.generate_new_rsa_keys_in_gas_dir
34
+ puts "Generating new ssh key..."
35
+
36
+ begin
37
+ k = SSHKey.generate() # (:comment => "#{@email}")
38
+
39
+ publ = k.ssh_public_key
40
+ privl = k.private_key
41
+
42
+ my_file_privl = File.open(GAS_DIRECTORY + "/#{@uid}_id_rsa",'w',0700)
43
+ my_file_privl.write(privl)
44
+ my_file_privl.close
45
+
46
+ my_file_publ = File.open(GAS_DIRECTORY + "/#{@uid}_id_rsa.pub",'w',0700)
47
+ my_file_publ.write(publ)
48
+ my_file_publ.close
49
+
50
+ return true
51
+ rescue
52
+ puts "Fatal Error: Something unexpected happened while writing to #{GAS_DIRECTORY}/#{@uid}_id_rsa"
53
+ puts "SSH key not saved."
54
+ return false
55
+ end
56
+ end
57
+
58
+ # This function creates the ssh keys if needed and puts them in ~/.gas/NICKNAME_id_rsa and ...rsa.pub
59
+ def self.setup_ssh_keys(user)
60
+ @uid = user.nickname
61
+ @email = user.email
62
+
63
+ if Gas::Prompter.user_wants_gas_to_handle_rsa_keys?
64
+ if corresponding_rsa_files_exist?(@uid) and Gas::Prompter.user_wants_to_use_key_already_in_gas?(@uid)
65
+ return true # We don't need to do anything because the .gas directory is already setup
66
+ elsif !corresponding_rsa_files_exist?(@uid) and ssh_dir_contains_rsa? and Gas::Prompter.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?"
67
+ use_current_rsa_files_for_this_user # copies the keys from ~/.ssh instead of generating new keys if desired/possible
68
+ return true
69
+ else
70
+ return generate_new_rsa_keys_in_gas_dir
71
+ end
72
+
73
+ else # !Gas::Prompter.user_wants_gas_to_handle_rsa_keys?
74
+ # check if ~/.gas/rsa exists, if it does, promt the user
75
+ # because that key must be destroyed (if the user really doesn't want gas handling keys for this user)
76
+ if corresponding_rsa_files_exist?(@uid) #in ~/.gas/
77
+ delete_associated_local_keys!(@uid) if Gas::Prompter.user_wants_to_remove_the_keys_that_already_exist_for_this_user?(@uid)
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ # This huge method handles the swapping of id_rsa files on the hdd
84
+ def self.swap_in_rsa(nickname)
85
+ @uid = nickname # woah, this is extremely sloppy I think... in order to use any other class methods,
86
+ # I need to write to @uid or it will
87
+ # Have the dumb information from the last time it registered a new git author?
88
+
89
+ if corresponding_rsa_files_exist?
90
+ if ssh_dir_contains_rsa?
91
+ if current_key_already_backed_up?
92
+ write_to_ssh_dir!
93
+ else
94
+ if Gas::Prompter.user_wants_to_overwrite_existing_rsa_key?
95
+ write_to_ssh_dir!
96
+ else
97
+ puts "Proceeding without swapping rsa keys (aborting)."
98
+ end
99
+ end
100
+ else # if no ~/.ssh/id_rsa exists... no overwrite potential... so just write away
101
+ write_to_ssh_dir!
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+ def self.write_to_ssh_dir!
108
+ supress_process_output = "> /dev/null 2>&1"
109
+ supress_process_output = "> NUL" if IS_WINDOWS
110
+
111
+ # remove the current key from the ssh-agent session (key will no longer be used with github)
112
+ system("ssh-add -d #{SSH_DIRECTORY}/id_rsa #{supress_process_output}") if is_ssh_agent_there?
113
+
114
+ FileUtils.cp(GAS_DIRECTORY + "/#{@uid}_id_rsa", SSH_DIRECTORY + "/id_rsa")
115
+ FileUtils.cp(GAS_DIRECTORY + "/#{@uid}_id_rsa.pub", SSH_DIRECTORY + "/id_rsa.pub")
116
+
117
+ FileUtils.chmod(0700, SSH_DIRECTORY + "/id_rsa")
118
+ FileUtils.chmod(0700, SSH_DIRECTORY + "/id_rsa.pub")
119
+
120
+ if is_ssh_agent_there?
121
+ `ssh-add #{SSH_DIRECTORY}/id_rsa #{supress_process_output}` # 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...
122
+
123
+ if $?.exitstatus == 1 # exit status 1 means failed
124
+ puts "Looks like there may have been a fatal error in registering the rsa key with ssh-agent. Might be worth looking into"
125
+ raise "Exit code on ssh-add command line was one meaning: Error!"
126
+ end
127
+
128
+ else
129
+ 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."
130
+ end
131
+
132
+ end
133
+
134
+ # This function scans each file in a directory to check
135
+ # to see if it is the same file which it's being compared against
136
+ # dir_to_scan The target directory you'd like to scan
137
+ # file_to_compare The file's path that you're expecting to find
138
+ def self.scan_for_file_match(file_to_compare, dir_to_scan)
139
+ pattern = get_md5_hash(file_to_compare)
140
+
141
+ @files = Dir.glob(dir_to_scan + "/*" + file_to_compare.split(//).last(1).to_s)
142
+
143
+ @files.each do |file|
144
+ return true if get_md5_hash(file) == pattern
145
+ end
146
+
147
+ return false
148
+ end
149
+
150
+ def self.current_key_already_backed_up?
151
+ if scan_for_file_match(SSH_DIRECTORY + "/id_rsa", GAS_DIRECTORY) and scan_for_file_match(SSH_DIRECTORY + "/id_rsa.pub", GAS_DIRECTORY)
152
+ return true
153
+ else
154
+ return false
155
+ end
156
+ end
157
+
158
+ def self.get_md5_hash(file_path)
159
+ if File.exists? file_path
160
+ file = File.open(file_path, "rb")
161
+ hash = Digest::MD5.hexdigest(file.read)
162
+ file.close
163
+ return hash
164
+ end
165
+ return nil
166
+ end
167
+
168
+
169
+ def self.upload_public_key_to_github(user, github_speaker = nil)
170
+ if Gas::Prompter.user_wants_to_install_key_to_github?
171
+ key_installation_routine!(user, nil, github_speaker)
172
+ end
173
+ end
174
+
175
+
176
+ def self.key_installation_routine!(user = nil, rsa_test = nil, github_speaker = nil)
177
+ @uid = user.nickname unless user.nil? # allows for easy testing
178
+
179
+ rsa_key = get_associated_rsa_key(@uid).first
180
+ rsa_key = rsa_test unless rsa_test.nil?
181
+ return false if rsa_key.nil?
182
+
183
+ # 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
184
+ # A file in your .gas folder!!! That sounds SO fun!
185
+ github_speaker = GithubSpeaker.new(user) if github_speaker.nil?
186
+
187
+ puts github_speaker.status
188
+
189
+ if github_speaker.status == :bad_credentials
190
+ puts "Invalid credentials. Skipping upload of keys to github. "
191
+ puts "To try again, type $ gas ssh #{@uid}"
192
+ return false
193
+ end
194
+
195
+ result = github_speaker.post_key!(rsa_key)
196
+
197
+ if result
198
+ puts "Key uploaded successfully!"
199
+ return true
200
+ end
201
+ end
202
+
203
+ # Get's the ~/.gas/user_id_rsa and ~/.gas/user_id_rsa.pub strings associated with
204
+ # the specified user and returns it as an array. Returns array with two nils if there's no keys
205
+ # [pub_rsa, priv_rsa]
206
+ def self.get_associated_rsa_key(nickname)
207
+ pub_path = "#{GAS_DIRECTORY}/#{nickname}_id_rsa.pub"
208
+ priv_path = "#{GAS_DIRECTORY}/#{nickname}_id_rsa"
209
+
210
+ if File.exists? pub_path and File.exists? priv_path
211
+ pub_file = File.open(pub_path, "rb")
212
+ pub_rsa = pub_file.read.strip
213
+ pub_file.close
214
+ if pub_rsa.count(' ') == 2 # special trick to split off the trailing comment text because github API won't store it.
215
+ pub_rsa = pub_rsa.split(" ")
216
+ pub_rsa = "#{rsa[0]} #{rsa[1]}"
217
+ end
218
+
219
+ priv_file = File.open(priv_path, "rb")
220
+ priv_rsa = priv_file.read.strip
221
+ priv_file.close
222
+
223
+ return [pub_rsa, priv_rsa]
224
+ end
225
+ return [nil, nil]
226
+ end
227
+
228
+ # Cross-platform way of finding an executable in the $PATH.
229
+ # returns nil if command not present
230
+ #
231
+ # which('ruby') #=> /usr/bin/ruby
232
+ def self.which(cmd)
233
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
234
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
235
+ exts.each { |ext|
236
+ exe = "#{path}/#{cmd}#{ext}"
237
+ return exe if File.executable? exe
238
+ }
239
+ end
240
+ return nil
241
+ end
242
+
243
+ def self.is_ssh_agent_there?
244
+ return false if which("ssh-add").nil?
245
+ return true
246
+ end
247
+
248
+ # deletes the ssh keys associated with a user
249
+ def self.delete(nickname)
250
+ return false unless user_has_ssh_keys?(nickname) # return if no keys
251
+
252
+ case Gas::Prompter.user_wants_to_delete_all_ssh_data?
253
+ when "a"
254
+ delete_associated_github_keys!(nickname)
255
+ delete_associated_local_keys!(nickname)
256
+ when "l"
257
+ delete_associated_local_keys!(nickname)
258
+ when "g"
259
+ delete_associated_github_keys!(nickname)
260
+ when "n"
261
+ return false
262
+ end
263
+
264
+ end
265
+
266
+ def self.user_has_ssh_keys?(nickname)
267
+ return false if get_associated_rsa_key(nickname).first.nil?
268
+ return true
269
+ end
270
+
271
+ def self.delete_associated_github_keys!(nickname)
272
+ rsa = get_associated_rsa_key(nickname).first
273
+
274
+ credentials = get_nils
275
+
276
+ github_speaker = GithubSpeaker.new(nickname, credentials[:username], credentials[:password])
277
+
278
+ result = github_speaker.remove_key! rsa
279
+ puts "The key for this user was not in the specified github account's public keys section." if !result
280
+ end
281
+
282
+ # this is just for testing... it gets stubbed... otherwise, the nils are normal and allow for
283
+ # normal prompting for username and password from within the GithubSpeaker class
284
+ def self.get_nils; { :username => nil, :password => nil };end
285
+
286
+ def self.delete_associated_local_keys!(nickname)
287
+ puts "Removing associated keys from local machine..."
288
+ puts
289
+
290
+ ssh_file = get_md5_hash("#{SSH_DIRECTORY}/id_rsa")
291
+ gas_file = get_md5_hash("#{GAS_DIRECTORY}/#{nickname}_id_rsa")
292
+
293
+ 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
294
+
295
+ if ssh_file == gas_file
296
+ File.delete("#{SSH_DIRECTORY}/id_rsa")
297
+ File.delete("#{SSH_DIRECTORY}/id_rsa.pub")
298
+ end
299
+
300
+ File.delete("#{GAS_DIRECTORY}/#{nickname}_id_rsa")
301
+ File.delete("#{GAS_DIRECTORY}/#{nickname}_id_rsa.pub")
302
+ end
303
+
304
+ end
305
+ end
data/lib/gas/user.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Gas
2
+
3
+ # Class that contains data for a git user
4
+ class User
5
+ attr_reader :name, :email, :nickname
6
+
7
+
8
+ # @param [String] name The name of the user
9
+ # @param [String] email The email of the user
10
+ # @param [String] nickname A nickname for the user, not used when parsing from gitconfig
11
+ def initialize(name, email, nickname = '')
12
+ @name = name
13
+ @email = email
14
+ @nickname = nickname
15
+
16
+ end
17
+
18
+ # Returns the git format of user
19
+ # @return [String]
20
+ def git_user
21
+ to_s false
22
+ end
23
+
24
+ # Overides to_s to output in the correct format
25
+ # @param [Boolean] use_nickname Defaults to true
26
+ # @return [String]
27
+ def to_s(use_nickname = true)
28
+ " [#{use_nickname ? @nickname : 'user'}]\n name = #{@name}\n email = #{@email}"
29
+ end
30
+
31
+
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module Gas
2
+
3
+ VERSION = '0.1.9'
4
+
5
+ end
6
+
data/lib/gas.rb ADDED
@@ -0,0 +1,171 @@
1
+ require 'rbconfig'
2
+
3
+ GAS_DIRECTORY = "#{ENV['HOME']}/.gas" # File.expand_path('~/.gas')
4
+ SSH_DIRECTORY = "#{ENV['HOME']}/.ssh" # File.expand_path('~/.ssh')
5
+ GITHUB_SERVER = 'api.github.com'
6
+ IS_WINDOWS = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
7
+
8
+
9
+ require 'sshkey' #external
10
+
11
+ require 'gas/version'
12
+ require 'gas/prompter'
13
+ require 'gas/ssh'
14
+ require 'gas/user'
15
+ require 'gas/config'
16
+ require 'gas/gitconfig'
17
+ require 'gas/settings'
18
+ require 'gas/github_speaker'
19
+
20
+
21
+ module Gas
22
+
23
+ @config = Config.new
24
+ @gitconfig = Gitconfig.new
25
+
26
+ # Lists all authors
27
+ def self.list
28
+ puts
29
+ puts 'Available users:'
30
+ puts
31
+ puts @config
32
+ puts
33
+ end
34
+
35
+ # Shows the current user
36
+ def self.show
37
+ user = @gitconfig.current_user
38
+
39
+ if user
40
+ puts 'Current user:'
41
+ puts "#{user.name} <#{user.email}>"
42
+ else
43
+ puts 'No current user in gitconfig'
44
+ end
45
+ end
46
+
47
+ # Sets _nickname_ as current user
48
+ # @param [String] nickname The nickname to use
49
+ def self.use(nickname)
50
+ return false unless self.no_user?(nickname)
51
+ user = @config[nickname]
52
+
53
+ @gitconfig.change_user user # daring change made here! Heads up Walle
54
+
55
+ self.show
56
+ end
57
+
58
+ # Adds a author to the config
59
+ # @param [String] nickname The nickname of the author
60
+ # @param [String] name The name of the author
61
+ # @param [String] email The email of the author
62
+ def self.add(nickname, name, email, github_speaker = nil)
63
+ return false if self.has_user?(nickname)
64
+ user = User.new name, email, nickname
65
+ @config.add user
66
+ @config.save!
67
+
68
+ using_ssh = Ssh.setup_ssh_keys user
69
+
70
+ Ssh.upload_public_key_to_github(user, github_speaker) if using_ssh
71
+
72
+ puts 'Added new author'
73
+ puts user
74
+ end
75
+
76
+
77
+ # Adds an ssh key for the specified user
78
+ def self.ssh(nickname)
79
+ if nickname.nil?
80
+ puts "Oh, so you'd like an elaborate explanation on how ssh key juggling works? Well pull up a chair!"
81
+ puts
82
+ 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)."
83
+ puts "You will be prompted if you would like to handle SSH keys when you create a new user."
84
+ puts "If you are a long time user of gas, you can add ssh to an author by the command..."
85
+ puts "\$ gas ssh NICKNAME"
86
+ puts
87
+ puts "Your ssh keys will be stored in ~/.gas/NICKNAME_id_rsa and automatically copied to ~/.ssh/id_rsa when you use the command..."
88
+ puts "\$ gas use NICKNAME"
89
+ 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?)"
90
+ puts
91
+ 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)"
92
+ puts
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 =("
94
+ else
95
+ user = @config[nickname]
96
+
97
+
98
+ # Prompt Remake this user's ssh keys?
99
+
100
+ # check for ssh keys
101
+ if !Ssh.corresponding_rsa_files_exist?(nickname)
102
+ Ssh.setup_ssh_keys user
103
+ Ssh.upload_public_key_to_github user
104
+ else
105
+ Ssh.setup_ssh_keys user
106
+ Ssh.upload_public_key_to_github user
107
+ end
108
+ end
109
+ end
110
+
111
+
112
+ # Imports current user from .gitconfig to .gas
113
+ # @param [String] nickname The nickname to give to the new user
114
+ def self.import(nickname)
115
+ return false if self.has_user?(nickname)
116
+ user = @gitconfig.current_user
117
+
118
+ if user
119
+ user = User.new user.name, user.email, nickname
120
+
121
+ @config.add user
122
+ @config.save!
123
+
124
+ puts 'Added author'
125
+ puts user
126
+ else
127
+ puts 'No current user to import'
128
+ end
129
+ end
130
+
131
+ # Deletes an author from the config using nickname
132
+ # @param [String] nickname The nickname of the author
133
+ def self.delete(nickname)
134
+
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
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
+
139
+ @config.delete nickname
140
+ @config.save!
141
+
142
+ puts "Deleted author #{nickname}"
143
+ return true
144
+ end
145
+
146
+ # Prints the current version
147
+ def self.version
148
+ puts Gas::VERSION
149
+ end
150
+
151
+ # Checks if the user exists and gives error and exit if not
152
+ # @param [String] nickname
153
+ def self.no_user?(nickname)
154
+ if !@config.exists? nickname
155
+ puts "Nickname #{nickname} does not exist"
156
+ return false
157
+ end
158
+ return true
159
+ end
160
+
161
+ # Checks if the user exists and gives error and exit if so
162
+ # @param [String] nickname
163
+ def self.has_user?(nickname)
164
+ if @config.exists? nickname
165
+ puts "Nickname #{nickname} already exists"
166
+ return true
167
+ end
168
+ return false
169
+ end
170
+
171
+ end