This is a repost of the patch series from last Tuesday. The main change is that auth errors are now handled with Rails rescue_from mechanism. David
David Lutterkort
2009-May-04 23:15 UTC
[Ovirt-devel] [PATCH server 1/4] * src/Rakefile: do not require gettext for all tasks, breaks rdoc
--- src/Rakefile | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-) diff --git a/src/Rakefile b/src/Rakefile index 74b9d18..eebf798 100644 --- a/src/Rakefile +++ b/src/Rakefile @@ -1,3 +1,4 @@ +# -*- ruby -*- # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. @@ -8,14 +9,18 @@ require 'rake/testtask' require 'rake/rdoctask' require 'tasks/rails' -require 'gettext/utils' +# Do not require 'gettext/utils' here, it breaks rdoc, +# require it in tasks that actually need it instead + desc "Create mo-files for L10n" task :makemo do + require 'gettext/utils' GetText.create_mofiles(true, "po", "locale") end desc "Update pot/po files to match new version." task :updatepo do + require 'gettext/utils' MY_APP_TEXT_DOMAIN = "ovirt" MY_APP_VERSION = "ovirt 0.0.1" GetText.update_pofiles(MY_APP_TEXT_DOMAIN, -- 1.6.0.6
David Lutterkort
2009-May-04 23:15 UTC
[Ovirt-devel] [PATCH server 2/4] * app/controllers/application.rb: handler for permission errors
Use Rails' rescue_from to catch permission errors and render an appropriate response. --- src/app/controllers/application.rb | 35 +++++++++++++++++++++++++++++++++++ 1 files changed, 35 insertions(+), 0 deletions(-) diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb index e5f4d4b..3902e78 100644 --- a/src/app/controllers/application.rb +++ b/src/app/controllers/application.rb @@ -48,6 +48,8 @@ class ApplicationController < ActionController::Base before_filter :tmp_authorize_admin, :only => [:create, :update, :destroy] before_filter :is_logged_in, :get_help_section + # General error handlers + rescue_from PermissionError, :with => :handle_perm_error def choose_layout if(params[:component_layout]) @@ -140,6 +142,39 @@ class ApplicationController < ActionController::Base end end + def handle_perm_error(error) + handle_error(:error => error, :status => :forbidden, + :title => "Access denied") + end + + def handle_error(hash) + msg = hash[:message] || hash[:error].message + title = hash[:title] || "Internal Server Error" + status = hash[:status] || :internal_server_error + respond_to do |format| + format.html do + @title = title || "Something went very wrong" + @errmsg = msg + @ajax = params[:ajax] + @nolayout = params[:nolayout] + if @ajax + render :template => 'layouts/popup-error', :layout => 'tabs-and-content' + elsif @nolayout + render :template => 'layouts/popup-error', :layout => 'help-and-content' + else + render :template => 'layouts/popup-error', :layout => 'popup' + end + end + format.json do + @json_hash ||= {} + @json_hash[:success] = (status == :ok) + @json_hash[:alert] = msg + render :json => @json_hash + end + format.xml { head status } + end + end + # don't define find_opts for array inputs def json_hash(full_items, attributes, arg_list=[], find_opts={}, id_method=:id) page = params[:page].to_i -- 1.6.0.6
David Lutterkort
2009-May-04 23:15 UTC
[Ovirt-devel] [PATCH server 3/4] Remove handle_auth_error since we use rescue_with now
--- src/app/controllers/application.rb | 27 +-------------------- src/app/controllers/storage_volume_controller.rb | 5 +++- src/app/controllers/vm_controller.rb | 12 ++------- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/src/app/controllers/application.rb b/src/app/controllers/application.rb index 3902e78..638f9c0 100644 --- a/src/app/controllers/application.rb +++ b/src/app/controllers/application.rb @@ -111,36 +111,13 @@ class ApplicationController < ActionController::Base def authorize_action(privilege, msg=nil) msg ||= 'You have insufficient privileges to perform action.' unless authorized?(privilege) - handle_auth_error(msg) + handle_error(:message => msg, + :title => "Access Denied", :status => :forbidden) false else true end end - def handle_auth_error(msg) - respond_to do |format| - format.html do - @title = "Access denied" - @errmsg = msg - @ajax = params[:ajax] - @nolayout = params[:nolayout] - if @ajax - render :template => 'layouts/popup-error', :layout => 'tabs-and-content' - elsif @nolayout - render :template => 'layouts/popup-error', :layout => 'help-and-content' - else - render :template => 'layouts/popup-error', :layout => 'popup' - end - end - format.json do - @json_hash ||= {} - @json_hash[:success] = false - @json_hash[:alert] = msg - render :json => @json_hash - end - format.xml { head :forbidden } - end - end def handle_perm_error(error) handle_error(:error => error, :status => :forbidden, diff --git a/src/app/controllers/storage_volume_controller.rb b/src/app/controllers/storage_volume_controller.rb index b6b0593..c6ce58d 100644 --- a/src/app/controllers/storage_volume_controller.rb +++ b/src/app/controllers/storage_volume_controller.rb @@ -112,7 +112,10 @@ class StorageVolumeController < ApplicationController @storage_volume = StorageVolume.find(params[:id]) set_perms(@storage_volume.storage_pool.hardware_pool) unless authorized?(Privilege::MODIFY) and @storage_volume.storage_pool.user_subdividable - handle_auth_error("You do not have permission to delete this storage volume.") + handle_error(:message => + "You do not have permission to delete this storage volume.", + :status => :forbidden, + :title => "Access Denied") else alert, success = delete_volume_internal(@storage_volume) respond_to do |format| diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index ee6d79e..c276f0b 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -41,13 +41,9 @@ class VmController < ApplicationController end def show - begin - svc_show(params[:id]) - @actions = @vm.get_action_hash(@user) - render :layout => 'selection' - rescue PermissionError => perm_error - handle_auth_error(perm_error.message) - end + svc_show(params[:id]) + @actions = @vm.get_action_hash(@user) + render :layout => 'selection' end def add_to_smart_pool @@ -65,8 +61,6 @@ class VmController < ApplicationController begin alert = svc_create(params[:vm], params[:start_now]) render :json => { :object => "vm", :success => true, :alert => alert } - rescue PermissionError => perm_error - handle_auth_error(perm_error.message) rescue Exception => error json_error("vm", @vm, error) end -- 1.6.0.6
David Lutterkort
2009-May-04 23:15 UTC
[Ovirt-devel] [PATCH server 4/4] * app/controllers/host_controller.rb: factor biz logic out
The business logic is now in HostService. The main functional difference is that svc_list filters the host list by permission. --- src/app/controllers/host_controller.rb | 125 +++++++++++++------------------- src/app/services/host_service.rb | 119 ++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 76 deletions(-) create mode 100644 src/app/services/host_service.rb diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb index 994b5e2..9b683fa 100644 --- a/src/app/controllers/host_controller.rb +++ b/src/app/controllers/host_controller.rb @@ -19,8 +19,13 @@ class HostController < ApplicationController - EQ_ATTRIBUTES = [ :state, :arch, :hostname, :uuid, - :hardware_pool_id ] + include HostService + + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + verify :method => [:post, :put], :only => [ :create, :update ], + :redirect_to => { :action => :list } + verify :method => [:post, :delete], :only => :destroy, + :redirect_to => { :action => :list } def index list @@ -30,42 +35,21 @@ class HostController < ApplicationController end end - before_filter :pre_action, :only => [:host_action, :enable, :disable, :clear_vms, :edit_network] - before_filter :pre_addhost, :only => [:addhost] - - # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) - verify :method => [:post, :put], :only => [ :create, :update ], - :redirect_to => { :action => :list } - verify :method => [:post, :delete], :only => :destroy, - :redirect_to => { :action => :list } - - def list - conditions = [] - EQ_ATTRIBUTES.each do |attr| - if params[attr] - conditions << "#{attr} = :#{attr}" - end - end - @hosts = Host.find(:all, - :conditions => [conditions.join(" and "), params], - :order => "id") - end - + def list + svc_list(params) + end def show - if authorize_view - respond_to do |format| - format.html { render :layout => 'selection' } - format.xml { render :xml => @host.to_xml(:include => [ :cpus ] ) } - end + svc_show(params[:id]) + respond_to do |format| + format.html { render :layout => 'selection' } + format.xml { render :xml => @host.to_xml(:include => [ :cpus ] ) } end end def quick_summary - pre_show - if authorize_view - render :layout => false - end + svc_show(id) + render :layout => false end # retrieves data used by snapshot graphs @@ -73,15 +57,17 @@ class HostController < ApplicationController end def addhost - @hardware_pool = Pool.find(params[:hardware_pool_id]) - render :layout => 'popup' - end + # FIXME: This probably should go into PoolService.svc_modify, + # so that we have permission checks in only one place - def pre_addhost + # Old pre_addhost @pool = Pool.find(params[:hardware_pool_id]) @parent = @pool.parent set_perms(@pool) authorize_admin + # Old addhost + @hardware_pool = Pool.find(params[:hardware_pool_id]) + render :layout => 'popup' end def add_to_smart_pool @@ -89,6 +75,11 @@ class HostController < ApplicationController render :layout => 'popup' end + # FIXME: We implement the standard controller actions, but catch + # them in filters and kick out friendly warnings that you can't + # perform them on hosts. Tat's overkill - the only way for a user + # to get to these actions is with URL surgery or from a bug in the + # application, both of which deserve a noisy error def new end @@ -116,52 +107,40 @@ class HostController < ApplicationController end def disable - set_disabled(1) + svc_enable(params[:id], "disabled") + render :json => { + :object => :host, + :alert => "Host was successfully disabled", + :success => true + } end + def enable - set_disabled(0) - end - - def set_disabled(value) - operation = value == 1 ? "disabled" : "enabled" - begin - @host.is_disabled = value - @host.save! - @json_hash[:alert]="Host was successfully #{operation}" - @json_hash[:success]=true - rescue - @json_hash[:alert]="Error setting host to #{operation}" - @json_hash[:success]=false - end - render :json => @json_hash + svc_enable(params[:id], "enabled") + render :json => { + :object => :host, + :alert => "Host was successfully enabled", + :success => true + } end def clear_vms - begin - Host.transaction do - task = HostTask.new({ :user => get_login_user, - :task_target => @host, - :action => HostTask::ACTION_CLEAR_VMS, - :state => Task::STATE_QUEUED}) - task.save! - @host.is_disabled = true - @host.save! - end - @json_hash[:alert]="Clear VMs action was successfully queued." - @json_hash[:success]=true - rescue - @json_hash[:alert]="Error in queueing Clear VMs action." - @json_hash[:success]=false - end - render :json => @json_hash + svc_clear_vms(params[:id]) + render :json => { + :object => :host, + :alert => "Clear VMs action was successfully queued.", + :success => true + } end def edit_network + svc_modify(params[:id]) render :layout => 'popup' end def bondings_json - bondings = Host.find(params[:id]).bondings + svc_show(params[:id]) + bondings = @host.bondings render :json => bondings.collect{ |x| {:id => x.id, :name => x.name} } end @@ -180,12 +159,6 @@ class HostController < ApplicationController flash[:notice] = 'Hosts may not be edited via the web UI' redirect_to :action=> 'show', :id => @host end - def pre_action - @host = Host.find(params[:id]) - set_perms(@host.hardware_pool) - @json_hash = { :object => :host } - authorize_admin - end def pre_show @host = Host.find(params[:id]) set_perms(@host.hardware_pool) diff --git a/src/app/services/host_service.rb b/src/app/services/host_service.rb new file mode 100644 index 0000000..074d830 --- /dev/null +++ b/src/app/services/host_service.rb @@ -0,0 +1,119 @@ +#-- +# Copyright (C) 2009 Red Hat, Inc. +# Written by David Lutterkort <lutter at redhat.com> +# +# 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; version 2 of the License. +# +# 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. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. +#++ +# Business logic around hosts aka nodes +module HostService + + include ApplicationService + + # Host attributes on which we filter with '=' + EQ_ATTRIBUTES = [ :state, :arch, :hostname, :uuid, + :hardware_pool_id ] + + # List hosts matching criteria described by +params+; only the entries + # for keys in +EQ_ATTRIBUTES+ are used + # + # === Instance variables + # [<tt>@hosts</tt>] stores list of hosts matching criteria + # === Required permissions + # [<tt>Privilege::VIEW</tt>] no exception raised, <tt>@hosts</tt> + # is filtered by privilege + def svc_list(params) + conditions = [] + EQ_ATTRIBUTES.each do |attr| + if params[attr] + conditions << "hosts.#{attr} = :#{attr}" + end + end + # Add permission check + params = params.dup + params[:user] = get_login_user + params[:priv] = Privilege::VIEW + conditions << "privileges.name=:priv" + conditions << "permissions.uid=:user" + incl = [{ :hardware_pool => { :permissions => { :role => :privileges}}}] + @hosts = Host.find(:all, + :include => incl, + :conditions => [conditions.join(" and "), params], + :order => "hosts.id") + end + + # Load the Host with +id+ for viewing + # + # === Instance variables + # [<tt>@host</tt>] stores the Host with +id+ + # === Required permissions + # [<tt>Privilege::VIEW</tt>] on host's HardwarePool + def svc_show(id) + lookup(id, Privilege::VIEW) + end + + # Load the Host with +id+ for editing + # + # === Instance variables + # [<tt>@host</tt>] stores the Host with +id+ + # === Required permissions + # [<tt>Privilege::MODIFY</tt>] on host's HardwarePool + def svc_modify(id) + lookup(id, Privilege::MODIFY) + end + + # Set the disabled state of the Host with +id+ to <tt>:enabled</tt> + # or <tt>:disabled</tt> + # + # === Instance variables + # [<tt>@host</tt>] stores the Host with +id+ + # === Required permissions + # [<tt>Privilege::MODIFY</tt>] on host's HardwarePool + def svc_enable(id, state) + ind = ["enabled", "disabled"].index(state.to_s) + if ind.nil? + raise ArgumentError, "STATE must be 'enabled' or 'disabled'" + end + svc_modify(id) + @host.is_disabled = ind + @host.save! + end + + # Queue task to migrate all VM's off the Host with +id+, and mark the + # host as disabled + # + # === Instance variables + # [<tt>@host</tt>] stores the Host with +id+ + # === Required permissions + # [<tt>Privilege::MODIFY</tt>] on host's HardwarePool + def svc_clear_vms(id) + svc_modify(id) + Host.transaction do + task = HostTask.new({ :user => get_login_user, + :task_target => @host, + :action => HostTask::ACTION_CLEAR_VMS, + :state => Task::STATE_QUEUED}) + task.save! + @host.is_disabled = true + @host.save! + end + end + + private + def lookup(id, priv) + @host = Host.find(id) + authorized!(priv, @host.hardware_pool) + end +end -- 1.6.0.6