Class: Azuki::Auth

Inherits:
Object
  • Object
show all
Extended by:
Helpers
Defined in:
lib/azuki/auth.rb

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from Helpers

action, ask, confirm, confirm_billing, confirm_command, create_git_remote, deprecate, display, display_header, display_object, display_row, display_table, error, error_with_failure, error_with_failure=, extended, extended_into, fail, format_bytes, format_date, format_error, format_with_bang, get_terminal_environment, git, has_git?, home_directory, hprint, hputs, included, included_into, json_decode, json_encode, launchy, line_formatter, longest, output_with_bang, quantify, redisplay, retry_on_exception, run_command, running_on_a_mac?, running_on_windows?, set_buffer, shell, spinner, status, string_distance, styled_array, styled_error, styled_hash, styled_header, suggestion, time_ago, truncate, with_tty

Class Attribute Details

.credentialsObject

Returns the value of attribute credentials.



12
13
14
# File 'lib/azuki/auth.rb', line 12

def credentials
  @credentials
end

Class Method Details

.apiObject



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/azuki/auth.rb', line 14

def api
  @api ||= begin
    require("azuki-api")
    api = Azuki::API.new(default_params.merge(:api_key => password))

    def api.request(params, &block)
      response = super
      if response.headers.has_key?('X-Azuki-Warning')
        Azuki::Command.warnings.concat(response.headers['X-Azuki-Warning'].split("\n"))
      end
      response
    end

    api
  end
end

.api_key(user = get_credentials[0], password = get_credentials[1]) ⇒ Object



81
82
83
84
85
# File 'lib/azuki/auth.rb', line 81

def api_key(user = get_credentials[0], password = get_credentials[1])
  require("azuki-api")
  api = Azuki::API.new(default_params)
  api.(user, password).body["api_key"]
end

.ask_for_and_save_credentialsObject



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/azuki/auth.rb', line 222

def ask_for_and_save_credentials
  require("azuki-api") # for the errors
  begin
    @credentials = ask_for_credentials
    write_credentials
    check
  rescue Azuki::API::Errors::NotFound, Azuki::API::Errors::Unauthorized => e
    delete_credentials
    display "Authentication failed."
    retry if retry_login?
    exit 1
  rescue Exception => e
    delete_credentials
    raise e
  end
  check_for_associated_ssh_key unless Azuki::Command.current_command == "keys:add"
  @credentials
end

.ask_for_credentialsObject



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/azuki/auth.rb', line 184

def ask_for_credentials
  puts "Enter your Azuki credentials."

  print "Email: "
  user = ask

  print "Password (typing will be hidden): "
  password = running_on_windows? ? ask_for_password_on_windows : ask_for_password

  [user, api_key(user, password)]
end

.ask_for_passwordObject



214
215
216
217
218
219
220
# File 'lib/azuki/auth.rb', line 214

def ask_for_password
  echo_off
  password = ask
  puts
  echo_on
  return password
end

.ask_for_password_on_windowsObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/azuki/auth.rb', line 196

def ask_for_password_on_windows
  require "Win32API"
  char = nil
  password = ''

  while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
    break if char == 10 || char == 13 # received carriage return or newline
    if char == 127 || char == 8 # backspace and delete
      password.slice!(-1, 1)
    else
      # windows might throw a -1 at us so make sure to handle RangeError
      (password << char.chr) rescue RangeError
    end
  end
  puts
  return password
end

.associate_key(key) ⇒ Object



291
292
293
294
295
296
297
298
299
# File 'lib/azuki/auth.rb', line 291

def associate_key(key)
  action("Uploading SSH public key #{key}") do
    if File.exists?(key)
      api.post_key(File.read(key))
    else
      error("Could not upload SSH public key: key file '" + key + "' does not exist")
    end
  end
end

.associate_or_generate_ssh_keyObject



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/azuki/auth.rb', line 247

