rocketjob_mission_control 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (24) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -1
  3. data/app/controllers/rocket_job_mission_control/active_workers_controller.rb +1 -0
  4. data/app/controllers/rocket_job_mission_control/application_controller.rb +15 -2
  5. data/app/controllers/rocket_job_mission_control/dirmon_entries_controller.rb +19 -2
  6. data/app/controllers/rocket_job_mission_control/jobs_controller.rb +45 -17
  7. data/app/controllers/rocket_job_mission_control/servers_controller.rb +18 -2
  8. data/app/datatables/rocket_job_mission_control/abstract_datatable.rb +0 -1
  9. data/app/datatables/rocket_job_mission_control/jobs_datatable.rb +8 -6
  10. data/app/datatables/rocket_job_mission_control/servers_datatable.rb +20 -8
  11. data/app/models/rocket_job_mission_control/access_policy.rb +50 -0
  12. data/app/models/rocket_job_mission_control/authorization.rb +22 -0
  13. data/app/views/rocket_job_mission_control/dirmon_entries/_sidebar.html.erb +5 -2
  14. data/app/views/rocket_job_mission_control/dirmon_entries/show.html.erb +8 -2
  15. data/app/views/rocket_job_mission_control/jobs/show.html.erb +9 -5
  16. data/app/views/rocket_job_mission_control/servers/index.html.erb +10 -8
  17. data/lib/rocket_job_mission_control/engine.rb +9 -0
  18. data/lib/rocket_job_mission_control/version.rb +1 -1
  19. data/test/controllers/rocket_job_mission_control/dirmon_entries_controller_test.rb +78 -1
  20. data/test/controllers/rocket_job_mission_control/jobs_controller_test.rb +52 -1
  21. data/test/controllers/rocket_job_mission_control/servers_controller_test.rb +58 -1
  22. data/test/test_helper.rb +12 -10
  23. data/vendor/assets/stylesheets/{fontawesome-all.min.css → fontawesome-all.min.css.erb} +1 -1
  24. metadata +20 -4
@@ -42,7 +42,21 @@ module RocketJobMissionControl
42
42
 
43
43
  def action_links_html(server)
44
44
  actions = '<div class="actions">'
45
- if server.stopping?
45
+ events = valid_events(server)
46
+
47
+ if events.include?(:resume) && view.can?(:resume, server)
48
+ actions += "#{ link_to "resume", resume_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Resume this server?"} }"
49
+ end
50
+
51
+ if events.include?(:pause) && view.can?(:pause, server)
52
+ actions += "#{ link_to "pause", pause_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Pause this server?"} }"
53
+ end
54
+
55
+ if events.include?(:stop) && view.can?(:stop, server)
56
+ actions += "#{ link_to "stop", stop_server_path(server), method: :patch, class: 'btn btn-danger', data: {confirm: "Stop this server?"} }"
57
+ end
58
+
59
+ if server.stopping? && view.can?(:destroy, server)
46
60
  actions += "Server is stopping..."
47
61
  confirmation = ''
48
62
  unless server.zombie?
@@ -50,16 +64,14 @@ module RocketJobMissionControl
50
64
  end
51
65
  confirmation << "Are you sure you want to destroy #{server.name} ?"
52
66
  actions += "#{ link_to "destroy", server_path(server), method: :delete, class: 'btn btn-danger', data: {confirm: confirmation} }"
53
- else
54
- if server.paused?
55
- actions += "#{ link_to "resume", resume_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Resume this server?"} }"
56
- else
57
- actions += "#{ link_to "pause", pause_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Pause this server?"} }"
58
- end
59
- actions += "#{ link_to "stop", stop_server_path(server), method: :patch, class: 'btn btn-danger', data: {confirm: "Stop this server?"} }"
60
67
  end
68
+
61
69
  actions += '</div>'
62
70
  end
63
71
 
72
+ def valid_events(server)
73
+ server.aasm.events.collect(&:name)
74
+ end
75
+
64
76
  end
