Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-09 12:08:17 +00:00
parent 3834f7913d
commit 0e26adf3e6
11 changed files with 119 additions and 32 deletions

View File

@ -34,9 +34,13 @@ export default {
key: 'title', key: 'title',
label: __('Title'), label: __('Title'),
}, },
{
key: 'fingerprint_sha256',
label: __('Fingerprint (SHA256)'),
},
{ {
key: 'fingerprint', key: 'fingerprint',
label: __('Fingerprint'), label: __('Fingerprint (MD5)'),
}, },
{ {
key: 'projects', key: 'projects',
@ -130,10 +134,18 @@ export default {
} }
this.items = items.map( this.items = items.map(
({ id, title, fingerprint, projects_with_write_access, created_at }) => ({ ({
id, id,
title, title,
fingerprint, fingerprint,
fingerprint_sha256,
projects_with_write_access,
created_at,
}) => ({
id,
title,
fingerprint,
fingerprint_sha256,
projects: projects_with_write_access, projects: projects_with_write_access,
created: created_at, created: created_at,
}), }),
@ -196,8 +208,12 @@ export default {
> >
</template> </template>
<template #cell(fingerprint_sha256)="{ item: { fingerprint_sha256 } }">
<span v-if="fingerprint_sha256" class="monospace">{{ fingerprint_sha256 }}</span>
</template>
<template #cell(fingerprint)="{ item: { fingerprint } }"> <template #cell(fingerprint)="{ item: { fingerprint } }">
<code>{{ fingerprint }}</code> <span v-if="fingerprint" class="monospace">{{ fingerprint }}</span>
</template> </template>
<template #cell(created)="{ item: { created } }"> <template #cell(created)="{ item: { created } }">

View File

@ -115,10 +115,20 @@ export default {
<div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Deploy key') }}</div> <div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Deploy key') }}</div>
<div class="table-mobile-content" data-qa-selector="key_container"> <div class="table-mobile-content" data-qa-selector="key_container">
<strong class="title" data-qa-selector="key_title_content"> {{ deployKey.title }} </strong> <strong class="title" data-qa-selector="key_title_content"> {{ deployKey.title }} </strong>
<div class="fingerprint" data-qa-selector="key_md5_fingerprint_content"> <dl>
{{ __('MD5') }}:{{ deployKey.fingerprint }} <dt>{{ __('SHA256') }}</dt>
</div> <dd class="fingerprint" data-qa-selector="key_sha256_fingerprint_content">
<div class="fingerprint">{{ __('SHA256') }}:{{ deployKey.fingerprint_sha256 }}</div> {{ deployKey.fingerprint_sha256 }}
</dd>
<template v-if="deployKey.fingerprint">
<dt>
{{ __('MD5') }}
</dt>
<dd class="fingerprint" data-qa-selector="key_md5_fingerprint_content">
{{ deployKey.fingerprint }}
</dd>
</template>
</dl>
</div> </div>
</div> </div>
<div class="table-section section-30 section-wrap"> <div class="table-section section-30 section-wrap">

View File

@ -441,11 +441,13 @@ export default class AccessDropdown {
const { const {
id, id,
fingerprint, fingerprint,
fingerprint_sha256: fingerprintSha256,
title, title,
owner: { avatar_url, name, username }, owner: { avatar_url, name, username },
} = response; } = response;
const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`; const availableFingerprint = fingerprintSha256 || fingerprint;
const shortFingerprint = `(${availableFingerprint.substring(0, 14)}...)`;
return { return {
id, id,

View File

@ -203,11 +203,13 @@ export default {
const { const {
id, id,
fingerprint, fingerprint,
fingerprint_sha256: fingerprintSha256,
title, title,
owner: { avatar_url, name, username }, owner: { avatar_url, name, username },
} = response; } = response;
const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`; const availableFingerprint = fingerprintSha256 || fingerprint;
const shortFingerprint = `(${availableFingerprint.substring(0, 14)}...)`;
return { return {
id, id,
@ -387,7 +389,7 @@ export default {
}}</gl-dropdown-section-header> }}</gl-dropdown-section-header>
<gl-dropdown-item <gl-dropdown-item
v-for="key in deployKeys" v-for="key in deployKeys"
:key="`${key.id}${key.fingerprint}`" :key="`${key.id}-{key.title}`"
data-testid="deploy_key-dropdown-item" data-testid="deploy_key-dropdown-item"
is-check-item is-check-item
:is-checked="isSelected(key)" :is-checked="isSelected(key)"

View File

@ -18,9 +18,14 @@
= _('Paste a public key here. %{link_start}How do I generate it?%{link_end}').html_safe % { link_start: link_start, link_end: link_end.html_safe } = _('Paste a public key here. %{link_start}How do I generate it?%{link_end}').html_safe % { link_start: link_start, link_end: link_end.html_safe }
= form.text_area :key, class: 'form-control gl-form-input thin_area', rows: 5, data: { qa_selector: 'deploy_key_field' } = form.text_area :key, class: 'form-control gl-form-input thin_area', rows: 5, data: { qa_selector: 'deploy_key_field' }
- else - else
= form.label :fingerprint, class: 'col-form-label col-sm-2' - if deploy_key.fingerprint_sha256.present?
.col-sm-10 = form.label :fingerprint, _('Fingerprint (SHA256)'), class: 'col-form-label col-sm-2'
= form.text_field :fingerprint, class: 'form-control gl-form-input', readonly: 'readonly' .col-sm-10
= form.text_field :fingerprint_sha256, class: 'form-control gl-form-input', readonly: 'readonly'
- if deploy_key.fingerprint.present?
= form.label :fingerprint, _('Fingerprint (MD5)'), class: 'col-form-label col-sm-2'
.col-sm-10
= form.text_field :fingerprint, class: 'form-control gl-form-input', readonly: 'readonly'
- if deploy_keys_project.present? - if deploy_keys_project.present?
= form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form| = form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form|

View File

@ -16371,7 +16371,10 @@ msgstr ""
msgid "Find file" msgid "Find file"
msgstr "" msgstr ""
msgid "Fingerprint" msgid "Fingerprint (MD5)"
msgstr ""
msgid "Fingerprint (SHA256)"
msgstr "" msgstr ""
msgid "Fingerprints" msgid "Fingerprints"

View File

@ -20,6 +20,12 @@ FactoryBot.define do
expires_at { Date.today.beginning_of_day + 3.hours } expires_at { Date.today.beginning_of_day + 3.hours }
end end
trait :without_md5_fingerprint do
after(:create) do |key|
key.update_column(:fingerprint, nil)
end
end
factory :key_without_comment do factory :key_without_comment do
key { SSHData::PrivateKey::RSA.generate(3072, unsafe_allow_small_key: true).public_key.openssh } key { SSHData::PrivateKey::RSA.generate(3072, unsafe_allow_small_key: true).public_key.openssh }
end end

View File

@ -3,22 +3,38 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Project deploy keys', :js do RSpec.describe 'Project deploy keys', :js do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:project) { create(:project_empty_repo) } let_it_be(:project) { create(:project_empty_repo) }
let_it_be(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
let_it_be(:deploy_key) { deploy_keys_project.deploy_key }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
context 'editing key' do
it 'shows fingerprints' do
visit edit_project_deploy_key_path(project, deploy_key)
expect(page).to have_content('Fingerprint (SHA256)')
expect(find('#deploy_key_fingerprint_sha256').value).to eq(deploy_key.fingerprint_sha256)
if Gitlab::FIPS.enabled?
expect(page).not_to have_content('Fingerprint (MD5)')
else
expect(page).to have_content('Fingerprint (MD5)')
expect(find('#deploy_key_fingerprint').value).to eq(deploy_key.fingerprint)
end
end
end
describe 'removing key' do describe 'removing key' do
before do before do
create(:deploy_keys_project, project: project) visit project_settings_repository_path(project)
end end
it 'removes association between project and deploy key' do it 'removes association between project and deploy key' do
visit project_settings_repository_path(project)
page.within(find('.rspec-deploy-keys-settings')) do page.within(find('.rspec-deploy-keys-settings')) do
expect(page).to have_selector('.deploy-key', count: 1) expect(page).to have_selector('.deploy-key', count: 1)

View File

@ -27,6 +27,7 @@ describe('DeployKeysTable', () => {
const deployKey = responseBody[0]; const deployKey = responseBody[0];
const deployKey2 = responseBody[1]; const deployKey2 = responseBody[1];
const deployKeyWithoutMd5Fingerprint = responseBody[2];
const createComponent = (provide = {}) => { const createComponent = (provide = {}) => {
wrapper = mountExtended(DeployKeysTable, { wrapper = mountExtended(DeployKeysTable, {
@ -57,9 +58,10 @@ describe('DeployKeysTable', () => {
const timeAgoTooltip = findTimeAgoTooltip(expectedRowIndex); const timeAgoTooltip = findTimeAgoTooltip(expectedRowIndex);
expect(wrapper.findByText(expectedDeployKey.title).exists()).toBe(true); expect(wrapper.findByText(expectedDeployKey.title).exists()).toBe(true);
expect(wrapper.findByText(expectedDeployKey.fingerprint, { selector: 'code' }).exists()).toBe(
true, expect(
); wrapper.findByText(expectedDeployKey.fingerprint_sha256, { selector: 'span' }).exists(),
).toBe(true);
expect(timeAgoTooltip.exists()).toBe(true); expect(timeAgoTooltip.exists()).toBe(true);
expect(timeAgoTooltip.props('time')).toBe(expectedDeployKey.created_at); expect(timeAgoTooltip.props('time')).toBe(expectedDeployKey.created_at);
expect(editButton.exists()).toBe(true); expect(editButton.exists()).toBe(true);
@ -67,6 +69,13 @@ describe('DeployKeysTable', () => {
expect(findRemoveButton(expectedRowIndex).exists()).toBe(true); expect(findRemoveButton(expectedRowIndex).exists()).toBe(true);
}; };
const expectDeployKeyWithFingerprintIsRendered = (expectedDeployKey, expectedRowIndex) => {
expect(wrapper.findByText(expectedDeployKey.fingerprint, { selector: 'span' }).exists()).toBe(
true,
);
expectDeployKeyIsRendered(expectedDeployKey, expectedRowIndex);
};
const itRendersTheEmptyState = () => { const itRendersTheEmptyState = () => {
it('renders empty state', () => { it('renders empty state', () => {
const emptyState = wrapper.findComponent(GlEmptyState); const emptyState = wrapper.findComponent(GlEmptyState);
@ -127,8 +136,12 @@ describe('DeployKeysTable', () => {
}); });
it('renders deploy keys in table', () => { it('renders deploy keys in table', () => {
expectDeployKeyIsRendered(deployKey, 0); expectDeployKeyWithFingerprintIsRendered(deployKey, 0);
expectDeployKeyIsRendered(deployKey2, 1); expectDeployKeyWithFingerprintIsRendered(deployKey2, 1);
});
it('renders deploy keys that do not have an MD5 fingerprint', () => {
expectDeployKeyIsRendered(deployKeyWithoutMd5Fingerprint, 2);
}); });
describe('when delete button is clicked', () => { describe('when delete button is clicked', () => {
@ -157,7 +170,7 @@ describe('DeployKeysTable', () => {
beforeEach(() => { beforeEach(() => {
Api.deployKeys.mockResolvedValueOnce({ Api.deployKeys.mockResolvedValueOnce({
data: [deployKey], data: [deployKey],
headers: { 'x-total': '2' }, headers: { 'x-total': '3' },
}); });
createComponent(); createComponent();
@ -179,7 +192,7 @@ describe('DeployKeysTable', () => {
describe('when pagination is changed', () => { describe('when pagination is changed', () => {
it('calls API with `page` parameter', async () => { it('calls API with `page` parameter', async () => {
const pagination = findPagination(); const pagination = findPagination();
expectDeployKeyIsRendered(deployKey, 0); expectDeployKeyWithFingerprintIsRendered(deployKey, 0);
Api.deployKeys.mockResolvedValue({ Api.deployKeys.mockResolvedValue({
data: [deployKey2], data: [deployKey2],
@ -199,7 +212,7 @@ describe('DeployKeysTable', () => {
page: 2, page: 2,
public: true, public: true,
}); });
expectDeployKeyIsRendered(deployKey2, 0); expectDeployKeyWithFingerprintIsRendered(deployKey2, 0);
}); });
}); });
}); });

View File

@ -11,6 +11,7 @@ RSpec.describe API::DeployKeys, '(JavaScript fixtures)', type: :request do
let_it_be(:project2) { create(:project) } let_it_be(:project2) { create(:project) }
let_it_be(:deploy_key) { create(:deploy_key, public: true) } let_it_be(:deploy_key) { create(:deploy_key, public: true) }
let_it_be(:deploy_key2) { create(:deploy_key, public: true) } let_it_be(:deploy_key2) { create(:deploy_key, public: true) }
let_it_be(:deploy_key_without_fingerprint) { create(:deploy_key, :without_md5_fingerprint, public: true) }
let_it_be(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) } let_it_be(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) }
let_it_be(:deploy_keys_project2) { create(:deploy_keys_project, :write_access, project: project2, deploy_key: deploy_key) } let_it_be(:deploy_keys_project2) { create(:deploy_keys_project, :write_access, project: project2, deploy_key: deploy_key) }
let_it_be(:deploy_keys_project3) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key2) } let_it_be(:deploy_keys_project3) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key2) }

View File

@ -29,9 +29,20 @@ jest.mock('~/projects/settings/api/access_dropdown_api', () => ({
}), }),
getDeployKeys: jest.fn().mockResolvedValue({ getDeployKeys: jest.fn().mockResolvedValue({
data: [ data: [
{ id: 10, title: 'key10', fingerprint: 'abcdefghijklmnop', owner: { name: 'user1' } }, {
{ id: 11, title: 'key11', fingerprint: 'abcdefghijklmnop', owner: { name: 'user2' } }, id: 10,
{ id: 12, title: 'key12', fingerprint: 'abcdefghijklmnop', owner: { name: 'user3' } }, title: 'key10',
fingerprint: 'md5-abcdefghijklmnop',
fingerprint_sha256: 'sha256-abcdefghijklmnop',
owner: { name: 'user1' },
},
{
id: 11,
title: 'key11',
fingerprint_sha256: 'sha256-abcdefghijklmnop',
owner: { name: 'user2' },
},
{ id: 12, title: 'key12', fingerprint: 'md5-abcdefghijklmnop', owner: { name: 'user3' } },
], ],
}), }),
})); }));
@ -279,6 +290,7 @@ describe('Access Level Dropdown', () => {
{ id: 115, type: 'group', group_id: 5 }, { id: 115, type: 'group', group_id: 5 },
{ id: 118, type: 'user', user_id: 8, name: 'user2' }, { id: 118, type: 'user', user_id: 8, name: 'user2' },
{ id: 121, type: 'deploy_key', deploy_key_id: 11 }, { id: 121, type: 'deploy_key', deploy_key_id: 11 },
{ id: 122, type: 'deploy_key', deploy_key_id: 12 },
]; ];
const findSelected = (type) => const findSelected = (type) =>
@ -309,8 +321,9 @@ describe('Access Level Dropdown', () => {
it('should set selected deploy keys as intersection between the server response and preselected mapping some keys', () => { it('should set selected deploy keys as intersection between the server response and preselected mapping some keys', () => {
const selectedDeployKeys = findSelected(LEVEL_TYPES.DEPLOY_KEY); const selectedDeployKeys = findSelected(LEVEL_TYPES.DEPLOY_KEY);
expect(selectedDeployKeys).toHaveLength(1); expect(selectedDeployKeys).toHaveLength(2);
expect(selectedDeployKeys.at(0).text()).toContain('key11 (abcdefghijklmn...)'); expect(selectedDeployKeys.at(0).text()).toContain('key11 (sha256-abcdefg...)');
expect(selectedDeployKeys.at(1).text()).toContain('key12 (md5-abcdefghij...)');
}); });
}); });