foreman-tasks 0.6.10 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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