65
77
  end
@@ -0,0 +1,50 @@
1
+ module RocketJobMissionControl
2
+ class AccessPolicy
3
+ include AccessGranted::Policy
4
+
5
+ def configure
6
+ # Destroy Jobs, Dirmon Entries
7
+ role :admin, {admin: true} do
8
+ can %i[create destroy], RocketJob::Job
9
+ can :destroy, RocketJob::DirmonEntry
10
+ end
11
+
12
+ # View the contents of jobs and edit the data within them.
13
+ # Including encrypted records.
14
+ role :editor, {editor: true} do
15
+ can %i[read_records update_records], RocketJob::Job
16
+ end
17
+
18
+ # Stop, Pause, Resume, Destroy (force stop) Rocket Job Servers
19
+ role :operator, {operator: true} do
20
+ can %i[stop pause resume destroy update_all], RocketJob::Server
21
+ end
22
+
23
+ # Pause, Resume, Retry, Abort, Edit Jobs
24
+ role :manager, {manager: true} do
25
+ can %i[edit pause resume retry abort fail update run_now], RocketJob::Job
26
+ end
27
+
28
+ # Create, Destroy, Enable, Disable, Edit Dirmon Entries
29
+ role :dirmon, {dirmon: true} do
30
+ can %i[create enable disable update edit], RocketJob::DirmonEntry
31
+ end
32
+
33
+ # A User can only edit their own jobs
34
+ role :user, {user: true} do
35
+ can %i[edit pause resume retry abort update], RocketJob::Job do |job, auth|
36
+ job.respond_to?(:login) && (job.login == auth.login)
37
+ end
38
+ end
39
+
40
+ # Read only access
41
+ role :view do
42
+ can :read, RocketJob::Job
43
+ can :read, RocketJob::DirmonEntry
44
+ can :read, RocketJob::Server
45
+ can :read, RocketJob::Worker
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,22 @@
1
+ module RocketJobMissionControl
2
+ class Authorization
3
+ ROLES = %i[admin editor operator manager dirmon user view]
4
+ attr_accessor *ROLES
5
+ attr_accessor :login
6
+
7
+ def initialize(roles: [], login: nil)
8
+ @login = login
9
+ return if roles.blank?
10
+ invalid_roles = roles - ROLES
11
+ raise(ArgumentError, "Invalid Roles Supplied: #{invalid_roles.inspect}") unless invalid_roles.empty?
12
+
13
+ roles.each { |r| inherit_less_privilege_roles(r) }
14
+ end
15
+
16
+ def inherit_less_privilege_roles(role)
17
+ index = ROLES.index(role)
18
+ roles = ROLES[index..-1]
19
+ roles.each { |role| public_send("#{role}=", true) }
20
+ end
21
+ end
22
+ end
@@ -1,7 +1,10 @@
1
1
  <ul class='nav hidden-xs' id='lg-menu'>
2
2
  <li>
3
- <%= link_to(new_dirmon_entry_path) do %>
4
- <i class='fas fa-plus'></i> Create
3
+
4
+ <% if can?(:create, RocketJob::DirmonEntry) %>
5
+ <%= link_to(new_dirmon_entry_path) do %>
6
+ <i class='fas fa-plus'></i> Create
7
+ <% end %>
5
8
  <% end %>
6
9
 
7
10
  <%= link_to(dirmon_entries_path) do %>
@@ -9,21 +9,27 @@
9
9
 
10
10
  <div class='btn-toolbar job-actions pull-right'>
11
11
  <div class='btn-group'>
12
- <% if @dirmon_entry.disabled? || @dirmon_entry.pending? %>
12
+ <% if @dirmon_entry.disabled? || @dirmon_entry.pending? && can?(:enable, @dirmon_entry) %>
13
13
  <%= link_to 'Enable', enable_dirmon_entry_path(@dirmon_entry.id), method: :put, class: 'btn btn-default' %>
14
14
  <% end %>
15
15
 
