Revision 7114ed0d
Added by Boaz Shuster over 5 years ago
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
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 <boaz.shuster.github@gmail.com>