have authorization in topics representer

pull/7896/head
ulferts 5 years ago
parent 6c4e572f4e
commit e594f76cc6
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 10
      app/contracts/work_packages/base_contract.rb
  2. 2
      app/services/work_packages/set_attributes_service.rb
  3. 1
      modules/bcf/app/controllers/bcf/api/v2_1/project_extensions/api.rb
  4. 19
      modules/bcf/app/representers/bcf/api/v2_1/project_extensions/definitions.rb
  5. 55
      modules/bcf/app/representers/bcf/api/v2_1/topics/authorization_representer.rb
  6. 7
      modules/bcf/app/representers/bcf/api/v2_1/topics/single_representer.rb
  7. 18
      modules/bcf/spec/representers/bcf/api/v2_1/project_extensions/definitions_spec.rb
  8. 58
      modules/bcf/spec/representers/bcf/api/v2_1/topics/single_representer_rendering_spec.rb
  9. 4
      modules/bcf/spec/requests/api/bcf/v2_1/project_extensions_api_spec.rb
  10. 147
      modules/bcf/spec/requests/api/bcf/v2_1/topics_api_spec.rb

@ -144,7 +144,7 @@ module WorkPackages
ret
end
def assignable_statuses
def assignable_statuses(include_default = false)
# Do not allow skipping statuses without intermediately saving the work package.
# We therefore take the original status of the work_package, while preserving all
# other changes to it (e.g. type, assignee, etc.)
@ -154,7 +154,11 @@ module WorkPackages
model.status
end
new_statuses_allowed_from(status)
statuses = new_statuses_allowed_from(status)
statuses = statuses.or(Status.where_default) if include_default
statuses.order_by_position
end
def assignable_types
@ -365,7 +369,7 @@ module WorkPackages
statuses = statuses.where(is_closed: false) if model.blocked?
statuses.order_by_position
statuses
end
def closed_version_and_status?(status = model.status)

@ -216,6 +216,6 @@ class WorkPackages::SetAttributesService < ::BaseServices::SetAttributes
end
def assignable_statuses
instantiate_contract(work_package, user).assignable_statuses.or(Status.where_default.order_by_position)
instantiate_contract(work_package, user).assignable_statuses(true)
end
end

@ -31,7 +31,6 @@
module Bcf::API::V2_1
module ProjectExtensions
class API < ::API::OpenProjectAPI
get :extensions do
mapper = Definitions.new(project: @project, user: current_user)
Representer.new(mapper)

@ -37,26 +37,23 @@ module Bcf::API::V2_1
end
def topic_type
project.types.pluck(:name)
contract.assignable_types.pluck(:name)
end
##
# We only return the default status for now
# since that can always be set to a new issue
def topic_status
Status
.where_default
.pluck(:name)
contract.assignable_statuses(true).pluck(:name)
end
def priority
OpenProject::Cache.fetch(IssuePriority.all.cache_key, 'names') do
IssuePriority.all.pluck(:name)
end
contract.assignable_priorities.pluck(:name)
end
def user_id_type
if allowed?(:view_members)
# TODO: Move possible_assignees handling into wp base contract
project.possible_assignees.pluck(:mail)
else
[]
@ -101,10 +98,16 @@ module Bcf::API::V2_1
attr_reader :project, :user
def contract
@contract ||= begin
work_package = WorkPackage.new project: project
WorkPackages::CreateContract.new(work_package, user)
end
end
def allowed?(permission)
user.allowed_to?(permission, project)
end
end
end
end

@ -0,0 +1,55 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module Bcf::API::V2_1
class Topics::AuthorizationRepresenter < BaseRepresenter
property :topic_actions,
getter: ->(decorator:, **) {
if decorator.manage_bcf_allowed?
%w[update updateRelatedTopics updateFiles createViewpoint]
else
[]
end
}
property :topic_status,
getter: ->(decorator:, **) {
if decorator.manage_bcf_allowed?
assignable_statuses.pluck(:name)
else
[]
end
}
def manage_bcf_allowed?
User.current.allowed_to?(:manage_bcf, represented.model.project)
end
end
end

@ -134,6 +134,13 @@ module Bcf::API::V2_1
property :bim_snippet,
skip_render: true
property :authorization,
getter: ->(*) {
contract = WorkPackages::UpdateContract.new(work_package, User.current)
Topics::AuthorizationRepresenter.new(contract)
}
def datetime_formatter
::API::V3::Utilities::DateTimeFormatter
end