16
- <% if @dirmon_entry.enabled? %>
16
+ <% if @dirmon_entry.enabled? && can?(:disable, @dirmon_entry)%>
17
17
  <%= link_to 'Disable', disable_dirmon_entry_path(@dirmon_entry.id), method: :put, class: 'btn btn-default', data: { confirm: 'Are you sure?' } %>
18
18
  <% end %>
19
19
  </div>
20
20
 
21
+
21
22
  <div class='btn-group'>
23
+ <% if can?(:destroy, @dirmon_entry)%>
22
24
  <%= link_to 'Destroy', dirmon_entry_path(@dirmon_entry), method: :delete, class: 'btn btn-default', data: { confirm: 'Are you sure?' } %>
25
+ <% end %>
23
26
  </div>
24
27
 
28
+
25
29
  <div class='btn-group'>
30
+ <% if can?(:edit, @dirmon_entry)%>
26
31
  <%= link_to 'Edit', edit_dirmon_entry_path(@dirmon_entry), class: 'btn btn-default' %>
32
+ <% end %>
27
33
  </div>
28
34
  </div>
29
35
 
@@ -31,34 +31,38 @@
31
31
  <div class='btn-group'>
32
32
  <% valid_events = @job.aasm.events.collect { |e| e.name } %>
33
33
 
34
- <% if valid_events.include?(:pause) %>
34
+ <% if valid_events.include?(:pause) && can?(:pause, @job)%>
35
35
  <%= job_action_link('Pause', rocket_job_mission_control.pause_job_path(@job), :patch) %>
36
36
  <% end %>
37
37
 
38
- <% if valid_events.include?(:resume) %>
38
+ <% if valid_events.include?(:resume) && can?(:resume, @job)%>
39
39
  <%= job_action_link('Resume', rocket_job_mission_control.resume_job_path(@job), :patch) %>
40
40
  <% end %>
41
41
 
42
- <% if valid_events.include?(:retry) %>
42
+ <% if valid_events.include?(:retry) && can?(:retry, @job)%>
43
43
  <%= job_action_link('Retry', rocket_job_mission_control.retry_job_path(@job), :patch) %>
44
44
  <% end %>
45
45
  </div>
46
46
 
47
47
  <div class='btn-group'>
48
- <% if valid_events.include?(:fail) %>
48
+ <% if valid_events.include?(:fail) && can?(:fail, @job)%>
49
49
  <%= job_action_link('Fail', rocket_job_mission_control.fail_job_path(@job), :patch) %>
50
50
  <% end %>
51
51
 
52
- <% if valid_events.include?(:abort) %>
52
+ <% if valid_events.include?(:abort) && can?(:abort, @job) %>
53
53
  <%= job_action_link('Abort', rocket_job_mission_control.abort_job_path(@job), :patch) %>
54
54
  <% end %>
55
55
 
56
+ <% if can?(:destroy, @job)%>
56
57
  <%= job_action_link('Destroy', rocket_job_mission_control.job_path(@job), :delete) %>
58
+ <% end %>
57
59
  </div>
58
60
 
59
61
  <% unless @job.completed? || @job.aborted? %>
60
62
  <div class='btn-group'>
63
+ <% if can?(:edit, @job)%>
61
64
  <%= link_to 'Edit', edit_job_path(@job), class: 'btn btn-default' %>
65
+ <% end %>
62
66
  </div>
63
67
  <% end %>
64
68
  </div>
@@ -11,14 +11,16 @@
11
11
  <div class='server-collection-actions'>
12
12
  <ol>
13
13
  <div class='btn-group'>
