Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mutation to assign abilities to roles #91

Merged
merged 1 commit into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/graphql/mutations/team_roles/assign_abilities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Mutations
module TeamRoles
class AssignAbilities < BaseMutation
description 'Update the abilities a role is granted.'

field :abilities, [Types::TeamRoleAbilityEnum], description: 'The now granted abilities'

argument :abilities, [Types::TeamRoleAbilityEnum],
description: 'The abilities that should be granted to the ability'
argument :role_id, Types::GlobalIdType[::TeamRole],
description: 'The id of the role which should be granted the abilities'

def resolve(role_id:, abilities:)
role = SagittariusSchema.object_from_id(role_id)

return { abilities: nil, errors: [create_message_error('Invalid role')] } if role.nil?

::TeamRoles::AssignAbilitiesService.new(
current_user,
role,
abilities
).execute.to_mutation_response(success_key: :abilities)
end
end
end
end
5 changes: 5 additions & 0 deletions app/graphql/types/base_enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
module Types
class BaseEnum < GraphQL::Schema::Enum
include Sagittarius::Graphql::HasMarkdownDocumentation

def self.inherited(subclass)
super
subclass.graphql_name subclass.name.delete_prefix('Types::').delete_suffix('Enum').gsub('::', '')
end
end
end
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class MutationType < Types::BaseObject
mount_mutation Mutations::ApplicationSettings::Update
mount_mutation Mutations::TeamMembers::AssignRoles
mount_mutation Mutations::TeamMembers::Invite
mount_mutation Mutations::TeamRoles::AssignAbilities
mount_mutation Mutations::TeamRoles::Create
mount_mutation Mutations::Teams::Create
mount_mutation Mutations::Users::Login
Expand Down
11 changes: 11 additions & 0 deletions app/graphql/types/team_role_ability_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Types
class TeamRoleAbilityEnum < BaseEnum
description 'Represents abilities that can be granted to roles in teams.'

TeamRoleAbility::ABILITIES.each do |ability, settings|
value ability.upcase, settings[:description], value: ability, **settings.except(:db, :description)
end
end
end
5 changes: 5 additions & 0 deletions app/graphql/types/team_role_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ class TeamRoleType < BaseObject

authorize :read_team_role

field :abilities, [Types::TeamRoleAbilityEnum], null: false, description: 'The abilities the role is granted'
field :name, String, null: false, description: 'The name of this role'
field :team, Types::TeamType, null: false, description: 'The team where this role belongs to'

id_field ::TeamRole
timestamps

def abilities
object.abilities.map(&:ability)
end
end
end
1 change: 1 addition & 0 deletions app/models/audit_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class AuditEvent < ApplicationRecord
team_role_created: 5,
team_member_invited: 6,
team_member_roles_updated: 7,
team_role_abilities_updated: 8,
}.with_indifferent_access

enum :action_type, ACTION_TYPES, prefix: :action
Expand Down
9 changes: 5 additions & 4 deletions app/models/team_role_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

class TeamRoleAbility < ApplicationRecord
ABILITIES = {
create_team_role: 1,
invite_member: 2,
assign_member_roles: 3,
create_team_role: { db: 1, description: 'Allows the creation of roles in a team' },
invite_member: { db: 2, description: 'Allows to invite new members to a team' },
assign_member_roles: { db: 3, description: 'Allows to change the roles of a team member' },
assign_role_abilities: { db: 4, description: 'Allows to change the abilities of a team role' },
}.with_indifferent_access

enum :ability, ABILITIES, prefix: :can
enum :ability, ABILITIES.transform_values { |v| v[:db] }, prefix: :can

belongs_to :team_role, inverse_of: :abilities

Expand Down
1 change: 1 addition & 0 deletions app/policies/team_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ class TeamPolicy < BasePolicy
customizable_permission :create_team_role
customizable_permission :invite_member
customizable_permission :assign_member_roles
customizable_permission :assign_role_abilities
end
55 changes: 55 additions & 0 deletions app/services/team_roles/assign_abilities_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module TeamRoles
class AssignAbilitiesService
include Sagittarius::Database::Transactional

attr_reader :current_user, :role, :abilities

def initialize(current_user, role, abilities)
@current_user = current_user
@role = role
@abilities = abilities
end

def execute
team = role.team
unless Ability.allowed?(current_user, :assign_role_abilities, team)
return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission)
end

transactional do |t|
current_abilities = role.abilities
old_abilities_for_audit_event = current_abilities.map(&:ability)

current_abilities.where.not(ability: abilities).delete_all