@ -37,7 +37,7 @@ describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
let(:instance) { described_class.new(project: project, user: user) }
describe '#topic_type' do
subject { instance.topic_type }
subject { instance.topic_type }
it 'returns the project type names' do
expect(subject).to eq ['My BCF type']
@ -45,9 +45,9 @@ describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
end
describe '#topic_status' do
let!(:default_status) { FactoryBot.create :default_status }
let!(:status) { FactoryBot.create :status }
subject { instance.topic_status }
let!(:default_status) { FactoryBot.create :default_status }
let!(:status) { FactoryBot.create :status }
subject { instance.topic_status }
it 'returns default status only' do
expect(subject).to eq [default_status.name]
@ -55,8 +55,8 @@ describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
end
describe '#priority' do
let!(:priority) { FactoryBot.create :default_priority }
subject { instance.priority }
let!(:priority) { FactoryBot.create :default_priority }
subject { instance.priority }
it 'returns statuses for the available types' do
expect(subject).to eq [priority.name]
@ -69,7 +69,7 @@ describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
member_in_project: project,
member_with_permissions: [:view_work_packages])
end
subject { instance.user_id_type }
subject { instance.user_id_type }
before do
allow(user)
@ -95,7 +95,7 @@ describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
end
describe '#project_actions' do
subject { instance.project_actions }
subject { instance.project_actions }
it 'includes nothing if not permitted' do
allow(user).to receive(:allowed_to?).and_return false
@ -118,7 +118,7 @@ describe Bcf::API::V2_1::ProjectExtensions::Definitions, 'rendering' do
end
describe '#topic_actions' do
subject { instance.topic_actions }
subject { instance.topic_actions }
it 'includes nothing if not permitted' do
allow(user).to receive(:allowed_to?).and_return false

@ -54,10 +54,36 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
.and_return(journals)
end
end
let(:current_user) { FactoryBot.build_stubbed(:user) }
let(:issue) { FactoryBot.build_stubbed(:bcf_issue, work_package: work_package) }
let(:manage_bcf_allowed) { true }
let(:statuses) do
[
FactoryBot.build_stubbed(:status),
FactoryBot.build_stubbed(:status)
]
end
let(:instance) { described_class.new(issue) }
before do
login_as(current_user)
allow(current_user)
.to receive(:allowed_to?)
.with(:manage_bcf, issue.project)
.and_return(manage_bcf_allowed)
contract = double('contract',
model: issue,
assignable_statuses: statuses)
allow(WorkPackages::UpdateContract)
.to receive(:new)
.with(work_package, current_user)
.and_return(contract)
end
subject { instance.to_json }
describe 'attributes' do
@ -173,4 +199,36 @@ describe Bcf::API::V2_1::Topics::SingleRepresenter, 'rendering' do
end
end
end
describe 'authorization' do
context 'if the user has manage_bcf permission' do
it 'lists the actions' do
expect(subject)
.to be_json_eql(%w[update updateRelatedTopics updateFiles createViewpoint].to_json)
.at_path('authorization/topic_actions')
end
it 'lists the allowed statuses' do
expect(subject)
.to be_json_eql(statuses.map(&:name).to_json)
.at_path('authorization/topic_status')
end
end
context 'if the user lacks manage_bcf permission' do
let(:manage_bcf_allowed) { false }
it 'signals lack of available actions' do
expect(subject)
.to be_json_eql([])
.at_path('authorization/topic_actions')
end
it 'lists no allowed status' do
expect(subject)
.to be_json_eql([].to_json)
.at_path('authorization/topic_status')
end
end
end
end

@ -78,11 +78,11 @@ describe 'BCF 2.1 project extensions resource', type: :request, content_type: :j
member_with_permissions: [:view_project, :edit_project, :manage_bcf, :view_members])
end
let(:other_user) {
let(:other_user) do
FactoryBot.create(:user,
member_in_project: project,
member_with_permissions: [:view_project])
}
end
before do
other_user