14
- <% Array(@actions).each do |action| %>
15
- <%= link_to(
16
- "#{action.to_s.humanize.capitalize}",
17
- rocket_job_mission_control.update_all_servers_path(server_action: action),
18
- method: :patch,
19
- data: { confirm: t(:confirm, scope: [:server, :update_all], action: action.to_s.singularize.humanize.downcase) },
20
- class: 'btn btn-default'
21
- )%>
14
+ <% if can?(:create, RocketJob::DirmonEntry) %>
15
+ <% Array(@actions).each do |action| %>
16
+ <%= link_to(
17
+ "#{action.to_s.humanize.capitalize}",
18
+ rocket_job_mission_control.update_all_servers_path(server_action: action),
19
+ method: :patch,
20
+ data: {confirm: t(:confirm, scope: [:server, :update_all], action: action.to_s.singularize.humanize.downcase)},
21
+ class: 'btn btn-default'
22
+ ) %>
23
+ <% end %>
22
24
  <% end %>
23
25
  </div>
24
26
  </ol>
@@ -1,4 +1,10 @@
1
1
  module RocketJobMissionControl
2
+
3
+ # The authorization callback
4
+ module Config
5
+ mattr_accessor :authorization_callback
6
+ end
7
+
2
8
  class Engine < ::Rails::Engine
3
9
  isolate_namespace RocketJobMissionControl
4
10
 
@@ -7,11 +13,14 @@ module RocketJobMissionControl
7
13
  require 'bootstrap-sass'
8
14
  require 'sass-rails'
9
15
  require 'jquery-datatables-rails'
16
+ require 'access-granted'
10
17
  begin
11
18
  require 'rocketjob_pro'
12
19
  rescue LoadError
13
20
  end
14
21
 
22
+ config.rocket_job_mission_control = ::RocketJobMissionControl::Config
23
+
15
24
  config.to_prepare do