(abilities - current_abilities.map(&:ability)).map do |ability|
team_role_ability = TeamRoleAbility.create(team_role: role, ability: ability)

next if team_role_ability.persisted?

t.rollback_and_return! ServiceResponse.error(
message: 'Failed to save team role ability',
payload: team_role_ability.errors
)
end

new_abilities = role.reload.abilities.map(&:ability)

AuditService.audit(
:team_role_abilities_updated,
author_id: current_user.id,
entity: role,
details: {
old_abilities: old_abilities_for_audit_event,
new_abilities: new_abilities,
},
target: team
)

ServiceResponse.success(message: 'Role abilities updated', payload: new_abilities)
end
end
end
end
12 changes: 12 additions & 0 deletions docs/content/graphql/enum/teamroleability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: TeamRoleAbility
---

Represents abilities that can be granted to roles in teams.

| Value | Description |
|-------|-------------|
| `ASSIGN_MEMBER_ROLES` | Allows to change the roles of a team member |
| `ASSIGN_ROLE_ABILITIES` | Allows to change the abilities of a team role |
| `CREATE_TEAM_ROLE` | Allows the creation of roles in a team |
| `INVITE_MEMBER` | Allows to invite new members to a team |
21 changes: 21 additions & 0 deletions docs/content/graphql/mutation/teamrolesassignabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: teamRolesAssignAbilities
---

Update the abilities a role is granted.

## Arguments

| Name | Type | Description |
|------|------|-------------|
| `abilities` | [`[TeamRoleAbility!]!`](../enum/teamroleability.md) | The abilities that should be granted to the ability |
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `roleId` | [`TeamRoleID!`](../scalar/teamroleid.md) | The id of the role which should be granted the abilities |

## Fields

| Name | Type | Description |
|------|------|-------------|
| `abilities` | [`[TeamRoleAbility!]`](../enum/teamroleability.md) | The now granted abilities |
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. |
1 change: 1 addition & 0 deletions docs/content/graphql/object/teamrole.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Represents a team role.

| Name | Type | Description |
|------|------|-------------|
| `abilities` | [`[TeamRoleAbility!]!`](../enum/teamroleability.md) | The abilities the role is granted |
| `createdAt` | [`Time!`](../scalar/time.md) | Time when this TeamRole was created |
| `id` | [`TeamRoleID!`](../scalar/teamroleid.md) | Global ID of this TeamRole |
| `name` | [`String!`](../scalar/string.md) | The name of this role |
Expand Down
7 changes: 7 additions & 0 deletions spec/graphql/mutations/team_roles/assign_abilities_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Mutations::TeamRoles::AssignAbilities do
it { expect(described_class.graphql_name).to eq('TeamRolesAssignAbilities') }
end
1 change: 1 addition & 0 deletions spec/graphql/types/team_role_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
id
team
name
abilities
createdAt
updatedAt
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'teamRolesAssignAbilities Mutation' do
include GraphqlHelpers

subject(:mutate!) { post_graphql mutation, variables: variables, current_user: current_user }

let(:mutation) do
<<~QUERY
mutation($input: TeamRolesAssignAbilitiesInput!) {
teamRolesAssignAbilities(input: $input) {
#{error_query}
abilities
}
}
QUERY
end

let(:team_role) { create(:team_role) }
let(:input) do
{
roleId: team_role.to_global_id.to_s,
abilities: ['CREATE_TEAM_ROLE'],
}
end

let(:variables) { { input: input } }
let(:current_user) { create(:user) }

context 'when user has permission' do
before do
stub_allowed_ability(TeamPolicy, :assign_role_abilities, user: current_user, subject: team_role.team)
end

it 'assigns the given abilities to the role' do
mutate!

abilities = graphql_data_at(:team_roles_assign_abilities, :abilities)
expect(abilities).to be_present
expect(abilities).to be_a(Array)

expect(abilities).to eq(['CREATE_TEAM_ROLE'])

is_expected.to create_audit_event(
:team_role_abilities_updated,
author_id: current_user.id,
entity_id: team_role.id,
entity_type: 'TeamRole',
details: {
'new_abilities' => ['create_team_role'],
'old_abilities' => [],
},
target_id: team_role.team.id,
target_type: 'Team'
)
end
end

context 'when user does not have permission' do
it 'returns an error' do
mutate!

expect(graphql_data_at(:team_roles_assign_abilities, :abilities)).to be_nil
expect(graphql_data_at(:team_roles_assign_abilities, :errors)).to include({ 'message' => 'missing_permission' })
end
end
end
Loading