New upstream version 10.7.5+dfsg
This commit is contained in:
parent
dd13d6ff10
commit
2d79e65d03
21 changed files with 168 additions and 29 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -2,6 +2,22 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 10.7.5 (2018-05-28)
|
||||||
|
|
||||||
|
### Security (3 changes)
|
||||||
|
|
||||||
|
- Prevent user passwords from being changed without providing the previous password.
|
||||||
|
- Fix API to remove deploy key from project instead of deleting it entirely.
|
||||||
|
- Fixed bug that allowed importing arbitrary project attributes.
|
||||||
|
|
||||||
|
|
||||||
|
## 10.7.4 (2018-05-21)
|
||||||
|
|
||||||
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- Fix error when deleting an empty list of refs.
|
||||||
|
|
||||||
|
|
||||||
## 10.7.3 (2018-05-02)
|
## 10.7.3 (2018-05-02)
|
||||||
|
|
||||||
### Fixed (8 changes)
|
### Fixed (8 changes)
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -421,7 +421,7 @@ end
|
||||||
|
|
||||||
# Gitaly GRPC client
|
# Gitaly GRPC client
|
||||||
gem 'gitaly-proto', '~> 0.96.0', require: 'gitaly'
|
gem 'gitaly-proto', '~> 0.96.0', require: 'gitaly'
|
||||||
gem 'grpc', '~> 1.10.0'
|
gem 'grpc', '~> 1.11.0'
|
||||||
|
|
||||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||||
gem 'google-protobuf', '= 3.5.1'
|
gem 'google-protobuf', '= 3.5.1'
|
||||||
|
|
|
@ -374,7 +374,7 @@ GEM
|
||||||
rake
|
rake
|
||||||
grape_logging (1.7.0)
|
grape_logging (1.7.0)
|
||||||
grape
|
grape
|
||||||
grpc (1.10.0)
|
grpc (1.11.0)
|
||||||
google-protobuf (~> 3.1)
|
google-protobuf (~> 3.1)
|
||||||
googleapis-common-protos-types (~> 1.0.0)
|
googleapis-common-protos-types (~> 1.0.0)
|
||||||
googleauth (>= 0.5.1, < 0.7)
|
googleauth (>= 0.5.1, < 0.7)
|
||||||
|
@ -1074,7 +1074,7 @@ DEPENDENCIES
|
||||||
grape-entity (~> 0.6.0)
|
grape-entity (~> 0.6.0)
|
||||||
grape-route-helpers (~> 2.1.0)
|
grape-route-helpers (~> 2.1.0)
|
||||||
grape_logging (~> 1.7)
|
grape_logging (~> 1.7)
|
||||||
grpc (~> 1.10.0)
|
grpc (~> 1.11.0)
|
||||||
haml_lint (~> 0.26.0)
|
haml_lint (~> 0.26.0)
|
||||||
hamlit (~> 2.6.1)
|
hamlit (~> 2.6.1)
|
||||||
hashie-forbidden_attributes
|
hashie-forbidden_attributes
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
10.7.3
|
10.7.5
|
||||||
|
|
|
@ -93,8 +93,6 @@ class ProfilesController < Profiles::ApplicationController
|
||||||
:linkedin,
|
:linkedin,
|
||||||
:location,
|
:location,
|
||||||
:name,
|
:name,
|
||||||
:password,
|
|
||||||
:password_confirmation,
|
|
||||||
:public_email,
|
:public_email,
|
||||||
:skype,
|
:skype,
|
||||||
:twitter,
|
:twitter,
|
||||||
|
|
|
@ -148,10 +148,10 @@ module API
|
||||||
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
|
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
|
||||||
end
|
end
|
||||||
delete ":id/deploy_keys/:key_id" do
|
delete ":id/deploy_keys/:key_id" do
|
||||||
key = user_project.deploy_keys.find(params[:key_id])
|
deploy_key_project = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
|
||||||
not_found!('Deploy Key') unless key
|
not_found!('Deploy Key') unless deploy_key_project
|
||||||
|
|
||||||
destroy_conditionally!(key)
|
destroy_conditionally!(deploy_key_project)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2339,7 +2339,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def gitaly_delete_refs(*ref_names)
|
def gitaly_delete_refs(*ref_names)
|
||||||
gitaly_ref_client.delete_refs(refs: ref_names)
|
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
|
||||||
end
|
end
|
||||||
|
|
||||||
def rugged_remove_remote(remote_name)
|
def rugged_remove_remote(remote_name)
|
||||||
|
|
|
@ -7,14 +7,15 @@ module Gitlab
|
||||||
new(*args).clean
|
new(*args).clean
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(relation_hash:, relation_class:)
|
def initialize(relation_hash:, relation_class:, excluded_keys: [])
|
||||||
@relation_hash = relation_hash
|
@relation_hash = relation_hash
|
||||||
@relation_class = relation_class
|
@relation_class = relation_class
|
||||||
|
@excluded_keys = excluded_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def clean
|
def clean
|
||||||
@relation_hash.reject do |key, _value|
|
@relation_hash.reject do |key, _value|
|
||||||
prohibited_key?(key) || !@relation_class.attribute_method?(key)
|
prohibited_key?(key) || !@relation_class.attribute_method?(key) || excluded_key?(key)
|
||||||
end.except('id')
|
end.except('id')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -23,6 +24,12 @@ module Gitlab
|
||||||
def prohibited_key?(key)
|
def prohibited_key?(key)
|
||||||
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
|
key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def excluded_key?(key)
|
||||||
|
return false if @excluded_keys.empty?
|
||||||
|
|
||||||
|
@excluded_keys.include?(key)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,10 @@ module Gitlab
|
||||||
@methods[key].nil? ? {} : { methods: @methods[key] }
|
@methods[key].nil? ? {} : { methods: @methods[key] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_excluded_keys(klass_name)
|
||||||
|
@excluded_attributes[klass_name.to_sym]&.map(&:to_s) || []
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_attributes_only(value)
|
def find_attributes_only(value)
|
||||||
|
|
|
@ -97,8 +97,6 @@ excluded_attributes:
|
||||||
- :import_jid
|
- :import_jid
|
||||||
- :created_at
|
- :created_at
|
||||||
- :updated_at
|
- :updated_at
|
||||||
- :import_jid
|
|
||||||
- :import_jid
|
|
||||||
- :id
|
- :id
|
||||||
- :star_count
|
- :star_count
|
||||||
- :last_activity_at
|
- :last_activity_at
|
||||||
|
|
|
@ -88,16 +88,18 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_params
|
def project_params
|
||||||
@project_params ||= json_params.merge(override_params)
|
@project_params ||= begin
|
||||||
|
attrs = json_params.merge(override_params)
|
||||||
|
|
||||||
|
# Cleaning all imported and overridden params
|
||||||
|
Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: attrs,
|
||||||
|
relation_class: Project,
|
||||||
|
excluded_keys: excluded_keys_for_relation(:project))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def override_params
|
def override_params
|
||||||
return {} unless params = @project.import_data&.data&.fetch('override_params', nil)
|
@override_params ||= @project.import_data&.data&.fetch('override_params', nil) || {}
|
||||||
|
|
||||||
@override_params ||= params.select do |key, _value|
|
|
||||||
Project.column_names.include?(key.to_s) &&
|
|
||||||
!reader.project_tree[:except].include?(key.to_sym)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def json_params
|
def json_params
|
||||||
|
@ -171,7 +173,8 @@ module Gitlab
|
||||||
relation_hash: parsed_relation_hash(relation_hash, relation.to_sym),
|
relation_hash: parsed_relation_hash(relation_hash, relation.to_sym),
|
||||||
members_mapper: members_mapper,
|
members_mapper: members_mapper,
|
||||||
user: @user,
|
user: @user,
|
||||||
project: @restored_project)
|
project: @restored_project,
|
||||||
|
excluded_keys: excluded_keys_for_relation(relation))
|
||||||
end.compact
|
end.compact
|
||||||
|
|
||||||
relation_hash_list.is_a?(Array) ? relation_array : relation_array.first
|
relation_hash_list.is_a?(Array) ? relation_array : relation_array.first
|
||||||
|
@ -192,6 +195,10 @@ module Gitlab
|
||||||
def reader
|
def reader
|
||||||
@reader ||= Gitlab::ImportExport::Reader.new(shared: @shared)
|
@reader ||= Gitlab::ImportExport::Reader.new(shared: @shared)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def excluded_keys_for_relation(relation)
|
||||||
|
@reader.attributes_finder.find_excluded_keys(relation)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module ImportExport
|
module ImportExport
|
||||||
class Reader
|
class Reader
|
||||||
attr_reader :tree
|
attr_reader :tree, :attributes_finder
|
||||||
|
|
||||||
def initialize(shared:)
|
def initialize(shared:)
|
||||||
@shared = shared
|
@shared = shared
|
||||||
|
|
|
@ -35,13 +35,21 @@ module Gitlab
|
||||||
new(*args).create
|
new(*args).create
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:)
|
def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: [])
|
||||||
@relation_name = OVERRIDES[relation_sym] || relation_sym
|
@relation_name = OVERRIDES[relation_sym] || relation_sym
|
||||||
@relation_hash = relation_hash.except('noteable_id')
|
@relation_hash = relation_hash.except('noteable_id')
|
||||||
@members_mapper = members_mapper
|
@members_mapper = members_mapper
|
||||||
@user = user
|
@user = user
|
||||||
@project = project
|
@project = project
|
||||||
@imported_object_retries = 0
|
@imported_object_retries = 0
|
||||||
|
|
||||||
|
# Remove excluded keys from relation_hash
|
||||||
|
# We don't do this in the parsed_relation_hash because of the 'transformed attributes'
|
||||||
|
# For example, MergeRequestDiffFiles exports its diff attribute as utf8_diff. Then,
|
||||||
|
# in the create method that attribute is renamed to diff. And because diff is an excluded key,
|
||||||
|
# if we clean the excluded keys in the parsed_relation_hash, it will be removed
|
||||||
|
# from the object attributes and the export will fail.
|
||||||
|
@relation_hash.except!(*excluded_keys)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates an object from an actual model with name "relation_sym" with params from
|
# Creates an object from an actual model with name "relation_sym" with params from
|
||||||
|
|
|
@ -3,6 +3,19 @@ require('spec_helper')
|
||||||
describe ProfilesController, :request_store do
|
describe ProfilesController, :request_store do
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
describe 'POST update' do
|
||||||
|
it 'does not update password' do
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
post :update,
|
||||||
|
user: { password: 'hello12345', password_confirmation: 'hello12345' }
|
||||||
|
end.not_to change { user.reload.encrypted_password }
|
||||||
|
|
||||||
|
expect(response.status).to eq(302)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'PUT update' do
|
describe 'PUT update' do
|
||||||
it 'allows an email update from a user without an external email address' do
|
it 'allows an email update from a user without an external email address' do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
|
@ -600,6 +600,10 @@ describe Gitlab::Git::Repository, seed_helper: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not fail when deleting an empty list of refs' do
|
||||||
|
expect { repo.delete_refs(*[]) }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
it 'raises an error if it failed' do
|
it 'raises an error if it failed' do
|
||||||
expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
|
expect { repo.delete_refs('refs\heads\fix') }.to raise_error(Gitlab::Git::Repository::GitError)
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,7 +15,10 @@ describe Gitlab::ImportExport::AttributeCleaner do
|
||||||
'project_id' => 99,
|
'project_id' => 99,
|
||||||
'user_id' => 99,
|
'user_id' => 99,
|
||||||
'random_id_in_the_middle' => 99,
|
'random_id_in_the_middle' => 99,
|
||||||
'notid' => 99
|
'notid' => 99,
|
||||||
|
'import_source' => 'whatever',
|
||||||
|
'import_type' => 'whatever',
|
||||||
|
'non_existent_attr' => 'whatever'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -28,10 +31,30 @@ describe Gitlab::ImportExport::AttributeCleaner do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:excluded_keys) { %w[import_source import_type] }
|
||||||
|
|
||||||
|
subject { described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class, excluded_keys: excluded_keys) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(relation_class).to receive(:attribute_method?).and_return(true)
|
||||||
|
allow(relation_class).to receive(:attribute_method?).with('non_existent_attr').and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
it 'removes unwanted attributes from the hash' do
|
it 'removes unwanted attributes from the hash' do
|
||||||
# allow(relation_class).to receive(:attribute_method?).and_return(true)
|
expect(subject).to eq(post_safe_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes attributes not present in relation_class' do
|
||||||
|
expect(subject.keys).not_to include 'non_existent_attr'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes excluded keys from the hash' do
|
||||||
|
expect(subject.keys).not_to include excluded_keys
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not remove excluded key if not listed' do
|
||||||
parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
|
parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
|
||||||
|
|
||||||
expect(parsed_hash).to eq(post_safe_hash)
|
expect(parsed_hash.keys).to eq post_safe_hash.keys + excluded_keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
|
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
|
||||||
|
"import_type": "gitlab_project",
|
||||||
|
"creator_id": 123,
|
||||||
"visibility_level": 10,
|
"visibility_level": 10,
|
||||||
"archived": false,
|
"archived": false,
|
||||||
"labels": [
|
"labels": [
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
|
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
|
||||||
|
"import_type": "gitlab_project",
|
||||||
|
"creator_id": 123,
|
||||||
"visibility_level": 10,
|
"visibility_level": 10,
|
||||||
"archived": false,
|
"archived": false,
|
||||||
"milestones": [
|
"milestones": [
|
||||||
|
|
|
@ -23,6 +23,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
|
||||||
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
|
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
|
||||||
|
|
||||||
project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project)
|
project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project)
|
||||||
|
|
||||||
|
expect(Gitlab::ImportExport::RelationFactory).to receive(:create).with(hash_including(excluded_keys: ['whatever'])).and_call_original.at_least(:once)
|
||||||
|
allow(project_tree_restorer).to receive(:excluded_keys_for_relation).and_return(['whatever'])
|
||||||
|
|
||||||
@restored_project_json = project_tree_restorer.restore
|
@restored_project_json = project_tree_restorer.restore
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -248,6 +252,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
|
||||||
expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0))
|
expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0))
|
||||||
expect(labels.where(type: "ProjectLabel").where.not(group_id: nil).count).to eq(0)
|
expect(labels.where(type: "ProjectLabel").where.not(group_id: nil).count).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not set params that are excluded from import_export settings' do
|
||||||
|
expect(project.import_type).to be_nil
|
||||||
|
expect(project.creator_id).not_to eq 123
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'restores group correctly' do |**results|
|
shared_examples 'restores group correctly' do |**results|
|
||||||
|
|
|
@ -4,12 +4,14 @@ describe Gitlab::ImportExport::RelationFactory do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:members_mapper) { double('members_mapper').as_null_object }
|
let(:members_mapper) { double('members_mapper').as_null_object }
|
||||||
let(:user) { create(:admin) }
|
let(:user) { create(:admin) }
|
||||||
|
let(:excluded_keys) { [] }
|
||||||
let(:created_object) do
|
let(:created_object) do
|
||||||
described_class.create(relation_sym: relation_sym,
|
described_class.create(relation_sym: relation_sym,
|
||||||
relation_hash: relation_hash,
|
relation_hash: relation_hash,
|
||||||
members_mapper: members_mapper,
|
members_mapper: members_mapper,
|
||||||
user: user,
|
user: user,
|
||||||
project: project)
|
project: project,
|
||||||
|
excluded_keys: excluded_keys)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'hook object' do
|
context 'hook object' do
|
||||||
|
@ -67,6 +69,14 @@ describe Gitlab::ImportExport::RelationFactory do
|
||||||
expect(created_object.service_id).not_to eq(service_id)
|
expect(created_object.service_id).not_to eq(service_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'excluded attributes' do
|
||||||
|
let(:excluded_keys) { %w[url] }
|
||||||
|
|
||||||
|
it 'are removed from the imported object' do
|
||||||
|
expect(created_object.url).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Mocks an ActiveRecordish object with the dodgy columns
|
# Mocks an ActiveRecordish object with the dodgy columns
|
||||||
|
|
|
@ -171,7 +171,7 @@ describe API::DeployKeys do
|
||||||
deploy_key
|
deploy_key
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'deletes existing key' do
|
it 'removes existing key from project' do
|
||||||
expect do
|
expect do
|
||||||
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
|
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
|
||||||
|
|
||||||
|
@ -179,6 +179,44 @@ describe API::DeployKeys do
|
||||||
end.to change { project.deploy_keys.count }.by(-1)
|
end.to change { project.deploy_keys.count }.by(-1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the deploy key is public' do
|
||||||
|
it 'does not delete the deploy key' do
|
||||||
|
expect do
|
||||||
|
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(204)
|
||||||
|
end.not_to change { DeployKey.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the deploy key is not public' do
|
||||||
|
let!(:deploy_key) { create(:deploy_key, public: false) }
|
||||||
|
|
||||||
|
context 'when the deploy key is only used by this project' do
|
||||||
|
it 'deletes the deploy key' do
|
||||||
|
expect do
|
||||||
|
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(204)
|
||||||
|
end.to change { DeployKey.count }.by(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the deploy key is used by other projects' do
|
||||||
|
before do
|
||||||
|
create(:deploy_keys_project, project: project2, deploy_key: deploy_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not delete the deploy key' do
|
||||||
|
expect do
|
||||||
|
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(204)
|
||||||
|
end.not_to change { DeployKey.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns 404 Not Found with invalid ID' do
|
it 'returns 404 Not Found with invalid ID' do
|
||||||
delete api("/projects/#{project.id}/deploy_keys/404", admin)
|
delete api("/projects/#{project.id}/deploy_keys/404", admin)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue