Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3834f7913d
commit
0e26adf3e6
|
@ -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 } }">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)"
|
||||||
|
|
|
@ -18,7 +18,12 @@
|
||||||
= _('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?
|
||||||
|
= form.label :fingerprint, _('Fingerprint (SHA256)'), class: 'col-form-label col-sm-2'
|
||||||
|
.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
|
.col-sm-10
|
||||||
= form.text_field :fingerprint, class: 'form-control gl-form-input', readonly: 'readonly'
|
= form.text_field :fingerprint, class: 'form-control gl-form-input', readonly: 'readonly'
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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...)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue