Skip to content
Snippets Groups Projects
Commit 004b5da4 authored by Alex Buijs's avatar Alex Buijs Committed by Alexandru Croitor
Browse files

Add GraphQL support for removing namespace bans

Add destroy service and GraphQL mutation
for removing namespace bans.

Changelog: added
EE: true
parent ed9b3c33
No related branches found
No related tags found
1 merge request!92020Add GraphQL support for removing namespace bans
......@@ -15,7 +15,7 @@ class Create < ::Mutations::BaseMutation
description: 'User callout dismissed.'
def resolve(feature_name:)
callout = Users::DismissCalloutService.new(
callout = ::Users::DismissCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: feature_name }
).execute
errors = errors_on_object(callout)
......
......@@ -3845,6 +3845,25 @@ Input type: `MergeRequestUpdateInput`
| <a id="mutationmergerequestupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmergerequestupdatemergerequest"></a>`mergeRequest` | [`MergeRequest`](#mergerequest) | Merge request after mutation. |
 
### `Mutation.namespaceBanDestroy`
Input type: `NamespaceBanDestroyInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationnamespacebandestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationnamespacebandestroyid"></a>`id` | [`NamespacesNamespaceBanID!`](#namespacesnamespacebanid) | Global ID of the namespace ban to remove. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationnamespacebandestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationnamespacebandestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationnamespacebandestroynamespaceban"></a>`namespaceBan` | [`NamespaceBan`](#namespaceban) | Namespace Ban. |
### `Mutation.namespaceCiCdSettingsUpdate`
 
Input type: `NamespaceCiCdSettingsUpdateInput`
......@@ -14505,6 +14524,16 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="namespacescanexecutionpoliciesactionscantypes"></a>`actionScanTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filters policies by the action scan type. Only these scan types are supported: `dast`, `secret_detection`, `cluster_image_scanning`, `container_scanning`, `sast`. |
| <a id="namespacescanexecutionpoliciesrelationship"></a>`relationship` | [`SecurityPolicyRelationType`](#securitypolicyrelationtype) | Filter policies by the given policy relationship. |
 
### `NamespaceBan`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="namespacebanid"></a>`id` | [`GlobalID!`](#globalid) | Global ID of the namespace ban. |
| <a id="namespacebannamespace"></a>`namespace` | [`Namespace!`](#namespace) | Root namespace to which the ban applies. |
| <a id="namespacebanuser"></a>`user` | [`UserCore!`](#usercore) | User to which the namespace ban applies. |
### `NamespaceCiCdSetting`
 
#### Fields
......@@ -20814,6 +20843,12 @@ A `NamespaceID` is a global ID. It is encoded as a string.
 
An example `NamespaceID` is: `"gid://gitlab/Namespace/1"`.
 
### `NamespacesNamespaceBanID`
A `NamespacesNamespaceBanID` is a global ID. It is encoded as a string.
An example `NamespacesNamespaceBanID` is: `"gid://gitlab/Namespaces::NamespaceBan/1"`.
### `NoteID`
 
A `NoteID` is a global ID. It is encoded as a string.
......@@ -87,6 +87,7 @@ module MutationType
mount_mutation ::Mutations::Security::CiConfiguration::ConfigureDependencyScanning
mount_mutation ::Mutations::Security::CiConfiguration::ConfigureContainerScanning
mount_mutation ::Mutations::Security::TrainingProviderUpdate
mount_mutation ::Mutations::Users::Abuse::NamespaceBans::Destroy
mount_mutation ::Mutations::AuditEvents::ExternalAuditEventDestinations::Create
mount_mutation ::Mutations::AuditEvents::ExternalAuditEventDestinations::Destroy
mount_mutation ::Mutations::AuditEvents::ExternalAuditEventDestinations::Update
......
# frozen_string_literal: true
module Mutations
module Users
module Abuse
module NamespaceBans
class Destroy < BaseMutation
graphql_name 'NamespaceBanDestroy'
authorize :owner_access
argument :id, Types::GlobalIDType[::Namespaces::NamespaceBan],
required: true,
description: 'Global ID of the namespace ban to remove.'
field :namespace_ban,
Types::Namespaces::NamespaceBanType,
null: true,
description: 'Namespace Ban.'
def resolve(id:)
namespace_ban = authorized_find!(id: id)
response = ::Users::Abuse::NamespaceBans::DestroyService.new(
namespace_ban,
current_user
).execute
{
namespace_ban: response.payload[:namespace_ban],
errors: response.errors
}
end
private
def find_object(id:)
GitlabSchema.object_from_id(id, expected_class: ::Namespaces::NamespaceBan)
end
end
end
end
end
end
# frozen_string_literal: true
module Types
module Namespaces
class NamespaceBanType < BaseObject # rubocop:disable Graphql/AuthorizeTypes(Authorization is done in resolver layer)
graphql_name 'NamespaceBan'
field :id,
type: ::Types::GlobalIDType,
null: false,
description: 'Global ID of the namespace ban.'
field :namespace, NamespaceType, null: false,
description: 'Root namespace to which the ban applies.'
field :user, UserType, null: false,
description: 'User to which the namespace ban applies.'
end
end
end
# frozen_string_literal: true
module Namespaces
class NamespaceBanPolicy < BasePolicy
delegate { @subject.namespace }
end
end
# frozen_string_literal: true
module Users
module Abuse
module NamespaceBans
class DestroyService < BaseService
def initialize(namespace_ban, current_user)
@namespace_ban = namespace_ban
@current_user = current_user
end
def execute
return error_no_permissions unless allowed?
if namespace_ban.destroy
success
else
error(namespace_ban.errors.full_messages.to_sentence)
end
end
private
attr_reader :namespace_ban
def allowed?
current_user&.can?(:owner_access, namespace_ban.namespace)
end
def error(message)
ServiceResponse.error(message: message, payload: { namespace_ban: namespace_ban })
end
def success
ServiceResponse.success(payload: { namespace_ban: namespace_ban })
end
def error_no_permissions
error(_('You have insufficient permissions to remove this Namespace Ban'))
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Removing a namespace ban' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:namespace) { create(:group) }
let_it_be(:namespace_ban) { create(:namespace_ban, namespace: namespace) }
let(:mutation) do
variables = {
id: GitlabSchema.id_from_object(namespace_ban).to_s
}
graphql_mutation(:namespace_ban_destroy, variables) do
<<~QL
clientMutationId
errors
namespaceBan {
id
user {
id
}
namespace {
id
}
}
QL
end
end
def mutation_response
graphql_mutation_response(:namespace_ban_destroy)
end
before do
namespace.add_owner(user)
end
it 'removes the ban' do
post_graphql_mutation(mutation, current_user: user)
namespace_ban_response = mutation_response['namespaceBan']
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(namespace_ban_response['id']).to eq(GitlabSchema.id_from_object(namespace_ban).to_s)
expect(namespace_ban_response['user']['id']).to eq(GitlabSchema.id_from_object(namespace_ban.user).to_s)
expect(namespace_ban_response['namespace']['id']).to eq(GitlabSchema.id_from_object(namespace).to_s)
expect { namespace_ban.reload }.to raise_error ActiveRecord::RecordNotFound
end
context 'when resource is not accessible to the user' do
before do
namespace.add_maintainer(user)
end
it 'returns an error message' do
post_graphql_mutation(mutation, current_user: user)
expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you don't "\
'have permission to perform this action')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::Abuse::NamespaceBans::DestroyService do
describe '#execute' do
let_it_be(:user_with_permissions) { create(:user) }
let_it_be(:user_without_permissions) { create(:user) }
let_it_be(:namespace) { create(:group) }
let!(:namespace_ban) { create(:namespace_ban, namespace: namespace) }
let(:current_user) { user_with_permissions }
let(:service) { described_class.new(namespace_ban, current_user) }
before_all do
namespace.add_maintainer(user_without_permissions)
namespace.add_owner(user_with_permissions)
end
describe '#execute' do
shared_examples 'error response' do |message|
it 'has an informative message' do
expect(response).to be_error
expect(response.message).to eq(message)
expect(response.payload[:namespace_ban]).to eq(namespace_ban)
expect { namespace_ban.reload }.not_to raise_error
end
end
subject(:response) { service.execute }
context 'when the current_user is anonymous' do
let(:current_user) { nil }
it_behaves_like 'error response', 'You have insufficient permissions to remove this Namespace Ban'
end
context 'when current_user does not have permission to create integrations' do
let(:current_user) { user_without_permissions }
it_behaves_like 'error response', 'You have insufficient permissions to remove this Namespace Ban'
end
context 'when an error occurs during removal' do
before do
allow(namespace_ban).to receive(:destroy).and_return(false)
namespace_ban.errors.add(:base, 'Ban cannot be removed')
end
it_behaves_like 'error response', 'Ban cannot be removed'
end
it 'successfully deletes and returns the namespace_ban' do
expect(response).to be_success
expect(response.payload[:namespace_ban]).to eq(namespace_ban)
expect { namespace_ban.reload }.to raise_error ActiveRecord::RecordNotFound
end
end
end
end
......@@ -44396,6 +44396,9 @@ msgstr ""
msgid "You have insufficient permissions to remove this HTTP integration"
msgstr ""
 
msgid "You have insufficient permissions to remove this Namespace Ban"
msgstr ""
msgid "You have insufficient permissions to set customer relations contacts for this issue"
msgstr ""
 
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment