foreman-tasks 0.6.10 → 0.6.11

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,25 +1,42 @@
1
1
  Foreman Tasks
2
2
  =============
3
3
 
4
- Tasks management engine for Foreman. Gives you and overview of what's
5
- happening/happened in your Foreman instance.
4
+ Tasks management engine for Foreman. Gives you an overview of what's
5
+ happening/happened in your Foreman instance. A framework for asynchronous tasks in Foreman.
6
+
7
+ * Website: [TheForeman.org](http://theforeman.org)
8
+ * ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman)
9
+ * Issues: [foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
10
+ * Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About)
11
+ * Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net)
12
+ * Mailing lists:
13
+ * [foreman-users](https://groups.google.com/forum/?fromgroups#!forum/foreman-users)
14
+ * [foreman-dev](https://groups.google.com/forum/?fromgroups#!forum/foreman-dev)
6
15
 
7
16
  Installation
8
17
  ------------
9
18
 
10
- Put the following to your Foreman's bundle.d/Gemfile.local.rb:
19
+ Please see the Foreman manual for appropriate instructions:
11
20
 
12
- ```ruby
13
- gem 'dynflow', :git => 'https://github.com/Dynflow/dynflow.git'
14
- gem 'foreman-tasks', :git => 'https://github.com/theforeman/foreman-tasks.git'
15
- ```
21
+ * [Foreman: How to Install a Plugin](http://theforeman.org/manuals/latest/index.html#6.1InstallaPlugin)
16
22
 
17
- Run:
23
+ ### Red Hat, CentOS, Fedora, Scientific Linux (rpm)
18
24
 
19
- ```bash
20
- bundle install
21
- rake db:migrate
22
- ```
25
+ Set up the repo as explained in the link above, then run
26
+
27
+ # yum install ruby193-rubygem-foreman-tasks
28
+
29
+ ### Bundle (gem)
30
+
31
+ Add the following to bundler.d/Gemfile.local.rb in your Foreman installation directory (/usr/share/foreman by default)
32
+
33
+ $ gem 'foreman-tasks'
34
+
35
+ Then run `bundle install` and `foreman-rake db:migrate` from the same directory
36
+
37
+ --------------
38
+
39
+ To verify that the installation was successful, go to Foreman, top bar **Administer > About** and check 'foreman-tasks' shows up in the **System Status** menu under the Plugins tab. You should also see a **'Tasks'** button under the **Monitor** menu in the top bar.
23
40
 
24
41
  Usage
25
42
  -----
@@ -26,7 +26,7 @@ module ForemanTasks
26
26
  class BadRequest < Apipie::ParamError
27
27
  end
28
28
 
29
- before_filter :find_task, :only => [:show]
29
+ before_filter :find_resource, :only => [:show]
30
30
 
31
31
  api :GET, "/tasks/:id", "Show task details"
32
32
  param :id, :identifier, desc: "UUID of the task"
@@ -79,7 +79,7 @@ module ForemanTasks
79
79
  private
80
80
 
81
81
  def search_tasks(search_params)
82
- scope = ::ForemanTasks::Task.select('DISTINCT foreman_tasks_tasks.*')
82
+ scope = resource_scope_for_index.select('DISTINCT foreman_tasks_tasks.*')
83
83
  scope = ordering_scope(scope, search_params)
84
84
  scope = search_scope(scope, search_params)
85
85
  scope = active_scope(scope, search_params)
@@ -157,8 +157,17 @@ module ForemanTasks
157
157
 
158
158
  private
159
159
 
160
- def find_task
161
- @task = Task.find_by_id(params[:id])
160
+ def resource_scope(options = {})
161
+ @resource_scope ||= ForemanTasks::Task.authorized("#{action_permission}_foreman_tasks")
162
+ end
163
+
164
+ def action_permission
165
+ case params[:action]
166
+ when 'bulk_search'
167
+ :view
168
+ else
169
+ super
170
+ end
162
171
  end
163
172
  end
164
173
  end
@@ -3,29 +3,29 @@ module ForemanTasks
3
3
  include Foreman::Controller::AutoCompleteSearch
4
4
 
5
5
  def show
6
- @task = Task.find(params[:id])
6
+ @task = find_resource
7
7
  end
8
8
 
9
9
  def index
10
10
  params[:order] ||= 'started_at DESC'
11
- @tasks = filter(Task)
11
+ @tasks = filter(resource_base)
12
12
  end
13
13
 
14
14
  def sub_tasks
15
- task = Task.find(params[:id])
15
+ task = find_resource
16
16
  @tasks = filter(task.sub_tasks)
17
17
  render :index
18
18
  end
19
19
 
20
20
  def cancel_step
21
- task = find_task
21
+ task = find_dynflow_task
22
22
  flash[:notice] = _("Trying to cancel step %s") % params[:step_id]
23
23
  ForemanTasks.dynflow.world.event(task.external_id, params[:step_id].to_i, ::Dynflow::Action::Cancellable::Cancel).wait
24
24
  redirect_to foreman_tasks_task_path(task)
25
25
  end
26
26
 
27
27
  def resume
28
- task = find_task
28
+ task = find_dynflow_task
29
29
  if task.resumable?
30
30
  ForemanTasks.dynflow.world.execute(task.execution_plan.id)
31
31
  flash[:notice] = _('The execution was resumed.')
@@ -36,7 +36,7 @@ module ForemanTasks
36
36
  end
37
37
 
38
38
  def unlock
39
- task = find_task
39
+ task = find_dynflow_task
40
40
  if task.paused?
41
41
  task.state = :stopped
42
42
  task.save!
@@ -48,7 +48,7 @@ module ForemanTasks
48
48
  end
49
49
 
50
50
  def force_unlock
51
- task = find_task
51
+ task = find_dynflow_task
52
52
  task.state = :stopped
53
53
  task.save!
54
54
  flash[:notice] = _('The task resources were unlocked with force.')
@@ -62,8 +62,27 @@ module ForemanTasks
62
62
 
63
63
  private
64
64
 
65
- def find_task
66
- ForemanTasks::Task::DynflowTask.find(params[:id])
65
+ def controller_permission
66
+ 'foreman_tasks'
67
+ end
68
+
69
+ def action_permission
70
+ case params[:action]
71
+ when 'sub_tasks'
72
+ :view
73
+ when 'resume', 'unlock', 'force_unlock', 'cancel_step'
74
+ :edit
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ def resource_scope(options = {})
81
+ @resource_scope ||= ForemanTasks::Task.authorized("#{action_permission}_foreman_tasks")
82
+ end
83
+
84
+ def find_dynflow_task
85
+ resource_scope.where(:type => 'ForemanTasks::Task::DynflowTask').find(params[:id])
67
86
  end
68
87
 
69
88
  def filter(scope)
@@ -73,6 +73,7 @@ module ForemanTasks
73
73
  # of planning phase is expected to be commited when execution occurs. Also
74
74
  # we want to be able to rollback the whole db operation when planning fails.
75
75
  def plan_action(action_class, *args)
76
+ return if ForemanTasks.dynflow.config.disable_active_record_actions
76
77
  @execution_plan = ::ForemanTasks.dynflow.world.plan(action_class, *args)
77
78
  raise @execution_plan.errors.first if @execution_plan.error?
78
79
  end
@@ -91,7 +92,10 @@ module ForemanTasks
91
92
  # we would start the execution phase inside this transaction which would lead
92
93
  # to unexpected results.
93
94
  def dynflow_task_wrap(method)
94
- return yield if @_dynflow_task_wrapped
95
+ if ForemanTasks.dynflow.config.disable_active_record_actions ||
96
+ @_dynflow_task_wrapped
97
+ return yield
98
+ end
95
99
  @_dynflow_task_wrapped = true
96
100
 
97
101
  action = case method
@@ -1,7 +1,8 @@
1
- require 'uuidtools'
1
+ require 'securerandom'
2
2
 
3
3
  module ForemanTasks
4
4
  class Task < ActiveRecord::Base
5
+ include Authorizable
5
6
 
6
7
  # TODO missing validation of states
7
8
 
@@ -14,7 +15,8 @@ module ForemanTasks
14
15
 
15
16
  # in fact, the task has only one owner but Rails don't let you to
16
17
  # specify has_one relation though has_many relation
17
- has_many :owners, :through => :locks, :source => :resource, :source_type => 'User'
18
+ has_many :owners, :through => :locks, :source => :resource, :source_type => 'User',
19
+ :conditions => ["foreman_tasks_locks.name = ?", Lock::OWNER_LOCK_NAME]
18
20
 
19
21
  scoped_search :on => :id, :complete_value => false
20
22
  scoped_search :on => :label, :complete_value => true
@@ -23,6 +25,7 @@ module ForemanTasks
23
25
  scoped_search :on => :started_at, :complete_value => false
24
26
  scoped_search :in => :locks, :on => :resource_type, :complete_value => true, :rename => "resource_type", :ext_method => :search_by_generic_resource
25
27
  scoped_search :in => :locks, :on => :resource_id, :complete_value => false, :rename => "resource_id", :ext_method => :search_by_generic_resource
28
+ scoped_search :in => :owners, :on => :id, :complete_value => true, :rename => "owner.id", :ext_method => :search_by_owner
26
29
  scoped_search :in => :owners, :on => :login, :complete_value => true, :rename => "owner.login", :ext_method => :search_by_owner
27
30
  scoped_search :in => :owners, :on => :firstname, :complete_value => true, :rename => "owner.firstname", :ext_method => :search_by_owner
28
31
 
@@ -83,18 +86,28 @@ module ForemanTasks
83
86
  end
84
87
 
85
88
  def self.search_by_owner(key, operator, value)
89
+ return { :conditions => '0 = 1' } if value == 'current_user' && User.current.nil?
90
+
86
91
  key_name = self.connection.quote_column_name(key.sub(/^.*\./,''))
87
- joins = <<-JOINS
92
+ joins = <<-SQL
88
93
  INNER JOIN foreman_tasks_locks AS foreman_tasks_locks_owner
89
94
  ON (foreman_tasks_locks_owner.task_id = foreman_tasks_tasks.id AND
90
95
  foreman_tasks_locks_owner.resource_type = 'User' AND
91
96
  foreman_tasks_locks_owner.name = '#{Lock::OWNER_LOCK_NAME}')
92
- INNER JOIN users
93
- ON (users.id = foreman_tasks_locks_owner.resource_id)
94
- JOINS
95
-
97
+ SQL
98
+ if key !~ /\.id\Z/
99
+ joins << <<-SQL
100
+ INNER JOIN users
101
+ ON (users.id = foreman_tasks_locks_owner.resource_id)
102
+ SQL
103
+ end
96
104
  condition = if key.blank?
97
105
  sanitize_sql_for_conditions(["users.login #{operator} ? or users.firstname #{operator} ? ", value, value])
106
+ elsif key =~ /\.id\Z/
107
+ if value == 'current_user'
108
+ value = User.current.id
109
+ end
110
+ sanitize_sql_for_conditions(["foreman_tasks_locks_owner.resource_id #{operator} ?", value])
98
111
  else
99
112
  sanitize_sql_for_conditions(["users.#{key_name} #{operator} ?", value])
100
113
  end
@@ -112,10 +125,15 @@ module ForemanTasks
112
125
  end
113
126
  end
114
127
 
128
+ def self.authorized_resource_name
129
+ # We don't want STI subclasses to have separate permissions
130
+ 'ForemanTasks::Task'
131
+ end
132
+
115
133
  protected
116
134
 
117
135
  def generate_id
118
- self.id ||= UUIDTools::UUID.random_create.to_s
136
+ self.id ||= SecureRandom.uuid
119
137
  end
120
138
  end
121
139
  end
@@ -1,26 +1,27 @@
1
1
  <p>
2
2
  <%= link_to(_('Auto Reload'), '', class: %w(btn btn-sm btn-default reload-button hidden)) %>
3
+ <% if authorized_for(:permission => :edit_foreman_tasks, :auth_object => @task) %>
4
+ <%= link_to(_('Dynflow console'),
5
+ format((defined?(Rails) ? request.script_name : '') + '/foreman_tasks/dynflow/%s', @task.external_id),
6
+ class: %w(btn btn-sm btn-default)) if Setting['dynflow_enable_console'] %>
3
7
 
4
- <%= link_to(_('Dynflow console'),
5
- format((defined?(Rails) ? request.script_name : '') + '/foreman_tasks/dynflow/%s', @task.external_id),
6
- class: %w(btn btn-sm btn-default)) if Setting['dynflow_enable_console'] %>
8
+ <%= link_to(_('Resume'),
9
+ resume_foreman_tasks_task_path(@task),
10
+ class: ['btn', 'btn-sm', 'btn-primary', ('disabled' unless @task.resumable?)].compact,
11
+ method: :post) %>
7
12
 
8
- <%= link_to(_('Resume'),
9
- resume_foreman_tasks_task_path(@task),
10
- class: ['btn', 'btn-sm', 'btn-primary', ('disabled' unless @task.resumable?)].compact,
11
- method: :post) %>
13
+ <%= link_to(_('Unlock'),
14
+ '',
15
+ class: ['btn', 'btn-sm', 'btn-warning', 'reload-button-stop', ('disabled' unless @task.state == 'paused')].compact,
16
+ :'data-toggle' => "modal",
17
+ :'data-target' => "#unlock_modal") %>
12
18
 
13
- <%= link_to(_('Unlock'),
14
- '',
15
- class: ['btn', 'btn-sm', 'btn-warning', 'reload-button-stop', ('disabled' unless @task.state == 'paused')].compact,
16
- :'data-toggle' => "modal",
17
- :'data-target' => "#unlock_modal") %>
18
-
19
- <%= link_to(_('Force Unlock'),
20
- '',
21
- class: ['btn', 'btn-sm', 'btn-danger', 'reload-button-stop', ('disabled' if @task.state == 'stopped')].compact,
22
- :'data-toggle' => "modal",
23
- :'data-target' => "#force_unlock_modal") %>
19
+ <%= link_to(_('Force Unlock'),
20
+ '',
21
+ class: ['btn', 'btn-sm', 'btn-danger', 'reload-button-stop', ('disabled' if @task.state == 'stopped')].compact,
22
+ :'data-toggle' => "modal",
23
+ :'data-target' => "#force_unlock_modal") %>
24
+ <% end %>
24
25
  </p>
25
26
 
26
27
  <div class="modal fade" id="unlock_modal" tabindex="-1" role="dialog" aria-labelledby="Deploy" aria-hidden="true">
@@ -95,7 +96,7 @@
95
96
  </div>
96
97
  <div>
97
98
  <span class="param-name"><%= _("Result") %>:</span>
98
- <span class="param-value"><%= @task.result %></span>
99
+ <span class="param-value"><%= @task.state == 'running' ? '-' : @task.result %></span>
99
100
  </div>
100
101
  <div>
101
102
  <span class="param-name"><%= _("Params") %>:</span>
@@ -115,16 +116,19 @@
115
116
  <div class="col-xs-11">
116
117
  <div class="progress progress-striped <%= 'active' if @task.state == 'running' %>">
117
118
  <% progress = 100 * @task.progress %>
118
- <% classes = ['progress-bar',
119
- case @task.result
120
- when 'success'
121
- 'progress-bar-success'
122
- when 'error'
123
- 'progress-bar-danger'
124
- else
125
- nil
126
- end]
127
- %>
119
+ <% progress_class = if @task.state == 'running'
120
+ nil
121
+ else
122
+ case @task.result
123
+ when 'success'
124
+ 'progress-bar-success'
125
+ when 'error'
126
+ 'progress-bar-danger'
127
+ else
128
+ nil
129
+ end
130
+ end %>
131
+ <% classes = ['progress-bar', progress_class] %>
128
132
  <div class="<%= classes.join ' ' %>"
129
133
  role="progressbar"
130
134
  aria-valuenow="<%= progress %>"
@@ -1,6 +1,6 @@
1
1
  [Unit]
2
2
  Description=Foreman jobs daemon
3
- Documentation=https://github.com/iNecas/foreman-tasks
3
+ Documentation=https://github.com/theforeman/foreman-tasks
4
4
  After=network.target remote-fs.target nss-lookup.target
5
5
 
6
6
  [Service]
data/lib/foreman_tasks.rb CHANGED
@@ -3,6 +3,7 @@ require 'foreman_tasks/task_error'
3
3
  require 'foreman_tasks/engine'
4
4
  require 'foreman_tasks/dynflow'
5
5
  require 'foreman_tasks/triggers'
6
+ require 'foreman_tasks/authorizer_ext'
6
7
 
7
8
  module ForemanTasks
8
9
  extend Algebrick::TypeCheck
@@ -0,0 +1,19 @@
1
+ module ForemanTasks
2
+ # Monkey path until http://projects.theforeman.org/issues/8919 is
3
+ # resolved and released
4
+ module AuthorizerExt
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ alias_method_chain :resource_name, :authorized_resource_name
9
+ end
10
+
11
+ def resource_name_with_authorized_resource_name(klass)
12
+ if klass.respond_to?(:authorized_resource_name)
13
+ return klass.authorized_resource_name
14
+ else
15
+ resource_name_without_authorized_resource_name(klass)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'dynflow'
2
3
 
3
4
  module ForemanTasks
@@ -6,6 +7,7 @@ module ForemanTasks
6
7
  require 'foreman_tasks/dynflow/configuration'
7
8
  require 'foreman_tasks/dynflow/persistence'
8
9
  require 'foreman_tasks/dynflow/daemon'
10
+ require 'foreman_tasks/dynflow/console_authorizer'
9
11
 
10
12
  def initialize
11
13
  @required = false
@@ -82,14 +84,9 @@ module ForemanTasks
82
84
  def web_console
83
85
  ::Dynflow::WebConsole.setup do
84
86
  before do
85
- rack_request = Rack::Request.new(env)
86
- user_id, expires_at = rack_request.session.
87
- values_at('user', 'expires_at')
88
- if Setting[:dynflow_console_require_auth] &&
89
- (!Setting[:dynflow_enable_console] ||
90
- (user_id.nil? || !User.find(user_id).admin) ||
91
- Time.now.to_i > expires_at)
92
- redirect('dashboard')
87
+ if !Setting[:dynflow_enable_console] ||
88
+ (Setting[:dynflow_console_require_auth] && !ConsoleAuthorizer.new(env).allow?)
89
+ halt 403, 'Access forbidden'
93
90
  end
94
91
  end
95
92
 
@@ -33,6 +33,11 @@ module ForemanTasks
33
33
  # what rake tasks should run their own executor, not depending on the external one
34
34
  attr_accessor :rake_tasks_with_executor
35
35
 
36
+ # if true, the ForemanTasks::Concerns::ActionTriggering will make
37
+ # no effect. Useful for testing, where we mignt not want to execute
38
+ # the orchestration tied to the models.
39
+ attr_accessor :disable_active_record_actions
40
+
36
41
  def initialize
37
42
  self.action_logger = Rails.logger
38
43
  self.dynflow_logger = Rails.logger
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ module ForemanTasks
3
+ class Dynflow::ConsoleAuthorizer
4
+ def initialize(env)
5
+ @rack_request = Rack::Request.new(env)
6
+ @user_id, @expires_at = @rack_request.session.values_at('user', 'expires_at')
7
+ @user = User.find_by_id(@user_id) unless session_expired?
8
+ end
9
+
10
+ def allow?
11
+ @user && (unlimited_edit? || authorized_for_task?)
12
+ end
13
+
14
+ private
15
+
16
+ def session_expired?
17
+ Time.now.to_i > @expires_at.to_i
18
+ end
19
+
20
+ def unlimited_edit?
21
+ return true if @user.admin?
22
+ # users with unlimited edit_foreman_tasks can operate with the
23
+ # console no matter what task it is…
24
+ edit_permission = Permission.where(:name => :edit_foreman_tasks, :resource_type => ForemanTasks::Task.name).first
25
+ if @user.filters.joins(:filterings).unlimited.where('filterings.permission_id' => edit_permission).first
26
+ return true
27
+ end
28
+ end
29
+
30
+ def authorized_for_task?
31
+ if task = extract_task
32
+ begin
33
+ original_user = User.current
34
+ User.current = @user
35
+ return Authorizer.new(@user).can?(:edit_foreman_tasks, task)
36
+ ensure
37
+ User.current = original_user
38
+ end
39
+ else
40
+ return false
41
+ end
42
+ end
43
+
44
+ def extract_task
45
+ dynflow_id = @rack_request.path_info[/^\/([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})/,1]
46
+ unless dynflow_id.empty?
47
+ ForemanTasks::Task::DynflowTask.where(:external_id => dynflow_id).first
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -14,6 +14,22 @@ module ForemanTasks
14
14
  :url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
15
15
  :caption => N_('Tasks'),
16
16
  :parent => :monitor_menu
17
+
18
+ security_block :foreman_tasks do |map|
19
+ permission :view_foreman_tasks, {:'foreman_tasks/tasks' => [:auto_complete_search, :sub_tasks, :index, :show],
20
+ :'foreman_tasks/api/tasks' => [:bulk_search, :show] }, :resource_type => ForemanTasks::Task.name
21
+ permission :edit_foreman_tasks, {:'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step]}, :resource_type => ForemanTasks::Task.name
22
+ end
23
+
24
+ view_permission = Permission.where(:name => :view_foreman_tasks, :resource_type => ForemanTasks::Task.name).first
25
+ unless Role.anonymous.permissions.include?(view_permission)
26
+ Role.anonymous.filters.create(:search => 'owner.id = current_user') do |filter|
27
+ filter.filterings.build { |f| f.permission = view_permission }
28
+ end
29
+ end
30
+
31
+ role "Tasks Manager", [:view_foreman_tasks, :edit_foreman_tasks]
32
+ role "Tasks Reader", [:view_foreman_tasks]
17
33
  end
18
34
  end
19
35
 
@@ -53,11 +69,8 @@ module ForemanTasks
53
69
  end
54
70
 
55
71
  initializer "foreman_tasks.initialize_dynflow" do
56
- ForemanTasks.dynflow.eager_load_actions!
57
- ActionDispatch::Reloader.to_prepare do
58
- ForemanTasks.dynflow.eager_load_actions!
59
- end
60
72
 
73
+ ForemanTasks.dynflow.eager_load_actions!
61
74
  ForemanTasks.dynflow.config.increase_db_pool_size
62
75
 
63
76
  unless ForemanTasks.dynflow.config.lazy_initialization
@@ -73,8 +86,16 @@ module ForemanTasks
73
86
  end
74
87
  end
75
88
 
89
+ config.to_prepare do
90
+ ForemanTasks.dynflow.eager_load_actions!
91
+
92
+ Authorizer.send(:include, AuthorizerExt)
93
+ end
94
+
95
+
76
96
  rake_tasks do
77
97
  load File.expand_path('../tasks/dynflow.rake', __FILE__)
98
+ load File.expand_path('../tasks/test.rake', __FILE__)
78
99
  end
79
100
  end
80
101
 
@@ -0,0 +1,22 @@
1
+ namespace :test do
2
+ task :foreman_tasks => 'db:test:prepare' do
3
+ test_task = Rake::TestTask.new('foreman_tasks_test_task') do |t|
4
+ t.libs << ["test", "#{ForemanTasks::Engine.root}/test"]
5
+ t.test_files = ["#{ForemanTasks::Engine.root}/test/**/*_test.rb"]
6
+ t.verbose = true
7
+ end
8
+
9
+ Rake::Task[test_task.name].invoke
10
+ end
11
+ end
12
+
13
+ Rake::Task[:test].enhance do
14
+ Rake::Task['test:foreman_tasks'].invoke
15
+ end
16
+
17
+ load 'tasks/jenkins.rake'
18
+ if Rake::Task.task_defined?(:'jenkins:unit')
19
+ Rake::Task["jenkins:unit"].enhance do
20
+ Rake::Task['test:foreman_tasks'].invoke
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = "0.6.10"
2
+ VERSION = "0.6.11"
3
3
  end
@@ -0,0 +1,23 @@
1
+ require "foreman_tasks_test_helper"
2
+
3
+ module ForemanTasks
4
+ class Api::TasksControllerTest < ActionController::TestCase
5
+ describe 'tasks api controller' do
6
+ tests ForemanTasks::Api::TasksController
7
+
8
+ before do
9
+ User.current = User.where(:login => 'apiadmin').first
10
+ @request.env['HTTP_ACCEPT'] = 'application/json'
11
+ @request.env['CONTENT_TYPE'] = 'application/json'
12
+ @task = FactoryGirl.create(:dynflow_task, :user_create_task)
13
+ end
14
+
15
+ it 'searches for task on GET' do
16
+ get(:show, :id => @task.id)
17
+ assert_response :success
18
+ assert_template 'api/tasks/show'
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ FactoryGirl.define do
2
+ factory :some_task, :class => ForemanTasks::Task do
3
+ sequence(:label) { |n| "task#{n}" }
4
+ type 'ForemanTasks::Task'
5
+ state 'stopped'
6
+ result 'success'
7
+
8
+ transient do
9
+ set_owner nil
10
+ end
11
+
12
+ after(:create) do |task, evaluator|
13
+ ForemanTasks::Lock.owner!(evaluator.set_owner, task.id) if evaluator.set_owner
14
+ end
15
+
16
+ factory :dynflow_task, :class => ForemanTasks::Task::DynflowTask do
17
+ label "Support::DummyDynflowAction"
18
+ type "ForemanTasks::Task::DynflowTask"
19
+ started_at "2014-10-01 11:15:55"
20
+ ended_at "2014-10-01 11:15:57"
21
+ state "stopped"
22
+ result "success"
23
+ parent_task_id nil
24
+
25
+ after(:build) do |task|
26
+ dynflow_task = ForemanTasks.dynflow.world.plan(Support::DummyDynflowAction)
27
+ # remove the task created automatically by the persistence
28
+ ForemanTasks::Task.where(:external_id => dynflow_task.id).delete_all
29
+ task.external_id = dynflow_task.id
30
+ end
31
+
32
+ trait :user_create_task do
33
+ label "Actions::User::Create"
34
+ end
35
+
36
+ trait :product_create_task do
37
+ label "Actions::Katello::Product::Create"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+ require_relative './support/dummy_dynflow_action'
3
+
4
+ FactoryGirl.definition_file_paths = ["#{ForemanTasks::Engine.root}/test/factories"]
5
+ FactoryGirl.find_definitions
6
+
7
+ ForemanTasks.dynflow.require!
8
+ ForemanTasks.dynflow.config.disable_active_record_actions = true
@@ -0,0 +1,48 @@
1
+ require "foreman_tasks_test_helper"
2
+
3
+ module ForemanTasks
4
+ class TasksHelperTest < ActionView::TestCase
5
+
6
+ describe 'when formatting simple input' do
7
+ before do
8
+ @task = FactoryGirl.build(:dynflow_task, :user_create_task)
9
+ humanized = {:action=>"Create", :input=>[[:user, {:text=>"user 'Anonymous Admin'", :link=>nil}]], :output=>"", :errors=>[]}
10
+ @task.stubs(:input).returns({"user"=>{"id"=>1, "name"=>"Anonymous Admin"}, "locale"=>"en"})
11
+ @task.stubs(:humanized).returns(humanized)
12
+ end
13
+
14
+ it 'formats the task input properly' do
15
+ expects(:h).with("user 'Anonymous Admin'")
16
+ format_task_input(@task)
17
+ expects(:h).with("Create user 'Anonymous Admin'")
18
+ format_task_input(@task, true)
19
+ end
20
+
21
+ end
22
+
23
+ describe 'when formatting input' do
24
+ before do
25
+ @task = FactoryGirl.build(:dynflow_task, :product_create_task)
26
+ humanized = {:action=>"Create",
27
+ :input=>[[:product, {:text=>"product 'product-2'", :link=>"#/products/3/info"}], [:organization, {:text=>"organization 'test-0'", :link=>"/organizations/3/edit"}]],
28
+ :output=>"",
29
+ :errors=>[]}
30
+ input = {"product"=>{"id"=>3, "name"=>"product-2", "label"=>"product-2", "cp_id"=>nil},
31
+ "provider"=>{"id"=>3, "name"=>"Anonymous"},
32
+ "organization"=>{"id"=>3, "name"=>"test-0", "label"=>"test-0"},
33
+ "cp_id"=>"1412251033866",
34
+ "locale"=>"en"}
35
+ @task.stubs(:input).returns(input)
36
+ @task.stubs(:humanized).returns(humanized)
37
+ end
38
+
39
+ it 'formats the task input properly' do
40
+ response = "product 'product-2'; organization 'test-0'"
41
+ expects(:h).with(response)
42
+ format_task_input(@task)
43
+ expects(:h).with("Create #{response}")
44
+ format_task_input(@task, true)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ module Support
2
+ class DummyDynflowAction < Dynflow::Action
3
+ end
4
+ end
@@ -0,0 +1,79 @@
1
+ require "foreman_tasks_test_helper"
2
+
3
+ module ForemanTasks
4
+ class DynflowConsoleAuthorizerTest < ActiveSupport::TestCase
5
+ include Rack::Test::Methods
6
+
7
+ before do
8
+ User.current = User.where(:login => 'apiadmin').first
9
+ end
10
+
11
+ let(:own_task) { FactoryGirl.create(:dynflow_task, :set_owner => user) }
12
+ let(:foreign_task) { FactoryGirl.create(:dynflow_task) }
13
+
14
+ let(:edit_foreman_tasks_permission) do
15
+ FactoryGirl.build(:permission).tap do |permission|
16
+ permission.name = :edit_foreman_tasks
17
+ permission.resource_type = ForemanTasks::Task.name
18
+ permission.save!
19
+ end
20
+ end
21
+
22
+ def dynflow_console_authorized?(task = nil)
23
+ dynflow_path = '/'
24
+ dynflow_path += task.external_id.to_s if task
25
+ dynflow_rack_env = { "rack.session" => { "user" => user.id, "expires_at" => Time.now + 100 },
26
+ "PATH_INFO" => dynflow_path}
27
+ ForemanTasks::Dynflow::ConsoleAuthorizer.new(dynflow_rack_env).allow?
28
+ end
29
+
30
+ describe 'admin user' do
31
+ let(:user) { FactoryGirl.create(:user, :admin) }
32
+ it 'can see all tasks' do
33
+ assert dynflow_console_authorized?
34
+ assert dynflow_console_authorized?(own_task)
35
+ assert dynflow_console_authorized?(foreign_task)
36
+ end
37
+ end
38
+
39
+ describe 'user with unlimited edit_foreman_tasks permissions' do
40
+ let(:user) do
41
+ user_role = FactoryGirl.create(:user_user_role)
42
+ FactoryGirl.create(:filter,
43
+ :role => user_role.role, :permissions => [edit_foreman_tasks_permission])
44
+ user_role.owner
45
+ end
46
+
47
+ it 'can see all tasks' do
48
+ assert dynflow_console_authorized?
49
+ assert dynflow_console_authorized?(own_task)
50
+ assert dynflow_console_authorized?(foreign_task)
51
+ end
52
+ end
53
+
54
+ describe 'user with limited edit_foreman_tasks permissions' do
55
+ let(:user) do
56
+ user_role = FactoryGirl.create(:user_user_role)
57
+ FactoryGirl.create(:filter,
58
+ :search => 'owner.id = current_user',
59
+ :role => user_role.role, :permissions => [edit_foreman_tasks_permission])
60
+ user_role.owner
61
+ end
62
+
63
+ it 'can see only the tasks he has permissions on' do
64
+ refute dynflow_console_authorized?
65
+ assert dynflow_console_authorized?(own_task)
66
+ refute dynflow_console_authorized?(foreign_task)
67
+ end
68
+ end
69
+
70
+ describe 'user without edit_foreman_tasks permissions' do
71
+ let(:user) { FactoryGirl.create(:user) }
72
+ it 'can not see any tasks' do
73
+ refute dynflow_console_authorized?
74
+ refute dynflow_console_authorized?(own_task)
75
+ refute dynflow_console_authorized?(foreign_task)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,37 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ class TasksTest < ActiveSupport::TestCase
4
+ describe 'filtering by current user' do
5
+ before { @original_current_user = User.current }
6
+ after { User.current = @original_current_user }
7
+
8
+ test "can search the tasks by current_user" do
9
+ user_one = FactoryGirl.create(:user)
10
+ user_two = FactoryGirl.create(:user)
11
+
12
+ task_one = FactoryGirl.create(:some_task, :set_owner => user_one)
13
+ task_two = FactoryGirl.create(:some_task, :set_owner => user_two)
14
+
15
+ User.current = user_one
16
+ assert_equal [task_one], ForemanTasks::Task.search_for("owner.id = current_user")
17
+ end
18
+ end
19
+
20
+ describe 'authorization filtering' do
21
+ it 'can filter by the task subject' do
22
+ user_role = FactoryGirl.create(:user_user_role)
23
+ user = user_role.owner
24
+ role = user_role.role
25
+ permission = FactoryGirl.build(:permission)
26
+ permission.resource_type = 'ForemanTasks::Task'
27
+ permission.save!
28
+ FactoryGirl.create(:filter, :role => role, :permissions => [permission])
29
+
30
+ User.current = user
31
+ task = FactoryGirl.create(:dynflow_task)
32
+
33
+ auth = Authorizer.new(user)
34
+ assert auth.can?(permission.name.to_sym, task)
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.10
4
+ version: 0.6.11
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,40 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-11 00:00:00.000000000 Z
12
+ date: 2015-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rails
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ~>
20
- - !ruby/object:Gem::Version
21
- version: 3.2.0
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ~>
28
- - !ruby/object:Gem::Version
29
- version: 3.2.0
30
- - !ruby/object:Gem::Dependency
31
- name: uuidtools
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ! '>='
36
- - !ruby/object:Gem::Version
37
- version: '0'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
46
14
  - !ruby/object:Gem::Dependency
47
15
  name: dynflow
48
16
  requirement: !ruby/object:Gem::Requirement
@@ -126,65 +94,73 @@ executables: []
126
94
  extensions: []
127
95
  extra_rdoc_files: []
128
96
  files:
129
- - app/assets/stylesheets/foreman_tasks/application.css.scss
130
97
  - app/helpers/foreman_tasks/tasks_helper.rb
98
+ - app/models/foreman_tasks/task/dynflow_task.rb
99
+ - app/models/foreman_tasks/concerns/host_action_subject.rb
100
+ - app/models/foreman_tasks/concerns/action_triggering.rb
101
+ - app/models/foreman_tasks/concerns/architecture_action_subject.rb
102
+ - app/models/foreman_tasks/concerns/action_subject.rb
103
+ - app/models/foreman_tasks/lock.rb
104
+ - app/models/foreman_tasks/task.rb
105
+ - app/models/setting/foreman_tasks.rb
106
+ - app/assets/stylesheets/foreman_tasks/application.css.scss
107
+ - app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb
108
+ - app/controllers/foreman_tasks/api/tasks_controller.rb
109
+ - app/controllers/foreman_tasks/tasks_controller.rb
131
110
  - app/lib/actions/helpers/args_serialization.rb
132
- - app/lib/actions/helpers/humanizer.rb
133
111
  - app/lib/actions/helpers/lock.rb
134
- - app/lib/actions/entry_action.rb
135
- - app/lib/actions/base.rb
112
+ - app/lib/actions/helpers/humanizer.rb
136
113
  - app/lib/actions/bulk_action.rb
137
- - app/lib/actions/middleware/keep_current_user.rb
138
- - app/lib/actions/foreman/host/import_facts.rb
139
114
  - app/lib/actions/foreman/architecture/create.rb
140
115
  - app/lib/actions/foreman/architecture/update.rb
141
116
  - app/lib/actions/foreman/architecture/destroy.rb
142
- - app/views/foreman_tasks/api/tasks/show.json.rabl
143
- - app/views/foreman_tasks/tasks/_running_steps.html.erb
144
- - app/views/foreman_tasks/tasks/show.html.erb
117
+ - app/lib/actions/foreman/host/import_facts.rb
118
+ - app/lib/actions/entry_action.rb
119
+ - app/lib/actions/base.rb
120
+ - app/lib/actions/middleware/keep_current_user.rb
145
121
  - app/views/foreman_tasks/tasks/_details.html.erb
146
122
  - app/views/foreman_tasks/tasks/index.html.erb
147
- - app/views/foreman_tasks/tasks/_raw.html.erb
123
+ - app/views/foreman_tasks/tasks/_running_steps.html.erb
148
124
  - app/views/foreman_tasks/tasks/_errors.html.erb
125
+ - app/views/foreman_tasks/tasks/_raw.html.erb
126
+ - app/views/foreman_tasks/tasks/show.html.erb
149
127
  - app/views/foreman_tasks/tasks/_locks.html.erb
150
- - app/controllers/foreman_tasks/api/tasks_controller.rb
151
- - app/controllers/foreman_tasks/tasks_controller.rb
152
- - app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb
153
- - app/models/foreman_tasks/concerns/architecture_action_subject.rb
154
- - app/models/foreman_tasks/concerns/action_triggering.rb
155
- - app/models/foreman_tasks/concerns/host_action_subject.rb
156
- - app/models/foreman_tasks/concerns/action_subject.rb
157
- - app/models/foreman_tasks/task/dynflow_task.rb
158
- - app/models/foreman_tasks/lock.rb
159
- - app/models/foreman_tasks/task.rb
160
- - app/models/setting/foreman_tasks.rb
128
+ - app/views/foreman_tasks/api/tasks/show.json.rabl
161
129
  - bin/dynflow-executor
162
130
  - bin/foreman-tasks
163
131
  - config/routes.rb
164
- - db/migrate/20131209122644_create_foreman_tasks_locks.rb
165
132
  - db/migrate/20131205204140_create_foreman_tasks.rb
166
133
  - db/migrate/20140324104010_remove_foreman_tasks_progress.rb
167
134
  - db/migrate/20140813215942_add_parent_task_id.rb
168
- - deploy/foreman-tasks.init
135
+ - db/migrate/20131209122644_create_foreman_tasks_locks.rb
169
136
  - deploy/foreman-tasks.service
170
137
  - deploy/foreman-tasks.sysconfig
138
+ - deploy/foreman-tasks.init
171
139
  - lib/tasks/gettext.rake
140
+ - lib/foreman-tasks.rb
172
141
  - lib/foreman_tasks/tasks/dynflow.rake
173
- - lib/foreman_tasks/engine.rb
142
+ - lib/foreman_tasks/tasks/test.rake
174
143
  - lib/foreman_tasks/version.rb
175
144
  - lib/foreman_tasks/triggers.rb
176
- - lib/foreman_tasks/task_error.rb
145
+ - lib/foreman_tasks/dynflow.rb
146
+ - lib/foreman_tasks/dynflow/console_authorizer.rb
177
147
  - lib/foreman_tasks/dynflow/configuration.rb
178
148
  - lib/foreman_tasks/dynflow/persistence.rb
179
149
  - lib/foreman_tasks/dynflow/daemon.rb
180
- - lib/foreman_tasks/dynflow.rb
150
+ - lib/foreman_tasks/authorizer_ext.rb
151
+ - lib/foreman_tasks/engine.rb
152
+ - lib/foreman_tasks/task_error.rb
181
153
  - lib/foreman_tasks.rb
182
- - lib/foreman-tasks.rb
183
154
  - LICENSE
184
155
  - README.md
185
- - test/test_helper.rb
186
- - test/tasks_test.rb
187
- homepage: https://github.com/iNecas/foreman-tasks
156
+ - test/helpers/foreman_tasks/tasks_helper_test.rb
157
+ - test/support/dummy_dynflow_action.rb
158
+ - test/foreman_tasks_test_helper.rb
159
+ - test/controllers/api/tasks_controller_test.rb
160
+ - test/factories/task_factory.rb
161
+ - test/unit/dynflow_console_authorizer_test.rb
162
+ - test/unit/task_test.rb
163
+ homepage: https://github.com/theforeman/foreman-tasks
188
164
  licenses: []
189
165
  post_install_message:
190
166
  rdoc_options: []
@@ -209,6 +185,11 @@ signing_key:
209
185
  specification_version: 3
210
186
  summary: Foreman plugin for showing tasks information for resoruces and users
211
187
  test_files:
212
- - test/test_helper.rb
213
- - test/tasks_test.rb
188
+ - test/helpers/foreman_tasks/tasks_helper_test.rb
189
+ - test/support/dummy_dynflow_action.rb
190
+ - test/foreman_tasks_test_helper.rb
191
+ - test/controllers/api/tasks_controller_test.rb
192
+ - test/factories/task_factory.rb
193
+ - test/unit/dynflow_console_authorizer_test.rb
194
+ - test/unit/task_test.rb
214
195
  has_rdoc:
data/test/tasks_test.rb DELETED
@@ -1,7 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TasksTest < ActiveSupport::TestCase
4
- test "truth" do
5
- assert_kind_of Module, Tasks
6
- end
7
- end
data/test/test_helper.rb DELETED
@@ -1,15 +0,0 @@
1
- # Configure Rails Environment
2
- ENV["RAILS_ENV"] = "test"
3
-
4
- require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
- require "rails/test_help"
6
-
7
- Rails.backtrace_cleaner.remove_silencers!
8
-
9
- # Load support files
10
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
-
12
- # Load fixtures from the engine
13
- if ActiveSupport::TestCase.method_defined?(:fixture_path=)
14
- ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
15
- end