def associate_or_generate_ssh_key
  public_keys = Dir.glob("#{home_directory}/.ssh/*.pub").sort

  case public_keys.length
  when 0 then
    display "Could not find an existing public key."
    display "Would you like to generate one? [Yn] ", false
    unless ask.strip.downcase == "n"
      display "Generating new SSH public key."
      generate_ssh_key("id_rsa")
      associate_key("#{home_directory}/.ssh/id_rsa.pub")
    end
  when 1 then
    display "Found existing public key: #{public_keys.first}"
    associate_key(public_keys.first)
  else
    display "Found the following SSH public keys:"
    public_keys.each_with_index do |key, index|
      display "#{index+1}) #{File.basename(key)}"
    end
    display "Which would you like to use with your Azuki account? ", false
    choice = ask.to_i - 1
    chosen = public_keys[choice]
    if choice == -1 || chosen.nil?
      error("Invalid choice")
    end
    associate_key(chosen)
  end
end

.base_host(host) ⇒ Object



311
312
313
314
315
# File 'lib/azuki/auth.rb', line 311

def base_host(host)
  parts = URI.parse(full_host(host)).host.split(".")
  return parts.first if parts.size == 1
  parts[-2..-1].join(".")
end

.checkObject

just a stub; will raise if not authenticated



49
50
51
# File 'lib/azuki/auth.rb', line 49

def check
  api.get_user
end

.check_for_associated_ssh_keyObject



241
242
243
244
245
# File 'lib/azuki/auth.rb', line 241

def check_for_associated_ssh_key
  if api.get_keys.body.empty?
    associate_or_generate_ssh_key
  end
end

.clientObject



31
32
33
34
35
36
37
# File 'lib/azuki/auth.rb', line 31

def client
  @client ||= begin
    client = Azuki::Client.new(user, password, host)
    client.on_warning { |msg| self.display("\n#{msg}\n\n") }
    client
  end
end

.default_gitObject



57
58
59
# File 'lib/azuki/auth.rb', line 57

def default_git
  "git.azukiapp.com"
end

.default_hostObject



53
54
55
# File 'lib/azuki/auth.rb', line 53

def default_host
  "azukiapp.com"
end

.delete_credentialsObject



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/azuki/auth.rb', line 91

def delete_credentials
  if File.exists?(legacy_credentials_path)
    FileUtils.rm_f(legacy_credentials_path)
  end
  if netrc
    netrc.delete("api.#{host}")
    netrc.delete("code.#{host}")
    netrc.save
  end
  @api, @client, @credentials = nil, nil
end

.echo_offObject



172
173
174
175
176
# File 'lib/azuki/auth.rb', line 172

def echo_off
  with_tty do
    system "stty -echo"
  end
end

.echo_onObject



178
179
180
181
182
# File 'lib/azuki/auth.rb', line 178

def echo_on
  with_tty do
    system "stty echo"
  end
end

.full_host(host) ⇒ Object



317
318
319
# File 'lib/azuki/auth.rb', line 317

def full_host(host)
  (host =~ /^http/) ? host : "https://api.#{host}"
end

.generate_ssh_key(keyfile) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/azuki/auth.rb', line 277

def generate_ssh_key(keyfile)
  ssh_dir = File.join(home_directory, ".ssh")
  unless File.exists?(ssh_dir)
    FileUtils.mkdir_p ssh_dir
    unless running_on_windows?
      File.chmod(0700, ssh_dir)
    end
  end
  output = `ssh-keygen -t rsa -N "" -f \"#{home_directory}/.ssh/#{keyfile}\" 2>&1`
  if ! $?.success?
    error("Could not generate key: #{output}")
  end
end

.get_credentialsObject

:nodoc:



87
88
89
# File 'lib/azuki/auth.rb', line 87

def get_credentials    # :nodoc:
  @credentials ||= (read_credentials || ask_for_and_save_credentials)
end

.git_hostObject



61
62
63
# File 'lib/azuki/auth.rb', line 61

def git_host
  ENV['AZUKI_GIT_HOST'] || default_git
end

.hostObject



65
66
67
# File 'lib/azuki/auth.rb', line 65

def host
  ENV['AZUKI_HOST'] || default_host
