parent
b05973b30a
commit
9af15711ef
@ -0,0 +1,48 @@ |
||||
#-- 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 ProjectsAPI < ::API::OpenProjectAPI |
||||
include OpenProject::StaticRouting::UrlHelpers |
||||
|
||||
resources :projects do |
||||
route_param :id, regexp: /\A(\d+)\z/ do |
||||
after_validation do |
||||
@project = Project.find(params[:id]) |
||||
end |
||||
|
||||
get do |
||||
{ |
||||
project_id: @project.id, |
||||
name: @project.name |
||||
} |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,253 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
# Root class of the API |
||||
# This is the place for all API wide configuration, helper methods, exceptions |
||||
# rescuing, mounting of different API versions etc. |
||||
|
||||
require 'open_project/authentication' |
||||
|
||||
module Bcf::API::V2_1 |
||||
class Root < Grape::API |
||||
extend ::API::Utilities::GrapeHelper |
||||
|
||||
class Formatter |
||||
def call(object, _env) |
||||
object.respond_to?(:to_json) ? object.to_json : MultiJson.dump(object) |
||||
end |
||||
end |
||||
|
||||
class Parser |
||||
def call(object, _env) |
||||
MultiJson.load(object) |
||||
rescue MultiJson::ParseError => e |
||||
error = ::API::Errors::ParseError.new(details: e.message) |
||||
representer = ::API::V3::Errors::ErrorRepresenter.new(error) |
||||
|
||||
throw :error, status: 400, message: representer.to_json |
||||
end |
||||
end |
||||
|
||||
content_type :json, 'application/json; charset=utf-8' |
||||
format 'json' |
||||
formatter 'json', Formatter.new |
||||
|
||||
parser :json, Parser.new |
||||
|
||||
use ::OpenProject::Authentication::Manager |
||||
|
||||
helpers do |
||||
def current_user |
||||
User.current |
||||
end |
||||
|
||||
def declared_param(key) |
||||
declared(params)[key.to_s] |
||||
end |
||||
|
||||
def warden |
||||
env['warden'] |
||||
end |
||||
|
||||
def request_body |
||||
env['api.request.body'] |
||||
end |
||||
|
||||
def authenticate |
||||
warden.authenticate! scope: API_V3 |
||||
|
||||
User.current = warden.user scope: API_V3 |
||||
|
||||
if Setting.login_required? and not logged_in? |
||||
raise ::API::Errors::Unauthenticated |
||||
end |
||||
end |
||||
|
||||
def set_localization |
||||
SetLocalizationService.new(User.current, env['HTTP_ACCEPT_LANGUAGE']).call |
||||
end |
||||
|
||||
# Global helper to set allowed content_types |
||||
# This may be overriden when multipart is allowed (file uploads) |
||||
def allowed_content_types |
||||
%w(application/json) |
||||
end |
||||
|
||||
def enforce_content_type |
||||
# Content-Type is not present in GET |
||||
return if request.get? |
||||
|
||||
# Raise if missing header |
||||
content_type = request.content_type |
||||
error!('Missing content-type header', 406) unless content_type.present? |
||||
|
||||
# Allow JSON and JSON+HAL per default |
||||
# and anything that each endpoint may optionally add to that |
||||
if content_type.present? |
||||
allowed_content_types.each do |mime| |
||||
# Content-Type header looks like this (e.g.,) |
||||
# application/json;encoding=utf8 |
||||
return if content_type.start_with?(mime) |
||||
end |
||||
end |
||||
|
||||
bad_type = content_type.presence || I18n.t('api_v3.errors.missing_content_type') |
||||
message = I18n.t('api_v3.errors.invalid_content_type', |
||||
content_type: allowed_content_types.join(" "), |
||||
actual: bad_type) |
||||
|
||||
fail ::API::Errors::UnsupportedMediaType, message |
||||
end |
||||
|
||||
def logged_in? |
||||
# An admin SystemUser is anonymous but still a valid user to be logged in. |
||||
current_user && (current_user.admin? || !current_user.anonymous?) |
||||
end |
||||
|
||||
def authorize(permission, context: nil, global: false, user: current_user, &block) |
||||
auth_service = AuthorizationService.new(permission, |
||||
context: context, |
||||
global: global, |
||||
user: user) |
||||
|
||||
authorize_by_with_raise auth_service, &block |
||||
end |
||||
|
||||
def authorize_by_with_raise(callable) |
||||
is_authorized = callable.respond_to?(:call) ? callable.call : callable |
||||
|
||||
return true if is_authorized |
||||
|
||||
if block_given? |
||||
yield |
||||
else |
||||
raise API::Errors::Unauthorized |
||||
end |
||||
|
||||
false |
||||
end |
||||
|
||||
# checks whether the user has |
||||
# any of the provided permission in any of the provided |
||||
# projects |
||||
def authorize_any(permissions, projects: nil, global: false, user: current_user, &block) |
||||
raise ArgumentError if projects.nil? && !global |
||||
|
||||
projects = Array(projects) |
||||
|
||||
authorized = permissions.any? do |permission| |
||||
if global |
||||
authorize(permission, global: true, user: user) do |
||||
false |
||||
end |
||||
else |
||||
allowed_projects = Project.allowed_to(user, permission) |
||||
!(allowed_projects & projects).empty? |
||||
end |
||||
end |
||||
|
||||
authorize_by_with_raise(authorized, &block) |
||||
end |
||||
|
||||
def authorize_admin |
||||
authorize_by_with_raise(current_user.admin? && (current_user.active? || current_user.is_a?(SystemUser))) |
||||
end |
||||
|
||||
def authorize_logged_in |
||||
authorize_by_with_raise(current_user.logged? && current_user.active? || current_user.is_a?(SystemUser)) |
||||
end |
||||
|
||||
def raise_invalid_query_on_service_failure |
||||
service = yield |
||||
|
||||
if service.success? |
||||
service |
||||
else |
||||
api_errors = service.errors.full_messages.map do |message| |
||||
::API::Errors::InvalidQuery.new(message) |
||||
end |
||||
|
||||
raise ::API::Errors::MultipleErrors.create_if_many api_errors |
||||
end |
||||
end |
||||
end |
||||
|
||||
def self.auth_headers |
||||
lambda do |
||||
header = OpenProject::Authentication::WWWAuthenticate |
||||
.response_header(scope: API_V3, request_headers: env) |
||||
|
||||
{ 'WWW-Authenticate' => header } |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Return JSON error response on authentication failure. |
||||
OpenProject::Authentication.handle_failure(scope: API_V3) do |warden, _opts| |
||||
e = grape_error_for warden.env, self |
||||
error_message = I18n.t('api_v3.errors.code_401_wrong_credentials') |
||||
api_error = ::API::Errors::Unauthenticated.new error_message |
||||
representer = ::API::V3::Errors::ErrorRepresenter.new api_error |
||||
|
||||
e.error_response status: 401, message: representer.to_json, headers: warden.headers, log: false |
||||
end |
||||
|
||||
error_response ActiveRecord::RecordNotFound, ::API::Errors::NotFound, log: false |
||||
error_response ActiveRecord::StaleObjectError, ::API::Errors::Conflict, log: false |
||||
|
||||
error_response MultiJson::ParseError, ::API::Errors::ParseError |
||||
|
||||
error_response ::API::Errors::Unauthenticated, headers: auth_headers, log: false |
||||
error_response ::API::Errors::ErrorBase, rescue_subclasses: true, log: false |
||||
|
||||
# hide internal errors behind the same JSON response as all other errors |
||||
# only doing it in production to allow for easier debugging |
||||
if Rails.env.production? |
||||
error_response StandardError, ::API::Errors::InternalError, rescue_subclasses: true |
||||
end |
||||
|
||||
# run authentication before each request |
||||
before do |
||||
# authenticate |
||||
User.current = User.find_by login: 'admin' |
||||
set_localization |
||||
enforce_content_type |
||||
end |
||||
|
||||
version '2.1', using: :path do |
||||
## /auth |
||||
#mount ::OpenProject::Bcf::API::AuthEndpoint |
||||
## /current-user |
||||
#mount ::OpenProject::Bcf::API::CurrentUserEndpoint |
||||
# /projects |
||||
mount ::Bcf::API::V2_1::ProjectsAPI |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,60 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2019 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. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
require 'rack/test' |
||||
|
||||
describe 'BCF 2.1 projects resource', type: :request do |
||||
include Rack::Test::Methods |
||||
|
||||
let(:project) { FactoryBot.create(:project) } |
||||
subject(:response) { last_response } |
||||
|
||||
describe 'GET /bcf/2.1/projects/:project_id' do |
||||
let(:path) { "/bcf/2.1/projects/#{project.id}" } |
||||
|
||||
before do |
||||
get path |
||||
end |
||||
|
||||
it 'responds 200 OK' do |
||||
expect(subject.status) |
||||
.to eql 200 |
||||
end |
||||
|
||||
it 'returns the project' do |
||||
expected = { |
||||
project_id: project.id, |
||||
name: project.name |
||||
} |
||||
|
||||
expect(subject.body) |
||||
.to be_json_eql(expected.to_json) |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue