Commit c4a9454a authored by James Lopez's avatar James Lopez

Merge branch 'georgekoltsov/remove-legacy-github-client-v2' into 'master'

Replace legacy github client in GitHub Controller

See merge request gitlab-org/gitlab!38915
parents 2ddeaea2 b30c28ee
...@@ -54,6 +54,16 @@ class Import::GiteaController < Import::GithubController ...@@ -54,6 +54,16 @@ class Import::GiteaController < Import::GithubController
end end
end end
override :client_repos
def client_repos
@client_repos ||= filtered(client.repos)
end
override :client
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
override :client_options override :client_options
def client_options def client_options
{ host: provider_url, api_version: 'v1' } { host: provider_url, api_version: 'v1' }
......
...@@ -10,6 +10,9 @@ class Import::GithubController < Import::BaseController ...@@ -10,6 +10,9 @@ class Import::GithubController < Import::BaseController
before_action :provider_auth, only: [:status, :realtime_changes, :create] before_action :provider_auth, only: [:status, :realtime_changes, :create]
before_action :expire_etag_cache, only: [:status, :create] before_action :expire_etag_cache, only: [:status, :create]
OAuthConfigMissingError = Class.new(StandardError)
rescue_from OAuthConfigMissingError, with: :missing_oauth_config
rescue_from Octokit::Unauthorized, with: :provider_unauthorized rescue_from Octokit::Unauthorized, with: :provider_unauthorized
rescue_from Octokit::TooManyRequests, with: :provider_rate_limit rescue_from Octokit::TooManyRequests, with: :provider_rate_limit
...@@ -22,7 +25,7 @@ class Import::GithubController < Import::BaseController ...@@ -22,7 +25,7 @@ class Import::GithubController < Import::BaseController
end end
def callback def callback
session[access_token_key] = client.get_token(params[:code]) session[access_token_key] = get_token(params[:code])
redirect_to status_import_url redirect_to status_import_url
end end
...@@ -77,9 +80,7 @@ class Import::GithubController < Import::BaseController ...@@ -77,9 +80,7 @@ class Import::GithubController < Import::BaseController
override :provider_url override :provider_url
def provider_url def provider_url
strong_memoize(:provider_url) do strong_memoize(:provider_url) do
provider = Gitlab::Auth::OAuth::Provider.config_for('github') oauth_config&.dig('url').presence || 'https://github.com'
provider&.dig('url').presence || 'https://github.com'
end end
end end
...@@ -104,11 +105,66 @@ class Import::GithubController < Import::BaseController ...@@ -104,11 +105,66 @@ class Import::GithubController < Import::BaseController
end end
def client def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options) @client ||= if Feature.enabled?(:remove_legacy_github_client)
Gitlab::GithubImport::Client.new(session[access_token_key])
else
Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
end end
def client_repos def client_repos
@client_repos ||= filtered(client.repos) @client_repos ||= if Feature.enabled?(:remove_legacy_github_client)
filtered(concatenated_repos)
else
filtered(client.repos)
end
end
def concatenated_repos
return [] unless client.respond_to?(:each_page)
client.each_page(:repos).flat_map(&:objects)
end
def oauth_client
raise OAuthConfigMissingError unless oauth_config
@oauth_client ||= ::OAuth2::Client.new(
oauth_config.app_id,
oauth_config.app_secret,
oauth_options.merge(ssl: { verify: oauth_config['verify_ssl'] })
)
end
def oauth_config
@oauth_config ||= Gitlab::Auth::OAuth::Provider.config_for('github')
end
def oauth_options
if oauth_config
oauth_config.dig('args', 'client_options').deep_symbolize_keys
else
OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
end
end
def authorize_url
if Feature.enabled?(:remove_legacy_github_client)
oauth_client.auth_code.authorize_url(
redirect_uri: callback_import_url,
scope: 'repo, user, user:email'
)
else
client.authorize_url(callback_import_url)
end
end
def get_token(code)
if Feature.enabled?(:remove_legacy_github_client)
oauth_client.auth_code.get_token(code).token
else
client.get_token(code)
end
end end
def verify_import_enabled def verify_import_enabled
...@@ -116,7 +172,7 @@ class Import::GithubController < Import::BaseController ...@@ -116,7 +172,7 @@ class Import::GithubController < Import::BaseController
end end
def go_to_provider_for_permissions def go_to_provider_for_permissions
redirect_to client.authorize_url(callback_import_url) redirect_to authorize_url
end end
def import_enabled? def import_enabled?
...@@ -152,6 +208,12 @@ class Import::GithubController < Import::BaseController ...@@ -152,6 +208,12 @@ class Import::GithubController < Import::BaseController
alert: _("GitHub API rate limit exceeded. Try again after %{reset_time}") % { reset_time: reset_time } alert: _("GitHub API rate limit exceeded. Try again after %{reset_time}") % { reset_time: reset_time }
end end
def missing_oauth_config
session[access_token_key] = nil
redirect_to new_import_url,
alert: _('Missing OAuth configuration for GitHub.')
end
def access_token_key def access_token_key
:"#{provider_name}_access_token" :"#{provider_name}_access_token"
end end
......
...@@ -33,7 +33,7 @@ module Import ...@@ -33,7 +33,7 @@ module Import
end end
def repo def repo
@repo ||= client.repo(params[:repo_id].to_i) @repo ||= client.repository(params[:repo_id].to_i)
end end
def project_name def project_name
......
...@@ -53,6 +53,7 @@ RSpec.describe 'New project' do ...@@ -53,6 +53,7 @@ RSpec.describe 'New project' do
context 'when licensed' do context 'when licensed' do
before do before do
stub_licensed_features(ci_cd_projects: true) stub_licensed_features(ci_cd_projects: true)
stub_feature_flags(remove_legacy_github_client: false)
end end
it 'shows CI/CD tab and pane' do it 'shows CI/CD tab and pane' do
...@@ -122,7 +123,7 @@ RSpec.describe 'New project' do ...@@ -122,7 +123,7 @@ RSpec.describe 'New project' do
wait_for_requests wait_for_requests
# Mock the POST `/import/github` # Mock the POST `/import/github`
allow_any_instance_of(Gitlab::LegacyGithubImport::Client).to receive(:repo).and_return(repo) allow_any_instance_of(Gitlab::LegacyGithubImport::Client).to receive(:repository).and_return(repo)
project = create(:project, name: 'some-github-repo', creator: user, import_type: 'github') project = create(:project, name: 'some-github-repo', creator: user, import_type: 'github')
create(:import_state, :finished, import_url: repo.clone_url, project: project) create(:import_state, :finished, import_url: repo.clone_url, project: project)
allow_any_instance_of(CiCd::SetupProject).to receive(:setup_external_service) allow_any_instance_of(CiCd::SetupProject).to receive(:setup_external_service)
......
...@@ -10,7 +10,11 @@ module API ...@@ -10,7 +10,11 @@ module API
helpers do helpers do
def client def client
@client ||= Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options) @client ||= if Feature.enabled?(:remove_legacy_github_client)
Gitlab::GithubImport::Client.new(params[:personal_access_token])
else
Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options)
end
end end
def access_params def access_params
......
...@@ -15642,6 +15642,9 @@ msgstr "" ...@@ -15642,6 +15642,9 @@ msgstr ""
msgid "Mirroring will only be available if the feature is included in the plan of the selected group or user." msgid "Mirroring will only be available if the feature is included in the plan of the selected group or user."
msgstr "" msgstr ""
msgid "Missing OAuth configuration for GitHub."
msgstr ""
msgid "Missing commit signatures endpoint!" msgid "Missing commit signatures endpoint!"
msgstr "" msgstr ""
......
...@@ -34,6 +34,14 @@ RSpec.describe Import::GiteaController do ...@@ -34,6 +34,14 @@ RSpec.describe Import::GiteaController do
assign_host_url assign_host_url
end end
it "requests provider repos list" do
expect(stub_client(repos: [], orgs: [])).to receive(:repos)
get :status
expect(response).to have_gitlab_http_status(:ok)
end
context 'when host url is local or not http' do context 'when host url is local or not http' do
%w[https://localhost:3000 http://192.168.0.1 ftp://testing].each do |url| %w[https://localhost:3000 http://192.168.0.1 ftp://testing].each do |url|
let(:host_url) { url } let(:host_url) { url }
......
...@@ -15,10 +15,7 @@ RSpec.describe Import::GithubController do ...@@ -15,10 +15,7 @@ RSpec.describe Import::GithubController do
it "redirects to GitHub for an access token if logged in with GitHub" do it "redirects to GitHub for an access token if logged in with GitHub" do
allow(controller).to receive(:logged_in_with_provider?).and_return(true) allow(controller).to receive(:logged_in_with_provider?).and_return(true)
expect(controller).to receive(:go_to_provider_for_permissions).and_call_original expect(controller).to receive(:go_to_provider_for_permissions).and_call_original
allow_any_instance_of(Gitlab::LegacyGithubImport::Client) allow(controller).to receive(:authorize_url).and_call_original
.to receive(:authorize_url)
.with(users_import_github_callback_url)
.and_call_original
get :new get :new
...@@ -46,13 +43,15 @@ RSpec.describe Import::GithubController do ...@@ -46,13 +43,15 @@ RSpec.describe Import::GithubController do
end end
describe "GET callback" do describe "GET callback" do
before do
allow(controller).to receive(:get_token).and_return(token)
allow(controller).to receive(:oauth_options).and_return({})
stub_omniauth_provider('github')
end
it "updates access token" do it "updates access token" do
token = "asdasd12345" token = "asdasd12345"
allow_any_instance_of(Gitlab::LegacyGithubImport::Client)
.to receive(:get_token).and_return(token)
allow_any_instance_of(Gitlab::LegacyGithubImport::Client)
.to receive(:github_options).and_return({})
stub_omniauth_provider('github')
get :callback get :callback
...@@ -66,7 +65,86 @@ RSpec.describe Import::GithubController do ...@@ -66,7 +65,86 @@ RSpec.describe Import::GithubController do
end end
describe "GET status" do describe "GET status" do
it_behaves_like 'a GitHub-ish import controller: GET status' context 'when using OAuth' do
before do
allow(controller).to receive(:logged_in_with_provider?).and_return(true)
end
context 'when OAuth config is missing' do
let(:new_import_url) { public_send("new_import_#{provider}_url") }
before do
allow(controller).to receive(:oauth_config).and_return(nil)
end
it 'returns missing config error' do
expect(controller).to receive(:go_to_provider_for_permissions).and_call_original
get :status
expect(session[:"#{provider}_access_token"]).to be_nil
expect(controller).to redirect_to(new_import_url)
expect(flash[:alert]).to eq('Missing OAuth configuration for GitHub.')
end
end
end
context 'when feature remove_legacy_github_client is disabled' do
before do
stub_feature_flags(remove_legacy_github_client: false)
session[:"#{provider}_access_token"] = 'asdasd12345'
end
it_behaves_like 'a GitHub-ish import controller: GET status'
it 'uses Gitlab::LegacyGitHubImport::Client' do
expect(controller.send(:client)).to be_instance_of(Gitlab::LegacyGithubImport::Client)
end
it 'fetches repos using legacy client' do
expect_next_instance_of(Gitlab::LegacyGithubImport::Client) do |client|
expect(client).to receive(:repos)
end
get :status
end
end
context 'when feature remove_legacy_github_client is enabled' do
before do
stub_feature_flags(remove_legacy_github_client: true)
session[:"#{provider}_access_token"] = 'asdasd12345'
end
it_behaves_like 'a GitHub-ish import controller: GET status'
it 'uses Gitlab::GithubImport::Client' do
expect(controller.send(:client)).to be_instance_of(Gitlab::GithubImport::Client)
end
it 'fetches repos using latest github client' do
expect_next_instance_of(Gitlab::GithubImport::Client) do |client|
expect(client).to receive(:each_page).with(:repos).and_return([].to_enum)
end
get :status
end
it 'concatenates list of repos from multiple pages' do
repo_1 = OpenStruct.new(login: 'emacs', full_name: 'asd/emacs', name: 'emacs', owner: { login: 'owner' })
repo_2 = OpenStruct.new(login: 'vim', full_name: 'asd/vim', name: 'vim', owner: { login: 'owner' })
repos = [OpenStruct.new(objects: [repo_1]), OpenStruct.new(objects: [repo_2])].to_enum
allow(stub_client).to receive(:each_page).and_return(repos)
get :status, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('provider_repos').count).to eq(2)
expect(json_response.dig('provider_repos', 0, 'id')).to eq(repo_1.id)
expect(json_response.dig('provider_repos', 1, 'id')).to eq(repo_2.id)
end
end
end end
describe "POST create" do describe "POST create" do
......
...@@ -22,7 +22,7 @@ RSpec.describe API::ImportGithub do ...@@ -22,7 +22,7 @@ RSpec.describe API::ImportGithub do
before do before do
Grape::Endpoint.before_each do |endpoint| Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object) allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repository: provider_repo).as_null_object)
end end
end end
......
...@@ -6,7 +6,6 @@ RSpec.describe Import::GithubService do ...@@ -6,7 +6,6 @@ RSpec.describe Import::GithubService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:token) { 'complex-token' } let_it_be(:token) { 'complex-token' }
let_it_be(:access_params) { { github_access_token: 'github-complex-token' } } let_it_be(:access_params) { { github_access_token: 'github-complex-token' } }
let_it_be(:client) { Gitlab::LegacyGithubImport::Client.new(token) }
let_it_be(:params) { { repo_id: 123, new_name: 'new_repo', target_namespace: 'root' } } let_it_be(:params) { { repo_id: 123, new_name: 'new_repo', target_namespace: 'root' } }
let(:subject) { described_class.new(client, user, params) } let(:subject) { described_class.new(client, user, params) }
...@@ -15,41 +14,61 @@ RSpec.describe Import::GithubService do ...@@ -15,41 +14,61 @@ RSpec.describe Import::GithubService do
allow(subject).to receive(:authorized?).and_return(true) allow(subject).to receive(:authorized?).and_return(true)
end end
context 'do not raise an exception on input error' do shared_examples 'handles errors' do |klass|
let(:exception) { Octokit::ClientError.new(status: 404, body: 'Not Found') } let(:client) { klass.new(token) }
before do context 'do not raise an exception on input error' do
expect(client).to receive(:repo).and_raise(exception) let(:exception) { Octokit::ClientError.new(status: 404, body: 'Not Found') }
end
before do
expect(client).to receive(:repository).and_raise(exception)
end
it 'logs the original error' do it 'logs the original error' do
expect(Gitlab::Import::Logger).to receive(:error).with({ expect(Gitlab::Import::Logger).to receive(:error).with({
message: 'Import failed due to a GitHub error', message: 'Import failed due to a GitHub error',
status: 404, status: 404,
error: 'Not Found' error: 'Not Found'
}).and_call_original }).and_call_original
subject.execute(access_params, :github) subject.execute(access_params, :github)
end
it 'returns an error' do
result = subject.execute(access_params, :github)
expect(result).to include(
message: 'Import failed due to a GitHub error: Not Found',
status: :error,
http_status: :unprocessable_entity
)
end
end end
it 'returns an error' do it 'raises an exception for unknown error causes' do
result = subject.execute(access_params, :github) exception = StandardError.new('Not Implemented')
expect(client).to receive(:repository).and_raise(exception)
expect(result).to include( expect(Gitlab::Import::Logger).not_to receive(:error)
message: 'Import failed due to a GitHub error: Not Found',
status: :error, expect { subject.execute(access_params, :github) }.to raise_error(exception)
http_status: :unprocessable_entity
)
end end
end end
it 'raises an exception for unknown error causes' do context 'when remove_legacy_github_client feature flag is enabled' do
exception = StandardError.new('Not Implemented') before do
stub_feature_flags(remove_legacy_github_client: true)
end
expect(client).to receive(:repo).and_raise(exception) include_examples 'handles errors', Gitlab::GithubImport::Client
end
expect(Gitlab::Import::Logger).not_to receive(:error) context 'when remove_legacy_github_client feature flag is enabled' do
before do
stub_feature_flags(remove_legacy_github_client: false)
end
expect { subject.execute(access_params, :github) }.to raise_error(exception) include_examples 'handles errors', Gitlab::LegacyGithubImport::Client
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment