diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb index acbd26cbdf69c15729b86641dfd2b5fa9c794b26..a24273488fb086a2bf0b8b136de5497648446759 100644 --- a/app/controllers/projects/mirrors_controller.rb +++ b/app/controllers/projects/mirrors_controller.rb @@ -81,6 +81,7 @@ def mirror_params_attributes only_protected_branches keep_divergent_refs auth_method + user password ssh_known_hosts regenerate_ssh_private_key diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml index bc162458eae5efd71632ddac71ebd2f16b33cce3..ab039bdead254ed4f7cf8c31575684a903e69970 100644 --- a/app/views/projects/mirrors/_authentication_method.html.haml +++ b/app/views/projects/mirrors/_authentication_method.html.haml @@ -1,5 +1,5 @@ - mirror = f.object -- auth_options = [[_('Password'), 'password'], [_('SSH public key'), 'ssh_public_key']] +- auth_options = [[_('Username and Password'), 'password'], [_('SSH public key'), 'ssh_public_key']] .form-group = f.label :auth_method, _('Authentication method'), class: 'label-bold' @@ -9,6 +9,9 @@ = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type" .form-group - .well-password-auth.collapse.js-well-password-auth + = f.label :user, _('Username'), class: 'label-bold' + = f.text_field :user, class: 'form-control gl-form-input gl-form-input-xl', value: nil, autocomplete: 'off', required: false, autocorrect: 'off', autocapitalize: 'off', spellcheck: false +.well-password-auth.collapse.js-well-password-auth + .form-group = f.label :password, _("Password"), class: "label-bold" = f.password_field :password, class: 'form-control gl-form-input js-mirror-password-field gl-form-input-xl', autocomplete: 'off', data: { qa_selector: 'password_field' } diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml index 2bd2c7cac4424b06f657543ba1b37f767532dc88..5a8710a64b02f2114e3bfb2276c286bb817e775f 100644 --- a/app/views/projects/mirrors/_instructions.html.haml +++ b/app/views/projects/mirrors/_instructions.html.haml @@ -4,7 +4,7 @@ = html_escape(_('The repository must be accessible over %{code_open}http://%{code_close}, %{code_open}https://%{code_close}, %{code_open}ssh://%{code_close} or %{code_open}git://%{code_close}.')) % { code_open: ''.html_safe, code_close: ''.html_safe } %li= html_escape(_('When using the %{code_open}http://%{code_close} or %{code_open}https://%{code_close} protocols, please provide the exact URL to the repository. HTTP redirects will not be followed.')) % { code_open: ''.html_safe, code_close: ''.html_safe } - %li= html_escape(_('Include the username in the URL if required: %{code_open}https://username@gitlab.company.com/group/project.git%{code_close}.')) % { code_open: ''.html_safe, code_close: ''.html_safe } + %li= html_escape(_('Do not include the username in the URL, use the username field below if required: %{code_open}https://gitlab.company.com/group/project.git%{code_close}.')) % { code_open: ''.html_safe, code_close: ''.html_safe } %li - minutes = Gitlab.config.gitlab_shell.git_timeout / 60 = _("The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination.") % { number_of_minutes: minutes } diff --git a/ee/app/controllers/ee/projects/mirrors_controller.rb b/ee/app/controllers/ee/projects/mirrors_controller.rb index 8e725f0cc87da80518d1f875c2678dcc882eff13..cc6dc2df234f8fb1edab639e2b5546cd0a88b2b4 100644 --- a/ee/app/controllers/ee/projects/mirrors_controller.rb +++ b/ee/app/controllers/ee/projects/mirrors_controller.rb @@ -68,6 +68,7 @@ def mirror_params_attributes_ee import_data_attributes: %i[ id auth_method + user password ssh_known_hosts regenerate_ssh_private_key diff --git a/ee/spec/features/projects/mirror_spec.rb b/ee/spec/features/projects/mirror_spec.rb index 53f7b3a2cf8dffc1608ea119977a2f156e4b6372..05e58158f953907b493e797fc49e8f648414f3b5 100644 --- a/ee/spec/features/projects/mirror_spec.rb +++ b/ee/spec/features/projects/mirror_spec.rb @@ -108,17 +108,27 @@ end describe 'password authentication' do - it 'can be set up' do + let(:url) { 'http://example.com' } + let(:username) { 'user' } + let(:password) { 'foo' } + let(:direction) { 'Pull' } + + def add_mirror visit project_settings_repository_path(project) click_button('Add new') page.within('.project-mirror-settings') do - fill_and_wait_for_mirror_url_javascript('Git repository URL', 'http://user@example.com') + fill_and_wait_for_mirror_url_javascript('Git repository URL', url) - select 'Pull', from: 'Mirror direction' - fill_in 'Password', with: 'foo' + select direction, from: 'Mirror direction' + fill_in 'Username', with: username + fill_in 'Password', with: password click_without_sidekiq 'Mirror repository' end + end + + it 'can be set up' do + add_mirror expect(page).to have_content('Mirroring settings were successfully updated') @@ -128,6 +138,73 @@ expect(project.import_url).to eq('http://user:foo@example.com') end + context 'when username is provided in url' do + let(:url) { 'http://urlusername@example.com' } + + it 'will be ignored' do + add_mirror + + expect(page).to have_content('Mirroring settings were successfully updated') + + project.reload + expect(project.mirror?).to be_truthy + expect(import_data.auth_method).to eq('password') + expect(project.import_url).to eq('http://user:foo@example.com') + end + end + + context 'when given special characters' do + context 'in the username' do + let(:username) { 'u@s+e/r' } + let(:password) { 'foo' } + + context 'when adding a pull mirror' do + it 'escapes special characters in username' do + add_mirror + + project.reload + expect(project.import_url).to eq('http://u%40s%2Be%2Fr:foo@example.com') + end + end + + context 'when adding a push mirror' do + let(:direction) { 'Push' } + + it 'escapes special characters in username' do # this one + add_mirror + + project.reload + expect(project.remote_mirrors.first.url).to eq('http://u%40s%2Be%2Fr:foo@example.com') + end + end + end + + context 'in the password' do + let(:username) { 'user' } + let(:password) { 'f@o+o/' } + + context 'when adding a pull mirror' do + it 'escapes special characters in password' do + add_mirror + + project.reload + expect(project.import_url).to eq('http://user:f%40o%2Bo%2F@example.com') + end + end + + context 'when adding a push mirror' do + let(:direction) { 'Push' } + + it 'escapes special characters in the password' do # this one + add_mirror + + project.reload + expect(project.remote_mirrors.first.url).to eq('http://user:f%40o%2Bo%2F@example.com') + end + end + end + end + it 'can be changed to unauthenticated', quarantine: 'https://gitlab.com/gitlab-org/quality/engineering-productivity/master-broken-incidents/-/issues/1486' do # rubocop:disable Layout/LineLength project.update!(import_url: 'http://user:password@example.com') @@ -153,9 +230,10 @@ click_button('Add new') page.within('.project-mirror-settings') do - fill_and_wait_for_mirror_url_javascript('Git repository URL', 'ssh://user@example.com') + fill_and_wait_for_mirror_url_javascript('Git repository URL', 'ssh://example.com') select('Pull', from: 'Mirror direction') + fill_in 'Username', with: 'user' select 'SSH public key', from: 'Authentication method' # Generates an SSH public key with an asynchronous PUT and displays it @@ -170,9 +248,10 @@ click_button('Add new') page.within('.project-mirror-settings') do - fill_and_wait_for_mirror_url_javascript('Git repository URL', 'http://git@example.com') + fill_and_wait_for_mirror_url_javascript('Git repository URL', 'http://example.com') select('Pull', from: 'Mirror direction') + fill_in 'Username', with: 'git' fill_in 'Password', with: 'test_password' click_without_sidekiq 'Mirror repository' end @@ -187,7 +266,7 @@ end describe 'SSH public key authentication' do - let(:ssh_url) { 'ssh://user@example.com' } + let(:ssh_url) { 'ssh://example.com' } it 'can be set up' do visit project_settings_repository_path(project) @@ -197,6 +276,7 @@ fill_and_wait_for_mirror_url_javascript('Git repository URL', ssh_url) select('Pull', from: 'Mirror direction') + fill_in 'Username', with: 'user' select 'SSH public key', from: 'Authentication method' click_without_sidekiq 'Mirror repository' @@ -211,6 +291,26 @@ expect(import_data.auth_method).to eq('ssh_public_key') expect(import_data.password).to be_blank end + + context 'when no username is provided' do + it 'import url has no delimiting colon' do + visit project_settings_repository_path(project) + click_button('Add new') + + page.within('.project-mirror-settings') do + fill_and_wait_for_mirror_url_javascript('Git repository URL', ssh_url) + + select('Pull', from: 'Mirror direction') + select 'SSH public key', from: 'Authentication method' + + click_without_sidekiq 'Mirror repository' + end + project.reload + expect(project.username_only_import_url).to eq('ssh://example.com') + expect(import_data.auth_method).to eq('ssh_public_key') + expect(import_data.password).to be_blank + end + end end describe 'host key management', :use_clean_rails_memory_store_caching do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7501db76a2459415ffb971001939a535aa737a9d..66a352e947ba848ef04b0116a3732ebecf80f1a9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17242,6 +17242,9 @@ msgstr "" msgid "Do not force push over diverged refs. After the mirror is created, this setting can only be modified using the API. %{mirroring_docs_link_start}Learn more about this option%{link_closing_tag} and %{mirroring_api_docs_link_start}the API.%{link_closing_tag}" msgstr "" +msgid "Do not include the username in the URL, use the username field below if required: %{code_open}https://gitlab.company.com/group/project.git%{code_close}." +msgstr "" + msgid "Do you want to remove this deploy key?" msgstr "" @@ -24795,9 +24798,6 @@ msgstr "" msgid "Include the name of the author of the issue, merge request or comment in the email body. By default, GitLab overrides the email sender's name. Some email servers don't support that option." msgstr "" -msgid "Include the username in the URL if required: %{code_open}https://username@gitlab.company.com/group/project.git%{code_close}." -msgstr "" - msgid "Includes LFS objects. It can be overridden per group, or per project. Set to 0 for no limit." msgstr "" @@ -51531,6 +51531,9 @@ msgstr "" msgid "Username (optional)" msgstr "" +msgid "Username and Password" +msgstr "" + msgid "Username is already taken." msgstr ""