summaryrefslogtreecommitdiff
path: root/lib/gitlab/legacy_github_import/client.rb
blob: 53c910d44bd2ed9135a5f3ded386fc597a610d0e (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
module Gitlab
  module LegacyGithubImport
    class Client
      GITHUB_SAFE_REMAINING_REQUESTS = 100
      GITHUB_SAFE_SLEEP_TIME = 500

      attr_reader :access_token, :host, :api_version

      def initialize(access_token, host: nil, api_version: 'v3')
        @access_token = access_token
        @host = host.to_s.sub(%r{/+\z}, '')
        @api_version = api_version
        @users = {}

        if access_token
          ::Octokit.auto_paginate = false
        end
      end

      def api
        @api ||= ::Octokit::Client.new(
          access_token: access_token,
          api_endpoint: api_endpoint,
          # If there is no config, we're connecting to github.com and we
          # should verify ssl.
          connection_options: {
            ssl: { verify: config ? config['verify_ssl'] : true }
          }
        )
      end

      def client
        unless config
          raise Projects::ImportService::Error,
            'OAuth configuration for GitHub missing.'
        end

        @client ||= ::OAuth2::Client.new(
          config.app_id,
          config.app_secret,
          github_options.merge(ssl: { verify: config['verify_ssl'] })
        )
      end

      def authorize_url(redirect_uri)
        client.auth_code.authorize_url({
          redirect_uri: redirect_uri,
          scope: "repo, user, user:email"
        })
      end

      def get_token(code)
        client.auth_code.get_token(code).token
      end

      def method_missing(method, *args, &block)
        if api.respond_to?(method)
          request(method, *args, &block)
        else
          super(method, *args, &block)
        end
      end

      def respond_to?(method)
        api.respond_to?(method) || super
      end

      def user(login)
        return nil unless login.present?
        return @users[login] if @users.key?(login)

        @users[login] = api.user(login)
      end

      private

      def api_endpoint
        if host.present? && api_version.present?
          "#{host}/api/#{api_version}"
        else
          github_options[:site]
        end
      end

      def config
        Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
      end

      def github_options
        if config
          config["args"]["client_options"].deep_symbolize_keys
        else
          OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
        end
      end

      def rate_limit
        api.rate_limit!
      # GitHub Rate Limit API returns 404 when the rate limit is
      # disabled. In this case we just want to return gracefully
      # instead of spitting out an error.
      rescue Octokit::NotFound
        nil
      end

      def has_rate_limit?
        return @has_rate_limit if defined?(@has_rate_limit)

        @has_rate_limit = rate_limit.present?
      end

      def rate_limit_exceed?
        has_rate_limit? && rate_limit.remaining <= GITHUB_SAFE_REMAINING_REQUESTS
      end

      def rate_limit_sleep_time
        rate_limit.resets_in + GITHUB_SAFE_SLEEP_TIME
      end

      def request(method, *args, &block)
        sleep rate_limit_sleep_time if rate_limit_exceed?

        data = api.__send__(method, *args) # rubocop:disable GitlabSecurity/PublicSend
        return data unless data.is_a?(Array)

        last_response = api.last_response

        if block_given?
          yield data
          # api.last_response could change while we're yielding (e.g. fetching labels for each PR)
          # so we cache our own last response
          each_response_page(last_response, &block)
        else
          each_response_page(last_response) { |page| data.concat(page) }
          data
        end
      end

      def each_response_page(last_response)
        while last_response.rels[:next]
          sleep rate_limit_sleep_time if rate_limit_exceed?
          last_response = last_response.rels[:next].get
          yield last_response.data if last_response.data.is_a?(Array)
        end
      end
    end
  end
end