Revision b2b47290
Added by Petr Chalupa almost 12 years ago
- ID b2b4729059ff89327d7cd86d2e47664a2ce57c46
app/controllers/api/base_controller.rb | ||
---|---|---|
|
||
respond_to :json
|
||
|
||
def process_error(options = { })
|
||
options[:json_code] ||= :unprocessable_entity
|
||
|
||
errors = if options[:error]
|
||
options[:error]
|
||
else
|
||
options[:object] ||= get_resource || raise("No error to process")
|
||
if options[:object].respond_to?(:errors)
|
||
#TODO JSON resposne should include the real errors, not the pretty full messages
|
||
logger.info "Failed to save: #{options[:object].errors.full_messages.join(", ")}"
|
||
options[:object].errors.full_messages
|
||
else
|
||
raise("No error to process")
|
||
end
|
||
end
|
||
|
||
# set 403 status on permission errors
|
||
if errors.any? { |error| error =~ /You do not have permission/ }
|
||
options[:json_code] = :forbidden
|
||
end
|
||
rescue_from StandardError, :with => lambda { |error|
|
||
Rails.logger.error "#{error.message} (#{error.class})\n#{error.backtrace.join("\n")}"
|
||
render_error 'standard_error', :status => 500, :locals => { :error => error }
|
||
}
|
||
|
||
def get_resource
|
||
instance_variable_get :"@#{resource_name}" or raise 'no resource loaded'
|
||
end
|
||
|
||
render :json => { "errors" => errors }, :status => options[:json_code]
|
||
def resource_name
|
||
controller_name.singularize
|
||
end
|
||
|
||
def resource_class
|
||
@resource_class ||= resource_name.camelize.constantize
|
||
end
|
||
|
||
protected
|
||
|
||
def process_resource_error(options = { })
|
||
resource = options[:resource] || get_resource
|
||
|
||
raise 'resource have no errors' if resource.errors.empty?
|
||
|
||
if resource.permission_failed?
|
||
deny_access
|
||
else
|
||
render_error 'unprocessable_entity', :status => :unprocessable_entity
|
||
end
|
||
end
|
||
|
||
def process_response(condition, response = nil)
|
||
def process_response(condition, response = get_resource)
|
||
if condition
|
||
response ||= get_resource
|
||
respond_with response
|
||
else
|
||
process_error
|
||
process_resource_error
|
||
end
|
||
end
|
||
|
||
... | ... | |
|
||
if SETTINGS[:login]
|
||
unless User.current
|
||
user_to_login = nil
|
||
if result = authenticate_with_http_basic { |u, p| user_to_login = u; User.try_to_login(u, p) }
|
||
user_login = nil
|
||
result = authenticate_with_http_basic do |u, p|
|
||
user_login = u
|
||
User.try_to_login(u, p)
|
||
end
|
||
if result
|
||
User.current = result
|
||
else
|
||
process_error :error => "Unable to authenticate user %s" % user_to_login, :json_code => :unauthorized
|
||
render_error 'unauthorized', :status => :unauthorized, :locals => { :user_login => user_login }
|
||
return false
|
||
end
|
||
end
|
||
... | ... | |
end
|
||
|
||
def deny_access
|
||
process_error :error => "Access denied", :json_code => :forbidden
|
||
render_error 'access_denied', :status => :forbidden
|
||
false
|
||
end
|
||
|
||
def get_resource
|
||
instance_variable_get :"@#{resource_name}" or raise 'no resource loaded'
|
||
end
|
||
|
||
def resource_name
|
||
controller_name.singularize
|
||
end
|
||
|
||
def resource_class
|
||
@resource_class ||= resource_name.camelize.constantize
|
||
end
|
||
|
||
protected
|
||
# searches for a resource based on its name and assign it to an instance variable
|
||
# required for models which implement the to_param method
|
||
#
|
||
... | ... | |
def find_resource
|
||
finder, key = case
|
||
when (id = params[:"#{resource_name}_id"]).present?
|
||
[:find_by_id, id]
|
||
['id', id]
|
||
when (name = params[:"#{resource_name}_name"]).present?
|
||
[:find_by_name, name]
|
||
['name', name]
|
||
else
|
||
[nil, nil]
|
||
end
|
||
resource = resource_class.send(finder, key) if finder
|
||
|
||
if finder && resource
|
||
resource = resource_class.send(:"find_by_#{finder}", key) if finder
|
||
|
||
if resource
|
||
return instance_variable_set(:"@#{resource_name}", resource)
|
||
else
|
||
not_found and return false
|
||
render_error 'not_found', :status => :not_found, :locals => { :finder => finder, :key => key } and
|
||
return false
|
||
end
|
||
end
|
||
|
||
def not_found(exception = nil)
|
||
logger.debug "not found: #{exception}" if exception
|
||
head :status => 404
|
||
end
|
||
|
||
def set_default_response_format
|
||
request.format = :json if params[:format].nil?
|
||
end
|
||
... | ... | |
end
|
||
end
|
||
|
||
def api_version
|
||
raise NotImplementedError
|
||
end
|
||
|
||
def render_error(error, options = { })
|
||
render options.merge(:template => "/api/v#{api_version}/errors/#{error}")
|
||
end
|
||
end
|
||
end
|
app/controllers/api/v1/architectures_controller.rb | ||
---|---|---|
module Api
|
||
module V1
|
||
class ArchitecturesController < BaseController
|
||
class ArchitecturesController < V1::BaseController
|
||
include Foreman::Controller::AutoCompleteSearch
|
||
before_filter :find_resource, :only => %w{show update destroy}
|
||
|
app/controllers/api/v1/base_controller.rb | ||
---|---|---|
module Api
|
||
module V1
|
||
class BaseController < Api::BaseController
|
||
def api_version
|
||
'1'
|
||
end
|
||
end
|
||
end
|
||
end
|
app/controllers/api/v1/bookmarks_controller.rb | ||
---|---|---|
module Api
|
||
module V1
|
||
class BookmarksController < BaseController
|
||
class BookmarksController < V1::BaseController
|
||
before_filter :find_resource, :only => [:show, :update, :destroy]
|
||
|
||
api :GET, "/bookmarks/", "List all bookmarks."
|
app/controllers/api/v1/home_controller.rb | ||
---|---|---|
module Api
|
||
module V1
|
||
class HomeController < BaseController
|
||
class HomeController < V1::BaseController
|
||
|
||
def index
|
||
end
|
||
... | ... | |
def status
|
||
end
|
||
|
||
def route_error
|
||
render_error 'route_error', :status => :not_found
|
||
end
|
||
|
||
end
|
||
end
|
||
end
|
app/controllers/api/v1/operatingsystems_controller.rb | ||
---|---|---|
module Api
|
||
module V1
|
||
class OperatingsystemsController < BaseController
|
||
class OperatingsystemsController < V1::BaseController
|
||
|
||
resource_description do
|
||
name 'Operating systems'
|
app/models/authorization.rb | ||
---|---|---|
return true if User.current and User.current.allowed_to?("#{operation}_#{klasses}".to_sym)
|
||
|
||
errors.add :base, "You do not have permission to #{operation} this #{klass}"
|
||
@permission_failed = operation
|
||
false
|
||
end
|
||
|
||
# @return false or name of failed operation
|
||
def permission_failed?
|
||
return false unless @permission_failed
|
||
@permission_failed
|
||
end
|
||
|
||
private
|
||
def enforce?
|
||
return false if (User.current and User.current.admin?)
|
app/views/api/v1/errors/access_denied.json.rabl | ||
---|---|---|
object false
|
||
|
||
node(:message) { "Access denied" }
|
app/views/api/v1/errors/not_found.json.rabl | ||
---|---|---|
object false
|
||
|
||
node(:message) { "Resource #{controller.resource_name} not found by #{locals[:finder]} with value '#{locals[:key]}'" }
|
app/views/api/v1/errors/route_error.json.rabl | ||
---|---|---|
object false
|
||
|
||
node(:message) { 'Not found' }
|
||
|
app/views/api/v1/errors/standard_error.json.rabl | ||
---|---|---|
error = locals[:error]
|
||
|
||
object error => :error
|
||
|
||
attributes :message, :backtrace
|
||
node(:class) { error.class.to_s }
|
app/views/api/v1/errors/unauthorized.json.rabl | ||
---|---|---|
object false
|
||
|
||
node(:message) { "Unable to authenticate user %s" % locals[:user_login] }
|
app/views/api/v1/errors/unprocessable_entity.json.rabl | ||
---|---|---|
object resource = controller.get_resource
|
||
|
||
attributes :id
|
||
|
||
node(:errors) { resource.errors.to_hash }
|
||
node(:full_messages) { resource.errors.full_messages }
|
app/views/api/v1/home/index.json.rabl | ||
---|---|---|
object false
|
||
child(:links => "links") do
|
||
node(:bookmarks) { api_bookmarks_path }
|
||
node(:status) { api_status_path }
|
||
|
||
%w(bookmarks architectures operatingsystems).each do |name|
|
||
node(name.to_sym) { send :"api_#{name}_path" }
|
||
end
|
||
end
|
config/routes/api/v1.rb | ||
---|---|---|
|
||
match '/', :to => 'home#index'
|
||
match 'status', :to => 'home#status', :as => "status"
|
||
match '*other', :to => 'home#route_error'
|
||
end
|
||
#
|
||
end
|
config/routes/api/v2.rb | ||
---|---|---|
|
||
Rails.application.routes.draw do |map|
|
||
|
||
# namespace :api, :defaults => {:format => 'json'} do
|
||
# scope :module => :v2, :constraints => ApiConstraints.new(:version => 2, :default => false) do
|
||
# resources :bookmarks, :except => [:new, :edit]
|
||
# end
|
||
# end
|
||
#namespace :api, :defaults => {:format => 'json'} do
|
||
# scope :module => :v2, :constraints => ApiConstraints.new(:version => 2) do
|
||
# resources :bookmarks
|
||
# end
|
||
#end
|
||
|
||
end
|
lib/api_constraints.rb | ||
---|---|---|
class ApiConstraints
|
||
def initialize(options)
|
||
@version = options[:version]
|
||
@default = options[:default]
|
||
@default = options.has_key?(:default) ? options[:default] : false
|
||
end
|
||
|
||
def matches?(req)
|
||
@default || req.headers['Accept'].each {|h| return true if h.grep("version=#{@version}")}
|
||
req.accept =~ /version=([\d\.]+)/
|
||
if (version = $1) # version is specified in header
|
||
version == @version.to_s # are the versions same
|
||
else
|
||
@default # version is not specified, match if it's default version of api
|
||
end
|
||
end
|
||
end
|
test/functional/api/v1/architectures_controller_test.rb | ||
---|---|---|
users(:one).roles = [Role.find_by_name('Anonymous'), Role.find_by_name('Viewer')]
|
||
end
|
||
|
||
def user_one_as_manager
|
||
users(:one).roles = [Role.find_by_name('Anonymous'), Role.find_by_name('Manager')]
|
||
end
|
||
|
||
test "should get index" do
|
||
as_user :admin do
|
||
as_user :admin do
|
||
get :index, {}
|
||
end
|
||
assert_response :success
|
||
... | ... | |
end
|
||
|
||
test "should show architecture" do
|
||
as_user :admin do
|
||
as_user :admin do
|
||
get :show, {:id => architectures(:x86_64).to_param}
|
||
end
|
||
assert_response :success
|
||
... | ... | |
end
|
||
|
||
test "should update architecture" do
|
||
as_user :admin do
|
||
as_user :admin do
|
||
put :update, {:id => architectures(:x86_64).to_param, :architecture => {} }
|
||
end
|
||
assert_response :success
|
||
... | ... | |
assert_response :forbidden
|
||
end
|
||
|
||
test "user with manager rights should success to update an architecture" do
|
||
user_one_as_manager
|
||
as_user :one do
|
||
put :update, {:id => architectures(:x86_64).to_param, :architecture => {} }
|
||
end
|
||
assert_response :success
|
||
end
|
||
|
||
test "user with viewer rights should succeed in viewing architectures" do
|
||
user_one_as_anonymous_viewer
|
||
as_user :one do
|
||
as_user :one do
|
||
get :index, {}
|
||
end
|
||
assert_response :success
|
Also available in: Unified diff
api v1 - render errors with rabl
better detection of permission failure in model
fix ApiConstraints
catch bad routes in api and return json