16
25
  Rails.application.config.assets.precompile += %w(
17
26
  rocket_job_mission_control/rocket-icon-64x64.png
@@ -1,3 +1,3 @@
1
1
  module RocketJobMissionControl
2
- VERSION = '3.1.0'
2
+ VERSION = '3.2.0'
3
3
  end
@@ -5,6 +5,7 @@ module RocketJobMissionControl
5
5
  class DirmonEntriesControllerTest < ActionController::TestCase
6
6
  describe DirmonEntriesController do
7
7
  before do
8
+ set_role(:admin)
8
9
  RocketJob::DirmonEntry.delete_all
9
10
  end
10
11
 
@@ -188,7 +189,11 @@ module RocketJobMissionControl
188
189
  end
189
190
 
190
191
  it 'alerts the user' do
191
- assert_select 'div.message', "job_class_name: [\"job_class_name must be defined and must be derived from RocketJob::Job\"]"
192
+ if RocketJob::VERSION.to_f >= 3.5
193
+ assert_select 'div.message', "job_class_name: [\"Job FakeAndBadJob must be defined and inherit from RocketJob::Job\"]"
194
+ else
195
+ assert_select 'div.message', "job_class_name: [\"job_class_name must be defined and must be derived from RocketJob::Job\"]"
196
+ end
192
197
  end
193
198
  end
194
199
  end
@@ -436,6 +441,78 @@ module RocketJobMissionControl
436
441
  end
437
442
  end
438
443
  end
444
+
445
+ describe 'role base authentication control' do
446
+ let(:dirmon_params) do
447
+ {
448
+ name: 'Test',
449
+ pattern: '/files/*',
450
+ job_class_name: job_class_name,
451
+ properties: {description: '', priority: '42'}
452
+ }
453
+ end
454
+
455
+ %i[index disabled enabled failed pending].each do |method|
456
+ it "#{method} has read only default access" do
457
+ get method, format: :json
458
+ assert_response :success
459
+ end
460
+ end
461
+
462
+ # PATCH
463
+ %i[enable disable update].each do |method|
464
+ describe method.to_s do
465
+ %i[admin editor operator manager dirmon].each do |role|
466
+ it "allows role #{role} to access #{method}" do
467
+ set_role(role)
468
+ params = {id: existing_dirmon_entry.id}
469
+ params = {params: params} if Rails.version.to_i >= 5
470
+ patch :enable, params
471
+
472
+ assert_response(:redirect)
473
+ end
474
+ end
475
+ end
476
+ end
477
+
478
+ # POST
479
+ %i[admin editor operator manager dirmon].each do |role|
480
+ it 'creates dirmon entry' do
481
+ set_role(role)
482
+ params = {rocket_job_dirmon_entry: dirmon_params}
483
+ params = {params: params} if Rails.version.to_i >= 5
484
+ post :create, params
485
+
486
+ assert_response(:redirect)
487
+ end
488
+ end
489
+
490
+ it 'deletes dirmon entry' do
491
+ set_role(:admin)
492
+ params = {id: existing_dirmon_entry.id}
493
+ params = {params: params} if Rails.version.to_i >= 5
494
+ delete :destroy, params
495
+
496
+ assert_response(:redirect)
497
+ end
498
+
499
+ %i[editor operator manager dirmon].each do |role|
500
+ it "raises authentication error for #{role}" do
501
+ set_role(role)
502
+ assert_raises AccessGranted::AccessDenied do
503
+ params = {id: existing_dirmon_entry.id}
504
+ params = {params: params} if Rails.version.to_i >= 5
505
+ delete :destroy, params
506
+ end
507
+ end
508
+ end
509
+ end
510
+ end
511
+
512
+ def set_role(r)
513
+ Config.authorization_callback = -> do
514
+ {roles: [r]}
515
+ end
439
516
  end
440
517
  end
441
518
  end
@@ -14,6 +14,7 @@ module RocketJobMissionControl
14
14
 
15
15
  describe JobsController do
16
16
  before do
17
+ set_role(:admin)
17
18
  RocketJob::Job.delete_all
18
19
  end
19
20
 
@@ -52,7 +53,7 @@ module RocketJobMissionControl
52
53
 
53
54
  let :one_job_for_every_state do
54
55
  job_states.collect do |state|
55
- RocketJob::Jobs::SimpleJob.create!(state: state, worker_name: 'worker')
56
+ RocketJob::Jobs::SimpleJob.create!(state: state, worker_name: 'worker', started_at: (Time.now - 0.5))
56
57
  end
57
58
  end
58
59
 
@@ -315,6 +316,7 @@ module RocketJobMissionControl
315
316
 
316
317
  describe "with #{state} server" do
317
318
  before do
319
+ set_role(:admin)
318
320
  one_job_for_every_state
319
321
  get state, format: :json
320
322
  end
@@ -393,6 +395,55 @@ module RocketJobMissionControl
393
395
  end
394
396
  end
395
397
 
398
+ describe 'role base authentication control' do
399
+ %i[index aborted completed failed paused queued running scheduled].each do |method|
400
+ it "#{method} has read access as default" do
401
+ get method, format: :json
402
+ assert_response :success
403
+ end
404
+ end
405
+
406
+ %i[update abort retry pause resume run_now fail].each do |method|
407
+ describe "#{method}" do
408
+ before do
409
+ case method
410
+ when :pause, :fail, :abort
411
+ pausable_job.start!
412
+ when :resume
413
+ pausable_job.pause!
414
+ when :retry, :exception
415
+ pausable_job.fail!
416
+ end
417
+
418
+ @params = {id: pausable_job.id, job: {id: pausable_job.id, priority: pausable_job.priority}}
419
+ @params = {params: @params} if Rails.version.to_i >= 5
420
+ end
421
+
422
+ %i[admin editor operator manager].each do |role|
423
+ it "redirects with #{method} method and role #{role}" do
424
+ set_role(role)
425
+ patch method, @params
426
+ assert_response(:redirect)
427
+ end
428
+ end
429
+
430
+ %i[dirmon user].each do |role|
431
+ it "raises authentication error for #{role}" do
432
+ set_role(role)
433
+ assert_raises AccessGranted::AccessDenied do
434
+ patch method, @params
435
+ end
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end
442
+
443
+ def set_role(r)
444
+ Config.authorization_callback = -> do
445
+ {roles: [r]}
446
+ end
396
447
  end
397
448
  end
398
449
  end