@ -79,6 +79,19 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
due_date: Date.today,
project: project)
end
let(:other_status) do
FactoryBot.create(:status).tap do |s|
member = current_user.members.detect { |m| m.project_id == work_package.project_id }
if member
FactoryBot.create(:workflow,
old_status: work_package.status,
new_status: s,
type: work_package.type,
role: member.roles.first)
end
end
end
let(:bcf_issue) { FactoryBot.create(:bcf_issue, work_package: work_package) }
subject(:response) { last_response }
@ -90,6 +103,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
before do
login_as(current_user)
bcf_issue
other_status
get path
end
@ -114,7 +128,11 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
"stage": bcf_issue.stage,
"title": work_package.subject,
"topic_status": work_package.status.name,
"topic_type": work_package.type.name
"topic_type": work_package.type.name,
"authorization": {
"topic_status": [],
"topic_actions": []
}
}
]
end
@ -131,6 +149,41 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
it_behaves_like 'bcf api not allowed response'
end
context 'having edit permission' do
let(:current_user) { edit_member_user }
it_behaves_like 'bcf api successful response' do
let(:expected_body) do
[
{
"assigned_to": assignee.mail,
"creation_author": work_package.author.mail,
"creation_date": work_package.created_at.iso8601,
"description": work_package.description,
"due_date": work_package.due_date.iso8601,
"guid": bcf_issue.uuid,
"index": bcf_issue.index,
"labels": bcf_issue.labels,
"priority": work_package.priority.name,
"modified_author": current_user.mail,
"modified_date": work_package.updated_at.iso8601,
"reference_links": [
api_v3_paths.work_package(work_package.id)
],
"stage": bcf_issue.stage,
"title": work_package.subject,
"topic_status": work_package.status.name,
"topic_type": work_package.type.name,
"authorization": {
"topic_status": [work_package.status.name, other_status.name],
"topic_actions": %w[update updateRelatedTopics updateFiles createViewpoint]
}
}
]
end
end
end
end
describe 'GET /api/bcf/2.1/projects/:project_id/topics/:uuid' do
@ -140,6 +193,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
before do
login_as(current_user)
bcf_issue
other_status
get path
end
@ -163,7 +217,11 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
"stage": bcf_issue.stage,
"title": work_package.subject,
"topic_status": work_package.status.name,
"topic_type": work_package.type.name
"topic_type": work_package.type.name,
"authorization": {
"topic_status": [],
"topic_actions": []
}
}
end
end
@ -185,6 +243,39 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
it_behaves_like 'bcf api not allowed response'
end
context 'having edit permission' do
let(:current_user) { edit_member_user }
it_behaves_like 'bcf api successful response' do
let(:expected_body) do
{
"assigned_to": assignee.mail,
"creation_author": work_package.author.mail,
"creation_date": work_package.created_at.iso8601,
"description": work_package.description,
"due_date": work_package.due_date.iso8601,
"guid": bcf_issue.uuid,
"index": bcf_issue.index,
"labels": bcf_issue.labels,
"priority": work_package.priority.name,
"modified_author": current_user.mail,
"modified_date": work_package.updated_at.iso8601,
"reference_links": [
api_v3_paths.work_package(work_package.id)
],
"stage": bcf_issue.stage,
"title": work_package.subject,
"topic_status": work_package.status.name,
"topic_type": work_package.type.name,
"authorization": {
"topic_status": [work_package.status.name, other_status.name],
"topic_actions": %w[update updateRelatedTopics updateFiles createViewpoint]
}
}
end
end
end
end
describe 'DELETE /api/bcf/2.1/projects/:project_id/topics/:uuid' do
@ -393,6 +484,19 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
let!(:default_status) do
FactoryBot.create(:default_status)
end
let(:other_status) do
FactoryBot.create(:status).tap do |s|
member = current_user.members.detect { |m| m.project_id == project.id }
if member
FactoryBot.create(:workflow,
old_status: status,
new_status: s,
type: type,
role: member.roles.first)
end
end
end
let!(:default_type) do
FactoryBot.create(:type, is_default: true)
end
@ -426,6 +530,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
before do
login_as(current_user)
other_status
post path, params.to_json
end
@ -453,7 +558,11 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
creation_date: work_package&.created_at&.iso8601,
modified_author: edit_member_user.mail,
modified_date: work_package&.updated_at&.iso8601,
description: description
description: description,
"authorization": {
"topic_status": [other_status.name, status.name],
"topic_actions": %w[update updateRelatedTopics updateFiles createViewpoint]
}
}
end
end
@ -489,7 +598,11 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
creation_date: work_package&.created_at&.iso8601,
modified_author: edit_member_user.mail,
modified_date: work_package&.updated_at&.iso8601,
description: nil
description: nil,
"authorization": {
"topic_status": [default_status.name],
"topic_actions": %w[update updateRelatedTopics updateFiles createViewpoint]
}
}
end
end
@ -515,6 +628,19 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
let(:status) do
FactoryBot.create(:status)
end
let(:other_status) do
FactoryBot.create(:status).tap do |s|
member = current_user.members.detect { |m| m.project_id == project.id }
if member
FactoryBot.create(:workflow,
old_status: status,
new_status: s,
type: type,
role: member.roles.first)
end
end
end
let!(:default_status) do
FactoryBot.create(:default_status)
end
@ -546,6 +672,7 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
before do
login_as(current_user)
other_status
put path, params.to_json
end
@ -572,7 +699,11 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
creation_date: work_package&.created_at&.iso8601,
modified_author: edit_member_user.mail,
modified_date: work_package&.updated_at&.iso8601,
description: description
description: description,
"authorization": {
"topic_status": [other_status.name, status.name],
"topic_actions": %w[update updateRelatedTopics updateFiles createViewpoint]
}
}
end
end
@ -607,7 +738,11 @@ describe 'BCF 2.1 topics resource', type: :request, content_type: :json, with_ma
creation_date: work_package&.created_at&.iso8601,
modified_author: edit_member_user.mail,
modified_date: reloaded_work_package&.updated_at&.iso8601,
description: nil
description: nil,
"authorization": {
"topic_status": [default_status.name],
"topic_actions": %w[update updateRelatedTopics updateFiles createViewpoint]
}
}
end
end

Loading…
Cancel
Save