Project

General

Profile

« Previous | Next » 

Revision 4e6d2262

Added by Marek Hulán over 7 years ago

Fixes #16739 - unify parameters permissions

View differences:

Gemfile
gem 'apipie-rails', '~> 0.3.4'
gem 'rabl', '~> 0.11'
gem 'oauth', '~> 0.4'
gem 'deep_cloneable', '~> 2.0'
gem 'deep_cloneable', '>= 2.2.2', '< 3.0'
gem 'validates_lengths_from_database', '~> 0.5'
gem 'friendly_id', '~> 5.0'
gem 'secure_headers', '~> 3.4'
app/controllers/api/v1/common_parameters_controller.rb
def index
@common_parameters = CommonParameter.
authorized(:view_globals, CommonParameter).
authorized(:view_params, Parameter).where(:type => 'CommonParameter').
search_for(*search_options).
paginate(paginate_options)
end
......
def destroy
process_response @common_parameter.destroy
end
private
def controller_permission
'params'
end
def resource_scope(*args, &block)
super.where(:type => 'CommonParameter')
end
def resource_class
Parameter
end
end
end
end
app/controllers/api/v2/common_parameters_controller.rb
param_group :search_and_pagination, ::Api::V2::BaseController
def index
@common_parameters = resource_scope_for_index(:permission => :view_globals)
@common_parameters = resource_scope_for_index(:permission => :view_params)
end
api :GET, "/common_parameters/:id/", N_("Show a global parameter")
......
def destroy
process_response @common_parameter.destroy
end
private
def controller_permission
'params'
end
def resource_scope(*args, &block)
super.where(:type => 'CommonParameter')
end
def resource_class
Parameter
end
end
end
end
app/controllers/api/v2/parameters_controller.rb
param_group :search_and_pagination, ::Api::V2::BaseController
def index
@parameters = nested_obj.send(parameters_method).search_for(*search_options).paginate(paginate_options)
@total = nested_obj.send(parameters_method).count
base = nested_obj.send(parameters_method).authorized(current_permission)
@parameters = base.search_for(*search_options).paginate(paginate_options)
@total = base.count
end
api :GET, "/hosts/:host_id/parameters/:id", N_("Show a nested parameter for a host")
......
end
end
def controller_permission
'params'
end
def parameters_method
# hostgroup.rb has a method def parameters, so I didn't create has_many :parameters like Host, Domain, Os
# locations and organizations inherit def parameters from taxonomies
......
def find_parameter
# nested_obj is required, so no need to check here
@parameters = nested_obj.send(parameters_method)
@parameters = nested_obj.send(parameters_method).authorized(current_permission)
@parameter = @parameters.from_param(params[:id])
@parameter ||= @parameters.friendly.find(params[:id])
return @parameter if @parameter.present?
app/controllers/application_controller.rb
@model_of_controller ||= controller_path.singularize.camelize.gsub('/','::').constantize
end
def current_permission
[action_permission, controller_permission].join('_')
end
def controller_permission
controller_name
end
app/controllers/common_parameters_controller.rb
private
def controller_permission
'globals'
'params'
end
def resource_base
model_of_controller.authorized(current_permission, CommonParameter)
model_of_controller.authorized(current_permission, Parameter).where(:type => 'CommonParameter')
end
end
app/controllers/concerns/application_shared.rb
# Reset timezone for the next thread
Time.zone = default_timezone
end
def current_permission
[action_permission, controller_permission].join('_')
end
end
app/controllers/parameters_controller.rb
class ParametersController < ApplicationController
include Foreman::Controller::AutoCompleteSearch
private
def controller_permission
'params'
end
def resource_base
model_of_controller.authorized(current_permission, Parameter)
end
end
app/helpers/common_parameters_helper.rb
input = f.text_area(field, options.merge(:disabled => disabled,
:class => html_class,
:rows => 1,
:id => dom_id(f.object) + '_value',
:placeholder => _("Value")))
input_group(input, input_group_btn(hidden_toggle(f.object.hidden_value?), fullscreen_button("$(this).closest('.input-group').find('input,textarea')")))
......
text_area_tag(id, value, option_hash)
end
end
def authorized_resource_parameters(resource, type)
resource.send(type).authorized(:view_params) + resource.send(type).select(&:new_record?)
end
end
app/helpers/filters_helper.rb
'' # images are nested resource for CR, we can't autocomplete
when 'HostClass'
'' # host classes is only used in API
when 'Parameter'
'' # parameter is only used in API
else
return FiltersHelperOverrides.search_path(type) if FiltersHelperOverrides.can_override?(type)
resource_path = resource_path(type)
app/models/parameter.rb
include Authorizable
validates :name, :presence => true, :no_whitespace => true
def self.inherited(child)
child.instance_eval do
scoped_search :on => :name, :complete_value => true
scoped_search :on => :value, :complete_value => :true
end
super
scoped_search :on => :name, :complete_value => true
scoped_search :on => :type, :complete_value => true
scoped_search :on => :value, :complete_value => true
# children associations must be defined here, otherwise scoped search definitions won't find them
belongs_to :domain, :foreign_key => :reference_id, :inverse_of => :domain_parameters
belongs_to :operatingsystem, :foreign_key => :reference_id, :inverse_of => :os_parameters
belongs_to :subnet, :foreign_key => :reference_id, :inverse_of => :subnet_parameters
belongs_to_host :foreign_key => :reference_id, :inverse_of => :host_parameters
belongs_to :hostgroup, :foreign_key => :reference_id, :inverse_of => :group_parameters
belongs_to :location, :foreign_key => :reference_id, :inverse_of => :location_parameters
belongs_to :organization, :foreign_key => :reference_id, :inverse_of => :organization_parameters
# specific children search definitions, required for permission filters autocompletion
scoped_search :in => :domain, :on => :name, :complete_value => true, :rename => 'domain_name'
scoped_search :in => :operatingsystem, :on => :name, :complete_value => true, :rename => 'os_name'
scoped_search :in => :subnet, :on => :name, :complete_value => true, :rename => 'subnet_name'
scoped_search :in => :host, :on => :name, :complete_value => true, :rename => 'host_name'
scoped_search :in => :hostgroup, :on => :name, :complete_value => true, :rename => 'host_group_name'
if Taxonomy.locations_enabled
scoped_search :in => :location, :on => :name, :complete_value => true, :rename => 'location_name'
end
if Taxonomy.organizations_enabled
scoped_search :in => :organization, :on => :name, :complete_value => true, :rename => 'organization_name'
end
default_scope -> { order("parameters.name") }
app/models/parameters/domain_parameter.rb
class DomainParameter < Parameter
belongs_to :domain, :foreign_key => :reference_id, :inverse_of => :domain_parameters
audited :except => [:priority], :associated_with => :domain
validates :name, :uniqueness => {:scope => :reference_id}
validates :domain, :presence => true
app/models/parameters/group_parameter.rb
class GroupParameter < Parameter
belongs_to :hostgroup, :foreign_key => :reference_id, :inverse_of => :group_parameters
audited :except => [:priority], :associated_with => :hostgroup
validates :name, :uniqueness => {:scope => :reference_id}
validates :hostgroup, :presence => true
app/models/parameters/host_parameter.rb
class HostParameter < Parameter
belongs_to_host :foreign_key => :reference_id, :inverse_of => :host_parameters
audited :except => [:priority], :associated_with => :host
validates :name, :uniqueness => {:scope => :reference_id}
validates :host, :presence => true
app/models/parameters/location_parameter.rb
class LocationParameter < Parameter
belongs_to :location, :foreign_key => :reference_id, :inverse_of => :location_parameters
audited :except => [:priority], :associated_with => :location
validates :name, :uniqueness => {:scope => :reference_id}
validates :location, :presence => true
app/models/parameters/organization_parameter.rb
class OrganizationParameter < Parameter
belongs_to :organization, :foreign_key => :reference_id, :inverse_of => :organization_parameters
audited :except => [:priority], :associated_with => :organization
validates :name, :uniqueness => {:scope => :reference_id}
validates :organization, :presence => true
app/models/parameters/os_parameter.rb
class OsParameter < Parameter
belongs_to :operatingsystem, :foreign_key => :reference_id, :inverse_of => :os_parameters
audited :except => [:priority], :associated_with => :operatingsystem
validates :name, :uniqueness => {:scope => :reference_id}
validates :operatingsystem, :presence => true
app/models/parameters/subnet_parameter.rb
class SubnetParameter < Parameter
belongs_to :subnet, :foreign_key => :reference_id, :inverse_of => :subnet_parameters
audited :except => [:priority], :associated_with => :subnet
validates :name, :uniqueness => {:scope => :reference_id}
validates :subnet, :presence => true
app/services/authorizer.rb
def resource_name(klass)
return 'Operatingsystem' if klass <= Operatingsystem
return 'ComputeResource' if klass <= ComputeResource
return 'Parameter' if klass <= Parameter && !(klass <= CommonParameter)
return 'Subnet' if klass <= Subnet
return 'Parameter' if klass <= Parameter
case (name = klass.to_s)
when 'Audited::Adapters::ActiveRecord::Audit'
app/services/foreman/access_permissions.rb
:'api/v2/filters' => [:destroy]}
end
permission_set.security_block :global_variables do |map|
map.permission :view_globals, {:common_parameters => [:index, :show, :auto_complete_search],
:"api/v1/common_parameters" => [:index, :show],
:"api/v2/common_parameters" => [:index, :show]
}
map.permission :create_globals, {:common_parameters => [:new, :create],
:"api/v1/common_parameters" => [:create],
:"api/v2/common_parameters" => [:create]
}
map.permission :edit_globals, {:common_parameters => [:edit, :update],
:"api/v1/common_parameters" => [:update],
:"api/v2/common_parameters" => [:update]
}
map.permission :destroy_globals, {:common_parameters => [:destroy],
:"api/v1/common_parameters" => [:destroy],
:"api/v2/common_parameters" => [:destroy]
}
end
permission_set.security_block :hostgroups do |map|
ajax_actions = [:architecture_selected, :domain_selected, :environment_selected, :medium_selected, :os_selected,
:use_image_selected, :process_hostgroup, :puppetclass_parameters, :welcome]
......
:"api/v2/host_classes" => [:index, :create, :destroy]
}
map.permission :view_params, { :host_editing => [:view_params],
:parameters => [:index, :auto_complete_search],
:common_parameters => [:index, :show, :auto_complete_search],
:"api/v1/common_parameters" => [:index, :show],
:"api/v2/common_parameters" => [:index, :show],
:"api/v2/parameters" => [:index, :show]
}
map.permission :create_params, { :host_editing => [:create_params],
:common_parameters => [:new, :create],
:"api/v1/common_parameters" => [:create],
:"api/v2/common_parameters" => [:create],
:"api/v2/parameters" => [:create]
}
map.permission :edit_params, { :host_editing => [:edit_params],
:common_parameters => [:edit, :update],
:"api/v1/common_parameters" => [:update],
:"api/v2/common_parameters" => [:update],
:"api/v2/parameters" => [:update]
}
map.permission :destroy_params, { :host_editing => [:destroy_params],
:common_parameters => [:destroy],
:"api/v1/common_parameters" => [:destroy],
:"api/v2/common_parameters" => [:destroy],
:"api/v2/parameters" => [:destroy, :reset]
}
end
app/views/common_parameters/_parameter.html.erb
<tr class="fields <%= 'has-error' if f.object.errors.any? %>">
<tr class="fields <%= 'has-error' if f.object.errors.any? %>" id="<%= dom_id(f.object) + '_row' %>">
<td>
<%= f.text_field(:name, :class => "form-control", :disabled => !can_edit_params?, :placeholder => _("Name")) %>
<%= f.text_field(:name, :class => "form-control", :disabled => !authorizer.can?(:edit_params, f.object), :placeholder => _("Name")) %>
</td>
<td>
<%= hidden_value_field(f, :value, !can_edit_params?) %>
<%= hidden_value_field(f, :value, !authorizer.can?(:edit_params, f.object)) %>
</td>
<td>
<%= f.check_box(:hidden_value, :class => 'set_hidden_value hide', :checked => f.object.hidden_value?) if can_edit_params? %>
<%= link_to_remove_fields(_('remove'), f) if authorized_via_my_scope("host_editing", "destroy_params") %>
<%= f.check_box(:hidden_value, :class => 'set_hidden_value hide', :checked => f.object.hidden_value?) if authorizer.can?(:edit_params, f.object) %>
<%= link_to_remove_fields(_('remove'), f) if authorizer.can?(:destroy_params, f.object) %>
</td>
</tr>
<% if f.object.errors.any? %>
app/views/common_parameters/_parameters.html.erb
</tr>
</thead>
<tbody>
<%= f.fields_for type do |builder| %>
<%= render "common_parameters/parameter", :f => builder if builder.object.new_record? || User.current.can?(:view_params, builder.object) %>
<% authorized_resource_parameters(f.object, type).each do |parameter| %>
<%= f.fields_for type, parameter do |builder| %>
<%= render "common_parameters/parameter", :f => builder %>
<% end %>
<% end %>
</tbody>
</table>
app/views/common_parameters/index.html.erb
</thead>
<tbody>
<% for common_parameter in @common_parameters %>
<tr>
<td class="display-two-pane ellipsis"><%= link_to_if_authorized common_parameter, hash_for_edit_common_parameter_path(:id => common_parameter).merge(:auth_object => common_parameter, :authorizer => authorizer, :permission => 'edit_globals') %></td>
<tr id="<%= dom_id(common_parameter) %>_row">
<td class="display-two-pane ellipsis"><%= link_to_if_authorized common_parameter, hash_for_edit_common_parameter_path(:id => common_parameter).merge(:auth_object => common_parameter, :authorizer => authorizer, :permission => 'edit_params') %></td>
<td class='ellipsis'><%= common_parameter.safe_value %></td>
<td>
<%= action_buttons(display_delete_if_authorized hash_for_common_parameter_path(:id => common_parameter).merge(:auth_object => common_parameter, :authorizer => authorizer, :permission => 'destroy_globals'),
<%= action_buttons(display_delete_if_authorized hash_for_common_parameter_path(:id => common_parameter).merge(:auth_object => common_parameter, :authorizer => authorizer, :permission => 'destroy_params'),
:data => { :confirm => _("Delete %s?") % common_parameter.name } ) %>
</td>
</tr>
app/views/domains/_form.html.erb
<%= base_errors_for @domain %>
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#primary" data-toggle="tab"><%= _("Domain") %></a></li>
<% if authorized_for(:controller => "host_editing", :action => "view_params") %>
<% if authorizer.can?(:view_params) %>
<li><a href="#params" data-toggle="tab"><%= _("Parameters") %></a></li>
<% end %>
<% if show_location_tab? %>
......
<%= text_f f, :fullname, :help_inline => _("Full name describing the domain") %>
<%= smart_proxy_fields f %>
</div>
<% if authorized_for(:controller => "host_editing", :action => "view_params") %>
<% if authorizer.can?("view_params") %>
<div class="tab-pane" id="params">
<%= render "common_parameters/parameters", { :f => f, :type => :domain_parameters } %>
</div>
config/routes.rb
get 'auto_complete_search'
end
end
resources :parameters, :only => [:index] do
collection do
get 'auto_complete_search'
end
end
resources :environments, :except => [:show] do
collection do
get 'import_environments'
db/migrate/20161006070258_migrate_common_parameter_permissions.rb
class MigrateCommonParameterPermissions < ActiveRecord::Migration
class FakeFilter < ActiveRecord::Base
self.table_name = 'filters'
end
def up
all_new_permissions = Permission.where(:name => ['view_params', 'edit_params', 'create_params', 'destroy_params'])
affected = Filter.includes(:permissions).where(:permissions => {:name => ['view_globals', 'edit_globals', 'create_globals', 'destroy_globals']})
affected.each do |filter|
new_names = filter.permissions.map { |old| old.name.sub(/globals/, 'params') }
new_permissions = all_new_permissions.select do |new|
new_names.include?(new.name)
end
fake_filter = FakeFilter.find(filter.id)
new_search = "type = CommonParameter"
new_search = '(' + filter.search + ') and ' + new_search if filter.search.present?
fake_filter.update_attribute :search, new_search
filter.permissions = new_permissions
end
Permission.where(:name => ['view_globals', 'edit_globals', 'create_globals', 'destroy_globals']).destroy_all
end
def down
# we can't tell what if CommonParameter search was set by user or previous migration
end
end
db/seeds.d/03-permissions.rb
['Bookmark', 'create_bookmarks'],
['Bookmark', 'edit_bookmarks'],
['Bookmark', 'destroy_bookmarks'],
['CommonParameter', 'view_globals'],
['CommonParameter', 'create_globals'],
['CommonParameter', 'edit_globals'],
['CommonParameter', 'destroy_globals'],
['ComputeProfile', 'view_compute_profiles'],
['ComputeProfile', 'create_compute_profiles'],
['ComputeProfile', 'edit_compute_profiles'],
db/seeds.d/03-roles.rb
:view_environments, :create_environments, :edit_environments, :destroy_environments, :import_environments,
:view_external_variables, :create_external_variables, :edit_external_variables, :destroy_external_variables,
:view_external_parameters, :create_external_parameters, :edit_external_parameters, :destroy_external_parameters,
:view_globals, :create_globals, :edit_globals, :destroy_globals,
:view_hostgroups, :create_hostgroups, :edit_hostgroups, :destroy_hostgroups,
:view_hosts, :create_hosts, :edit_hosts, :destroy_hosts, :build_hosts, :power_hosts, :console_hosts, :ipmi_boot, :puppetrun_hosts,
:edit_classes, :view_params, :create_params, :edit_params, :destroy_params,
......
:view_domains, :view_environments, :import_environments, :view_external_variables,
:create_external_variables, :edit_external_variables, :destroy_external_variables,
:view_external_parameters, :create_external_parameters, :edit_external_parameters,
:destroy_external_parameters, :view_facts, :view_globals, :view_hostgroups, :view_hosts, :view_smart_proxies_puppetca,
:destroy_external_parameters, :view_facts, :view_hostgroups, :view_hosts, :view_smart_proxies_puppetca,
:view_smart_proxies_autosign, :create_hosts, :edit_hosts, :destroy_hosts,
:build_hosts, :view_media, :create_media, :edit_media, :destroy_media,
:view_models, :view_operatingsystems, :view_ptables, :view_puppetclasses,
test/controllers/api/v2/parameters_controller_test.rb
test 'user with permissions to view host can also view its parameters' do
setup_user 'view', 'params'
setup_user 'view', 'hosts', "name = #{@host.name}"
get :index, { :host_id => @host.name }, set_session_user
get :index, { :host_id => @host.name }, set_session_user(:one)
assert_response :success
end
test 'user without permissions to view host cannot view parameters' do
setup_user 'view', 'params'
setup_user 'view', 'hosts', "name = some.other.host"
get :index, { :host_id => @host.name }, set_session_user
get :index, { :host_id => @host.name }, set_session_user(:one)
assert_response :not_found
end
end
test/factories/common_parameter.rb
FactoryGirl.define do
factory :common_parameter do
sequence(:name) {|n| "parameter-#{n}" }
end
end
test/helpers/filters_helper_test.rb
end
def test_search_path_is_empty_for_excepted_classes
%w(Image HostClass Parameter).each do |clazz_name|
%w(Image HostClass).each do |clazz_name|
assert_equal '', search_path(clazz_name), "class #{clazz_name} doesn't support autocomplete, shouldn't return autocomplete path"
end
end
test/integration/parameters_permissions_test.rb
require 'integration_test_helper'
class ParametersPermissionsIntegrationTest < ActionDispatch::IntegrationTest
setup do
role = FactoryGirl.create(:role)
@filter = FactoryGirl.create(:filter,
:permissions => Permission.where(:name => ['view_params']),
:search => 'name ~ a* or domain_name ~ example*com',
:role => role)
domain_filter = FactoryGirl.create(:filter, :permissions => Permission.where(:name => ['edit_domains', 'view_domains']))
role.filters = [ @filter, domain_filter ]
@user = FactoryGirl.create(:user, :with_mail)
@user.roles << role
set_request_user(@user)
end
describe "global parameters" do
before do
@visible_global_parameter = FactoryGirl.create(:common_parameter, :name => "a_parameter")
@invisible_global_parameter = FactoryGirl.create(:common_parameter, :name => "b_parameter")
end
test "user can only see global parameters limited by filter on name" do
visit common_parameters_path
assert page.has_no_text?('Delete')
assert page.has_content?(@visible_global_parameter.name)
assert page.has_no_content?(@invisible_global_parameter.name)
end
test "user can edit global parameters limited by filter on name" do
@filter.permissions << Permission.find_by_name('edit_params')
visit common_parameters_path
assert page.has_no_text?('Delete')
assert page.has_content?(@visible_global_parameter.name)
assert page.has_no_content?(@invisible_global_parameter.name)
click_link @visible_global_parameter.name
fill_in 'common_parameter[value]', :with => 'another_value'
click_button 'Submit'
assert page.has_text? 'another_value'
end
test "user can delete global parameters limited by filter on name" do
@filter.permissions << Permission.find_by_name('destroy_params')
visit common_parameters_path
assert page.has_content?(@visible_global_parameter.name)
assert page.has_no_content?(@invisible_global_parameter.name)
parameter_row_css = "tr#common_parameter_#{@visible_global_parameter.id}_row"
within parameter_row_css do
click_link 'Delete'
end
assert page.has_text?('Successfully deleted a_parameter.')
assert page.has_no_css? "tr#common_parameter_#{@visible_global_parameter.id}_row"
end
end
describe "domain parameters" do
before do
@domain1 = FactoryGirl.create(:domain, :name => 'example.tst')
@visible_domain_parameter = FactoryGirl.create(:domain_parameter, :domain => @domain1, :name => 'a_parameter')
@invisible_domain_parameter = FactoryGirl.create(:domain_parameter, :domain => @domain1, :name => 'b_parameter')
@domain2 = FactoryGirl.create(:domain)
@domain_visible_domain_parameter = FactoryGirl.create(:domain_parameter, :domain => @domain2, :name => 'c_parameter')
end
test "user can only see domain parameters limited by filter on name or domain" do
visit edit_domain_path(@domain1)
refute_visible_parameter(@invisible_domain_parameter)
refute_user_can_see_domain_parameter_remove_link(@visible_domain_parameter)
assert_visible_parameter(@visible_domain_parameter)
visit edit_domain_path(@domain2)
assert_visible_parameter(@domain_visible_domain_parameter)
end
test "user can edit domain parameters limited by filter on name or domain" do
@filter.permissions << Permission.find_by_name('edit_params')
visit edit_domain_path(@domain1)
refute_visible_parameter(@invisible_domain_parameter)
refute_user_can_see_domain_parameter_remove_link(@visible_domain_parameter)
assert_domain_parameter_can_be_edited(@domain1, @visible_domain_parameter)
assert_domain_parameter_can_be_edited(@domain2, @domain_visible_domain_parameter)
end
test "user can destroy only domain parameters limited by filter on name or domain" do
@filter.permissions << Permission.find_by_name('destroy_params')
assert_domain_parameter_can_be_deleted(@domain1, @visible_domain_parameter)
assert_domain_parameter_can_be_deleted(@domain2, @domain_visible_domain_parameter)
end
end
private
def assert_visible_parameter(parameter)
assert page.has_css?("input[value=#{parameter.name}][type=text][disabled=disabled]")
end
def refute_visible_parameter(parameter)
assert page.has_no_css?("input[value=#{parameter.name}][type=text]")
end
def assert_domain_parameter_can_be_edited(domain, parameter)
visit edit_domain_path(domain)
assert page.has_css?("input[value=#{parameter.name}][type=text]")
fill_in "domain_parameter_#{parameter.id}_value", :with => 'new_value'
click_button 'Submit'
assert_includes page.text, "Successfully updated #{domain.name}."
visit edit_domain_path(domain)
parameter_area = find(:css, "#domain_parameter_#{parameter.id}_value")
assert_equal 'new_value', parameter_area.text
end
def assert_domain_parameter_can_be_deleted(domain, parameter)
visit edit_domain_path(domain)
within("tr#domain_parameter_#{parameter.id}_row") do
assert page.has_link?('remove')
# click_link 'remove' (we don't want to use JS here)
hidden = find(:css, "#domain_domain_parameters_attributes_0__destroy", :visible => false)
hidden.set '1'
end
click_button 'Submit'
visit edit_domain_path(domain)
refute_visible_parameter(parameter)
end
def refute_user_can_see_domain_parameter_remove_link(parameter)
within("tr#domain_parameter_#{parameter.id}_row") do
assert page.has_no_link?('remove')
end
end
end

Also available in: Unified diff