Project

General

Profile

« Previous | Next » 

Revision 7114ed0d

Added by Boaz Shuster over 5 years ago

Fixes #24227 - Add permissions info to models API

During the process of moving Hardware Models to a React page
the current API was missing significant details that are rendered
in the Backend before the page is sent to the user.

For example, whether the user is allowed to edit a model or not

This information should be moved to the API because in the new
page, once the page is rendered by Rails any communication is
going to be done through the API and by that Rails won't
render the page or part of it again.

Signed-off-by: Boaz Shuster <>

View differences:

app/controllers/api/base_controller.rb
instance_variable_get(:"@#{resource_name}") || raise(message)
end
helper_method :controller_permission
def controller_permission
controller_name
end
app/controllers/api/v2/base_controller.rb
module V2
class BaseController < Api::BaseController
include Api::Version2
include Foreman::Controller::Authorize
resource_description do
api_version "v2"
......
helper_method :root_node_name, :metadata_total, :metadata_subtotal, :metadata_search,
:metadata_order, :metadata_by, :metadata_page, :metadata_per_page
def root_node_name
@root_node_name ||= if Rabl.configuration.use_controller_name_as_json_root
controller_name.split('/').last
app/controllers/application_controller.rb
include ApplicationShared
include Foreman::Controller::Flash
include Foreman::Controller::Authorize
force_ssl :if => :require_ssl?
protect_from_forgery # See ActionController::RequestForgeryProtection for details
......
# standard layout to all controllers
helper 'layout'
helper_method :authorizer, :resource_path
helper_method :resource_path
before_action :require_login
before_action :set_gettext_locale_db, :set_gettext_locale
......
authorized ? true : deny_access
end
def authorizer
@authorizer ||= Authorizer.new(User.current, :collection => instance_variable_get("@#{controller_name}"))
end
def deny_access
(User.current.logged? || request.xhr?) ? render_403 : require_login
end
app/controllers/concerns/foreman/controller/authorize.rb
module Foreman::Controller::Authorize
extend ActiveSupport::Concern
included do
delegate :authorized_for, :authorizer, :can_create?, to: :helpers
end
end
app/helpers/application_helper.rb
options.merge(:'data-original-title' => _("Click to add %s") % options[:"data-class-name"]))
end
# Return true if user is authorized for controller/action, otherwise false
# +options+ : Hash containing
# :controller : String or symbol for the controller, defaults to params[:controller]
# :action : String or symbol for the action
# :id : Id parameter
# :auth_action: String or symbol for the action, this has higher priority that :action
# :auth_object: Specific object on which we may verify particular permission
# :authorizer : Specific authorizer to perform authorization on (handy to inject authorizer with base collection)
# :permission : Specific permission to check authorization on (handy on custom permission names)
def authorized_for(options)
action = options.delete(:auth_action) || options[:action]
object = options.delete(:auth_object)
user = User.current
controller = options[:controller] || params[:controller]
controller_name = controller.to_s.gsub(/::/, "_").underscore
id = options[:id]
user_id = options[:user_id].to_param
permission = options.delete(:permission) || [action, controller_name].join('_')
if object.nil?
user.allowed_to?({ :controller => controller_name, :action => action, :id => id, :user_id => user_id }) rescue false
else
authorizer = options.delete(:authorizer) || Authorizer.new(user)
authorizer.can?(permission, object) rescue false
end
end
# Display a link if user is authorized, otherwise a string
# +name+ : String to be displayed
# +options+ : Hash containing options for authorized_for and link_to
app/helpers/authorize_helper.rb
module AuthorizeHelper
# Return true if user is authorized for controller/action, otherwise false
# +options+ : Hash containing
# :controller : String or symbol for the controller, defaults to params[:controller]
# :action : String or symbol for the action
# :id : Id parameter
# :auth_action: String or symbol for the action, this has higher priority that :action
# :auth_object: Specific object on which we may verify particular permission
# :authorizer : Specific authorizer to perform authorization on (handy to inject authorizer with base collection)
# :permission : Specific permission to check authorization on (handy on custom permission names)
def authorized_for(options)
action = options.delete(:auth_action) || options[:action]
object = options.delete(:auth_object)
user = User.current
controller = options[:controller] || params[:controller]
controller_name = controller.to_s.gsub(/::/, "_").underscore
id = options[:id]
user_id = options[:user_id].to_param
permission = options.delete(:permission) || [action, controller_name].join('_')
if object.nil?
user.allowed_to?({ :controller => controller_name, :action => action, :id => id, :user_id => user_id }) rescue false
else
authorizer = options.delete(:authorizer) || Authorizer.new(user)
authorizer.can?(permission, object) rescue false
end
end
def authorizer
@authorizer ||= Authorizer.new(User.current, :collection => instance_variable_get("@#{controller_name}"))
end
def can_create?
authorized_for(controller: controller_permission, action: 'create', user_id: User.current.id, authorizer: authorizer)
end
end
app/views/api/v2/layouts/index_layout.json.erb
"page": <%= metadata_page.to_json %>,
"per_page": <%= metadata_per_page.to_json %>,
"search": <%= metadata_search.to_json.html_safe %>,
<% if params.has_key?(:include_permissions) %>
"can_create": <%= can_create?.to_json %>,
<% end %>
"sort": {
"by": <%= metadata_by.to_json.html_safe %>,
"order": <%= metadata_order.to_json.html_safe %>
app/views/api/v2/layouts/permissions.json.rabl
object @resource
if params.has_key?(:include_permissions)
node do |resource|
if resource&.class&.try(:include?, Authorizable)
# To avoid loading all the records into the cache
# authorized_for is used instead of authorizer.can?.
node(:can_edit) { authorized_for(:auth_object => resource, :authorizer => authorizer, :permission => "edit_#{controller_permission}") }
node(:can_delete) { authorized_for(:auth_object => resource, :authorizer => authorizer, :permission => "destroy_#{controller_permission}") }
end
end
end
app/views/api/v2/models/main.json.rabl
object @model
extends "api/v2/models/base"
extends "api/v2/layouts/permissions"
attributes :info, :created_at, :updated_at, :vendor_class, :hardware_model
test/controllers/api/v2/audits_controller_test.rb
audits = ActiveSupport::JSON.decode(@response.body)
assert_equal expected_audits.count, audits['results'].count
end
test "should return permissions passing include_permissions in index" do
get :index, params: { :include_permissions => true }
assert_response :success
resp = ActiveSupport::JSON.decode(@response.body)
assert resp["can_create"]
end
end
test/controllers/api/v2/models_controller_test.rb
assert_equal 0, m["hosts_count"]
end
end
test "should return permissions passing include_permissions in index" do
get :index, params: { :include_permissions => true }
assert_response :success
resp = ActiveSupport::JSON.decode(@response.body)
assert resp["can_create"]
resp["results"].map do |m|
assert m["can_edit"]
assert m["can_delete"]
end
end
test "should return can_edit and can_delete on passing include_permissions in show" do
get :show, params: { :id => models(:one), :include_permissions => true }
assert_response :success
resp = ActiveSupport::JSON.decode(@response.body)
assert resp["can_edit"]
assert resp["can_delete"]
assert_nil resp["can_create"]
end
test "should show false in can_delete if user is unauthorized" do
role = Role.create!(:name => "delete_KVM_models")
role.users = [User.find_by_login("one")]
assert role.save
Filter.create!(:search => "name = KVM", :role => role, :permissions => [Permission.find_by_name("destroy_models")])
Filter.create!(:role => role, :permissions => [Permission.find_by_name("view_models")])
as_user("one") do
get :index, params: { :include_permissions => true }
resp = ActiveSupport::JSON.decode(@response.body)
assert resp["results"].detect { |m| m['name'] == "KVM" } ['can_delete']
assert !resp["results"].detect { |m| m['name'] == "SUN V210" } ['can_delete']
end
end
end
test/helpers/host_groups_helper_test.rb
include HostsAndHostgroupsHelper
include ApplicationHelper
include HostsHelper
include AuthorizeHelper
include ::FormHelper
test "should have the full string of the parent class if the child is a substring" do

Also available in: Unified diff