summaryrefslogtreecommitdiff
path: root/lib/rubygems/gemcutter_utilities.rb
blob: 98e78fe341541f344892696074f65b3e259acf87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# frozen_string_literal: false
require 'rubygems/remote_fetcher'

##
# Utility methods for using the RubyGems API.

module Gem::GemcutterUtilities

  # TODO: move to Gem::Command
  OptionParser.accept Symbol do |value|
    value.to_sym
  end

  attr_writer :host

  ##
  # Add the --key option

  def add_key_option
    add_option('-k', '--key KEYNAME', Symbol,
               'Use the given API key',
               'from ~/.gem/credentials') do |value,options|
      options[:key] = value
    end
  end

  ##
  # The API key from the command options or from the user's configuration.

  def api_key
    if options[:key] then
      verify_api_key options[:key]
    elsif Gem.configuration.api_keys.key?(host)
      Gem.configuration.api_keys[host]
    else
      Gem.configuration.rubygems_api_key
    end
  end

  ##
  # The host to connect to either from the RUBYGEMS_HOST environment variable
  # or from the user's configuration

  def host
    configured_host = Gem.host unless
      Gem.configuration.disable_default_gem_server

    @host ||=
      begin
        env_rubygems_host = ENV['RUBYGEMS_HOST']
        env_rubygems_host = nil if
          env_rubygems_host and env_rubygems_host.empty?

        env_rubygems_host|| configured_host
      end
  end

  ##
  # Creates an RubyGems API to +host+ and +path+ with the given HTTP +method+.
  #
  # If +allowed_push_host+ metadata is present, then it will only allow that host.

  def rubygems_api_request(method, path, host = nil, allowed_push_host = nil, &block)
    require 'net/http'

    self.host = host if host
    unless self.host
      alert_error "You must specify a gem server"
      terminate_interaction 1 # TODO: question this
    end

    if allowed_push_host and self.host != allowed_push_host
      alert_error "#{self.host.inspect} is not allowed by the gemspec, which only allows #{allowed_push_host.inspect}"
      terminate_interaction 1
    end

    uri = URI.parse "#{self.host}/#{path}"

    request_method = Net::HTTP.const_get method.to_s.capitalize

    Gem::RemoteFetcher.fetcher.request(uri, request_method, &block)
  end

  ##
  # Signs in with the RubyGems API at +sign_in_host+ and sets the rubygems API
  # key.

  def sign_in sign_in_host = nil
    sign_in_host ||= self.host
    return if api_key

    pretty_host = if Gem::DEFAULT_HOST == sign_in_host then
                    'RubyGems.org'
                  else
                    sign_in_host
                  end

    say "Enter your #{pretty_host} credentials."
    say "Don't have an account yet? " +
        "Create one at #{sign_in_host}/sign_up"

    email    =              ask "   Email: "
    password = ask_for_password "Password: "
    say "\n"

    response = rubygems_api_request(:get, "api/v1/api_key",
                                    sign_in_host) do |request|
      request.basic_auth email, password
    end

    with_response response do |resp|
      say "Signed in."
      Gem.configuration.rubygems_api_key = resp.body
    end
  end

  ##
  # Retrieves the pre-configured API key +key+ or terminates interaction with
  # an error.

  def verify_api_key(key)
    if Gem.configuration.api_keys.key? key then
      Gem.configuration.api_keys[key]
    else
      alert_error "No such API key. Please add it to your configuration (done automatically on initial `gem push`)."
      terminate_interaction 1 # TODO: question this
    end
  end

  ##
  # If +response+ is an HTTP Success (2XX) response, yields the response if a
  # block was given or shows the response body to the user.
  #
  # If the response was not successful, shows an error to the user including
  # the +error_prefix+ and the response body.

  def with_response response, error_prefix = nil
    case response
    when Net::HTTPSuccess then
      if block_given? then
        yield response
      else
        say response.body
      end
    else
      message = response.body
      message = "#{error_prefix}: #{message}" if error_prefix

      say message
      terminate_interaction 1 # TODO: question this
    end
  end

end