Commit 18c07084 authored by Luke Bennett's avatar Luke Bennett Committed by Nick Thomas

Destroy repo mirrors instead of disabling them

It is important to destroy data related to repo mirrors when they are
disabled.
Use `_destroy` nested attribute instead of `enabled` for push mirrors.
Call `remove_import_data` after saving a project if its pull mirror is
disabled.
parent 53823531
...@@ -87,7 +87,7 @@ export default class MirrorRepos { ...@@ -87,7 +87,7 @@ export default class MirrorRepos {
project: { project: {
remote_mirrors_attributes: { remote_mirrors_attributes: {
id: $target.data('mirrorId'), id: $target.data('mirrorId'),
enabled: 0, _destroy: 1,
}, },
}, },
}; };
......
...@@ -24,12 +24,6 @@ export default class SSHMirror { ...@@ -24,12 +24,6 @@ export default class SSHMirror {
this.$wellAuthTypeChanging = this.$form.find('.js-well-changing-auth'); this.$wellAuthTypeChanging = this.$form.find('.js-well-changing-auth');
this.$wellPasswordAuth = this.$form.find('.js-well-password-auth'); this.$wellPasswordAuth = this.$form.find('.js-well-password-auth');
this.$wellSSHAuth = this.$form.find('.js-well-ssh-auth');
this.$sshPublicKeyWrap = this.$form.find('.js-ssh-public-key-wrap');
this.$regeneratePublicSshKeyButton = this.$wellSSHAuth.find('.js-btn-regenerate-ssh-key');
this.$regeneratePublicSshKeyModal = this.$wellSSHAuth.find(
'.js-regenerate-public-ssh-key-confirm-modal',
);
} }
init() { init() {
...@@ -40,15 +34,6 @@ export default class SSHMirror { ...@@ -40,15 +34,6 @@ export default class SSHMirror {
this.$dropdownAuthType.on('change', e => this.handleAuthTypeChange(e)); this.$dropdownAuthType.on('change', e => this.handleAuthTypeChange(e));
this.$btnDetectHostKeys.on('click', e => this.handleDetectHostKeys(e)); this.$btnDetectHostKeys.on('click', e => this.handleDetectHostKeys(e));
this.$btnSSHHostsShowAdvanced.on('click', e => this.handleSSHHostsAdvanced(e)); this.$btnSSHHostsShowAdvanced.on('click', e => this.handleSSHHostsAdvanced(e));
this.$regeneratePublicSshKeyButton.on('click', () =>
this.$regeneratePublicSshKeyModal.toggle(true),
);
$('.js-confirm', this.$regeneratePublicSshKeyModal).on('click', e =>
this.regeneratePublicSshKey(e),
);
$('.js-cancel', this.$regeneratePublicSshKeyModal).on('click', () =>
this.$regeneratePublicSshKeyModal.toggle(false),
);
} }
/** /**
...@@ -162,54 +147,11 @@ export default class SSHMirror { ...@@ -162,54 +147,11 @@ export default class SSHMirror {
* Authentication method dropdown change event listener * Authentication method dropdown change event listener
*/ */
handleAuthTypeChange() { handleAuthTypeChange() {
const projectMirrorAuthTypeEndpoint = `${this.$form.attr('action')}.json`;
const $sshPublicKey = this.$sshPublicKeyWrap.find('.ssh-public-key');
const selectedAuthType = this.$dropdownAuthType.val(); const selectedAuthType = this.$dropdownAuthType.val();
this.$wellPasswordAuth.collapse('hide'); this.$wellPasswordAuth.collapse('hide');
this.$wellSSHAuth.collapse('hide');
this.updateHiddenAuthType(selectedAuthType); this.updateHiddenAuthType(selectedAuthType);
// This request should happen only if selected Auth type was SSH
// and SSH Public key was not present on page load
if (selectedAuthType === AUTH_METHOD.SSH && !$sshPublicKey.text().trim()) {
if (!this.$wellSSHAuth.length) return;
// Construct request body
const authTypeData = {
project: {
...this.$regeneratePublicSshKeyButton.data().projectData,
},
};
this.$wellAuthTypeChanging.collapse('show');
this.$dropdownAuthType.disable();
axios
.put(projectMirrorAuthTypeEndpoint, JSON.stringify(authTypeData), {
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
.then(({ data }) => {
// Show SSH public key container and fill in public key
this.toggleAuthWell(selectedAuthType);
this.toggleSSHAuthWellMessage(true);
this.setSSHPublicKey(data.import_data_attributes.ssh_public_key);
this.$wellAuthTypeChanging.collapse('hide');
this.$dropdownAuthType.enable();
})
.catch(() => {
Flash(__('Something went wrong on our end.'));
this.$wellAuthTypeChanging.collapse('hide');
this.$dropdownAuthType.enable();
});
} else {
this.toggleAuthWell(selectedAuthType); this.toggleAuthWell(selectedAuthType);
this.$wellSSHAuth.find('.js-ssh-public-key-present').collapse('show');
}
} }
/** /**
...@@ -235,7 +177,6 @@ export default class SSHMirror { ...@@ -235,7 +177,6 @@ export default class SSHMirror {
*/ */
toggleAuthWell(authType) { toggleAuthWell(authType) {
this.$wellPasswordAuth.collapse(authType === AUTH_METHOD.PASSWORD ? 'show' : 'hide'); this.$wellPasswordAuth.collapse(authType === AUTH_METHOD.PASSWORD ? 'show' : 'hide');
this.$wellSSHAuth.collapse(authType === AUTH_METHOD.SSH ? 'show' : 'hide');
this.updateHiddenAuthType(authType); this.updateHiddenAuthType(authType);
} }
...@@ -244,64 +185,11 @@ export default class SSHMirror { ...@@ -244,64 +185,11 @@ export default class SSHMirror {
this.$hiddenAuthType.prop('disabled', authType === AUTH_METHOD.SSH); this.$hiddenAuthType.prop('disabled', authType === AUTH_METHOD.SSH);
} }
/**
* Toggle SSH auth information message
*/
toggleSSHAuthWellMessage(sshKeyPresent) {
this.$sshPublicKeyWrap.collapse(sshKeyPresent ? 'show' : 'hide');
this.$wellSSHAuth.find('.js-ssh-public-key-present').collapse(sshKeyPresent ? 'show' : 'hide');
this.$regeneratePublicSshKeyButton.collapse(sshKeyPresent ? 'show' : 'hide');
this.$wellSSHAuth.find('.js-ssh-public-key-pending').collapse(sshKeyPresent ? 'hide' : 'show');
}
/**
* Sets SSH Public key to Clipboard button and shows it on UI.
*/
setSSHPublicKey(sshPublicKey) {
this.$sshPublicKeyWrap.find('.ssh-public-key').text(sshPublicKey);
this.$sshPublicKeyWrap
.find('.btn-copy-ssh-public-key')
.attr('data-clipboard-text', sshPublicKey);
}
regeneratePublicSshKey(event) {
event.preventDefault();
this.$regeneratePublicSshKeyModal.toggle(false);
const button = this.$regeneratePublicSshKeyButton;
const spinner = $('.js-spinner', button);
const endpoint = button.data('endpoint');
const authTypeData = {
project: {
...this.$regeneratePublicSshKeyButton.data().projectData,
},
};
button.attr('disabled', 'disabled');
spinner.removeClass('d-none');
axios
.patch(endpoint, authTypeData)
.then(({ data }) => {
button.removeAttr('disabled');
spinner.addClass('d-none');
this.setSSHPublicKey(data.import_data_attributes.ssh_public_key);
})
.catch(() => {
Flash(__('Unable to regenerate public ssh key.'));
});
}
destroy() { destroy() {
this.$repositoryUrl.off('keyup'); this.$repositoryUrl.off('keyup');
this.$form.find('.js-known-hosts').off('keyup'); this.$form.find('.js-known-hosts').off('keyup');
this.$dropdownAuthType.off('change'); this.$dropdownAuthType.off('change');
this.$btnDetectHostKeys.off('click'); this.$btnDetectHostKeys.off('click');
this.$btnSSHHostsShowAdvanced.off('click'); this.$btnSSHHostsShowAdvanced.off('click');
this.$regeneratePublicSshKeyButton.off('click');
$('.js-confirm', this.$regeneratePublicSshKeyModal).off('click');
$('.js-cancel', this.$regeneratePublicSshKeyModal).off('click');
} }
} }
...@@ -81,6 +81,7 @@ class Projects::MirrorsController < Projects::ApplicationController ...@@ -81,6 +81,7 @@ class Projects::MirrorsController < Projects::ApplicationController
password password
ssh_known_hosts ssh_known_hosts
regenerate_ssh_private_key regenerate_ssh_private_key
_destroy
] ]
] ]
end end
......
- mirror = f.object - mirror = f.object
- is_push = local_assigns.fetch(:is_push, false)
- auth_options = [[_('Password'), 'password'], [_('SSH public key'), 'ssh_public_key']] - auth_options = [[_('Password'), 'password'], [_('SSH public key'), 'ssh_public_key']]
- regen_data = { auth_method: 'ssh_public_key', regenerate_ssh_private_key: true }
- ssh_public_key_present = mirror.ssh_public_key.present?
.form-group .form-group
= f.label :auth_method, _('Authentication method'), class: 'label-bold' = f.label :auth_method, _('Authentication method'), class: 'label-bold'
...@@ -17,21 +14,3 @@ ...@@ -17,21 +14,3 @@
.well-password-auth.collapse.js-well-password-auth .well-password-auth.collapse.js-well-password-auth
= f.label :password, _("Password"), class: "label-bold" = f.label :password, _("Password"), class: "label-bold"
= f.password_field :password, value: mirror.password, class: 'form-control qa-password', autocomplete: 'new-password' = f.password_field :password, value: mirror.password, class: 'form-control qa-password', autocomplete: 'new-password'
- unless is_push
.well-ssh-auth.collapse.js-well-ssh-auth
%p.js-ssh-public-key-present{ class: ('collapse' unless ssh_public_key_present) }
= _('Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation.')
%p.js-ssh-public-key-pending{ class: ('collapse' if ssh_public_key_present) }
= _('An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation.')
.clearfix.js-ssh-public-key-wrap{ class: ('collapse' unless ssh_public_key_present) }
%code.prepend-top-10.ssh-public-key
= mirror.ssh_public_key
= clipboard_button(text: mirror.ssh_public_key, title: _("Copy SSH public key to clipboard"), class: 'prepend-top-10 btn-copy-ssh-public-key')
= button_tag type: 'button',
data: { endpoint: project_mirror_path(@project), project_data: { import_data_attributes: regen_data } },
class: "btn btn-inverted btn-warning prepend-top-10 js-btn-regenerate-ssh-key#{ ' collapse' unless ssh_public_key_present }" do
= icon('spinner spin', class: 'js-spinner d-none')
= _('Regenerate key')
= render 'projects/mirrors/regenerate_public_ssh_key_confirm_modal'
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
= rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
= rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden' = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
= render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f } = render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f }
= render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f, is_push: true } = render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- verified_at = mirror.ssh_known_hosts_verified_at - verified_at = mirror.ssh_known_hosts_verified_at
.form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) } .form-group.js-ssh-host-keys-section{ class: ('collapse' unless mirror.ssh_mirror_url?) }
%button.btn.btn-inverted.btn-success.inline.js-detect-host-keys.append-right-10{ type: 'button' } %button.btn.btn-inverted.btn-secondary.inline.js-detect-host-keys.append-right-10{ type: 'button' }
= icon('spinner spin', class: 'js-spinner d-none') = icon('spinner spin', class: 'js-spinner d-none')
= _('Detect host keys') = _('Detect host keys')
.fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) } .fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless mirror.ssh_mirror_url?) }
......
---
title: Destroy project remote mirrors instead of disabling
merge_request: 27087
author:
type: security
...@@ -811,9 +811,6 @@ msgstr "" ...@@ -811,9 +811,6 @@ msgstr ""
msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication" msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
msgstr "" msgstr ""
msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
msgstr ""
msgid "An application called %{link_to_client} is requesting access to your GitLab account." msgid "An application called %{link_to_client} is requesting access to your GitLab account."
msgstr "" msgstr ""
...@@ -2811,9 +2808,6 @@ msgstr "" ...@@ -2811,9 +2808,6 @@ msgstr ""
msgid "Copy SSH public key" msgid "Copy SSH public key"
msgstr "" msgstr ""
msgid "Copy SSH public key to clipboard"
msgstr ""
msgid "Copy URL to clipboard" msgid "Copy URL to clipboard"
msgstr "" msgstr ""
...@@ -4762,9 +4756,6 @@ msgstr "" ...@@ -4762,9 +4756,6 @@ msgstr ""
msgid "Help page text and support page url." msgid "Help page text and support page url."
msgstr "" msgstr ""
msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
msgstr ""
msgid "Hide file browser" msgid "Hide file browser"
msgstr "" msgstr ""
...@@ -10154,9 +10145,6 @@ msgstr "" ...@@ -10154,9 +10145,6 @@ msgstr ""
msgid "Unable to load the diff. %{button_try_again}" msgid "Unable to load the diff. %{button_try_again}"
msgstr "" msgstr ""
msgid "Unable to regenerate public ssh key."
msgstr ""
msgid "Unable to resolve" msgid "Unable to resolve"
msgstr "" msgstr ""
......
...@@ -217,5 +217,24 @@ describe 'Projects > Settings > Repository settings' do ...@@ -217,5 +217,24 @@ describe 'Projects > Settings > Repository settings' do
expect(RepositoryCleanupWorker.jobs.count).to eq(1) expect(RepositoryCleanupWorker.jobs.count).to eq(1)
end end
end end
context 'with an existing mirror', :js do
let(:mirrored_project) { create(:project, :repository, :remote_mirror) }
before do
mirrored_project.add_maintainer(user)
visit project_settings_repository_path(mirrored_project)
end
it 'delete remote mirrors' do
expect(mirrored_project.remote_mirrors.count).to eq(1)
find('.js-delete-mirror').click
wait_for_requests
expect(mirrored_project.remote_mirrors.count).to eq(0)
end
end
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