end

.legacy_credentials_pathObject



103
104
105
106
107
108
109
# File 'lib/azuki/auth.rb', line 103

def legacy_credentials_path
  if host == default_host
    "#{home_directory}/.azuki/credentials"
  else
    "#{home_directory}/.azuki/credentials.#{CGI.escape(host)}"
  end
end

.loginObject



39
40
41
42
# File 'lib/azuki/auth.rb', line 39

def 
  delete_credentials
  get_credentials
end

.logoutObject



44
45
46
# File 'lib/azuki/auth.rb', line 44

def logout
  delete_credentials
end

.netrcObject

:nodoc:



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/azuki/auth.rb', line 121

def netrc   # :nodoc:
  @netrc ||= begin
    File.exists?(netrc_path) && Netrc.read(netrc_path)
  rescue => error
    if error.message =~ /^Permission bits for/
      perm = File.stat(netrc_path).mode & 0777
      abort("Permissions #{perm} for '#{netrc_path}' are too open. You should run `chmod 0600 #{netrc_path}` so that your credentials are NOT accessible by others.")
    else
      raise error
    end
  end
end

.netrc_pathObject



111
112
113
114
115
116
117
118
119
# File 'lib/azuki/auth.rb', line 111

def netrc_path
  default = Netrc.default_path
  encrypted = default + ".gpg"
  if File.exists?(encrypted)
    encrypted
  else
    default
  end
end

.passwordObject

:nodoc:



77
78
79
# File 'lib/azuki/auth.rb', line 77

def password    # :nodoc:
  get_credentials[1]
end

.read_credentialsObject



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/azuki/auth.rb', line 134

def read_credentials
  if ENV['AZUKI_API_KEY']
    ['', ENV['AZUKI_API_KEY']]
  else
    # convert legacy credentials to netrc
    if File.exists?(legacy_credentials_path)
      @api, @client = nil
      @credentials = File.read(legacy_credentials_path).split("\n")
      write_credentials
      FileUtils.rm_f(legacy_credentials_path)
    end

    # read netrc credentials if they exist
    if netrc
      # force migration of long api tokens (80 chars) to short ones (40)
      # #write_credentials rewrites both api.* and code.*
      credentials = netrc["api.#{host}"]
      if credentials && credentials[1].length > 40
        @credentials = [ credentials[0], credentials[1][0,40] ]
        write_credentials
      end

      netrc["api.#{host}"]
    end
  end
end

.reauthorizeObject



69
70
71
# File 'lib/azuki/auth.rb', line 69

def reauthorize
  @credentials = ask_for_and_save_credentials
end

.retry_login?Boolean

Returns:

  • (Boolean)


301
302
303
304
305
# File 'lib/azuki/auth.rb', line 301

def retry_login?
  @login_attempts ||= 0
  @login_attempts += 1
  @login_attempts < 3
end

.userObject

:nodoc:



73
74
75
# File 'lib/azuki/auth.rb', line 73

def user    # :nodoc:
  get_credentials[0]
end

.verified_hostsObject



307
308
309
# File 'lib/azuki/auth.rb', line 307

def verified_hosts
  %w( azukiapp.com azuki-shadow.com )
end

.verify_host?(host) ⇒ Boolean

Returns:

  • (Boolean)


321
322
323
324
325
326
# File 'lib/azuki/auth.rb', line 321

def verify_host?(host)
  hostname = base_host(host)
  verified = verified_hosts.include?(hostname)
  verified = false if ENV["AZUKI_SSL_VERIFY"] == "disable"
  verified
end

.write_credentialsObject



161
162
163
164
165
166
167
168
169
170
# File 'lib/azuki/auth.rb', line 161

def write_credentials
  FileUtils.mkdir_p(File.dirname(netrc_path))
  FileUtils.touch(netrc_path)
  unless running_on_windows?
    FileUtils.chmod(0600, netrc_path)
  end
  netrc["api.#{host}"] = self.credentials
  netrc["code.#{host}"] = self.credentials
  netrc.save
end