diff options
| -rw-r--r-- | app/policies/application_policy.rb | 5 | ||||
| -rw-r--r-- | app/policies/boiv_policy.rb | 2 | ||||
| -rw-r--r-- | app/policies/chain.rb | 57 | ||||
| -rw-r--r-- | app/policies/line_policy.rb | 7 | ||||
| -rw-r--r-- | app/policies/route_policy.rb | 3 | ||||
| -rw-r--r-- | app/policies/routing_constraint_zone_policy.rb | 3 | ||||
| -rw-r--r-- | app/policies/time_table_policy.rb | 12 | ||||
| -rw-r--r-- | app/views/referential_lines/_filters.html.slim | 2 | ||||
| -rw-r--r-- | spec/features/line_footnotes_spec.rb | 3 | ||||
| -rw-r--r-- | spec/policies/boiv_policy_spec.rb | 7 | ||||
| -rw-r--r-- | spec/policies/line_policy_spec.rb | 18 | ||||
| -rw-r--r-- | spec/policies/route_policy_spec.rb | 22 | ||||
| -rw-r--r-- | spec/policies/routing_constraint_zone_policy_spec.rb | 22 | ||||
| -rw-r--r-- | spec/policies/time_table_policy_spec.rb | 13 | ||||
| -rw-r--r-- | spec/support/pundit/shared_examples.rb | 45 |
15 files changed, 199 insertions, 22 deletions
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index de8a23344..a863404ae 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -7,7 +7,10 @@ class ApplicationPolicy @record = record end - attr_accessor :referential + def archived? + !!referential.try(:archived_at) + end + def referential @referential ||= record_referential end diff --git a/app/policies/boiv_policy.rb b/app/policies/boiv_policy.rb index e29a2e6de..4270dc686 100644 --- a/app/policies/boiv_policy.rb +++ b/app/policies/boiv_policy.rb @@ -1,3 +1,4 @@ +require_relative 'chain' class BoivPolicy < ApplicationPolicy def boiv_read_offer? @@ -11,5 +12,4 @@ class BoivPolicy < ApplicationPolicy def show? boiv_read_offer? end - end diff --git a/app/policies/chain.rb b/app/policies/chain.rb new file mode 100644 index 000000000..4bf96bd84 --- /dev/null +++ b/app/policies/chain.rb @@ -0,0 +1,57 @@ +module Policies + # Implements the `chain_policies` macro as follows + # + # chain_policies <method_chain>, policies: + # + # e.g. + # + # chain_policies [:archived?, :!], policies: %i{create? edit?} + # + # which would establish a precondition `not archived` for the `create?` and `edit?` + # method, it is semantically identical to instrumenting both methods + # as follows: + # + # def create? # or edit? + # archived?.! && <original code of method> + # end + module Chain + + # A local chain store implemented to avoid any possible side effect on client policies. + defined_chains = {} + + # Using `define_method` in order to close over `defined_chains` + # We need to store the chains because the methods they will apply to + # are not defined yet. + define_method :chain_policies do |*conditions, policies:| + policies.each do | meth_name | + # self represents the client Policy + defined_chains[[self, meth_name]] = conditions + end + end + # Intercept method definition and check if a policy_chain has been registered for it‥. + define_method :method_added do |meth_name, *args, &blk| + # Delete potentially registered criteria conditions to‥. + # (i) protect against endless recursion via (:merthod_added → :define_method → :method_added → ‥. + # (ii) get the condition + conditions = defined_chains.delete([self, meth_name]) + return unless conditions + + instrument_method(meth_name, conditions) + end + + private + + # Access to the closure is not necessary anymore, normal metaprogramming can take place :) + def instrument_method(meth_name, conditions) + orig_method = instance_method(meth_name) + # In case of warnings remove original method here, depends on Ruby Version, ok in 2.3.1 + define_method meth_name do |*a, &b| + # Method chain describing the chained policy precondition. + conditions.inject(self) do | result, msg | + result.send msg + end && + orig_method.bind(self).(*a, &b) + end + end + end +end diff --git a/app/policies/line_policy.rb b/app/policies/line_policy.rb index 443e3e22f..c3e0051c8 100644 --- a/app/policies/line_policy.rb +++ b/app/policies/line_policy.rb @@ -1,4 +1,9 @@ +require_relative 'chain' class LinePolicy < BoivPolicy + extend Policies::Chain + + chain_policies :archived?, :!, policies: %i{create_footnote? destroy_footnote? edit_footnote?} + class Scope < Scope def resolve scope @@ -22,7 +27,7 @@ class LinePolicy < BoivPolicy end def destroy_footnote? - user.has_permission?('routes.destroy') + user.has_permission?('footnotes.destroy') end def update_footnote? ; edit_footnote? end diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb index ff13d3163..dba3a27da 100644 --- a/app/policies/route_policy.rb +++ b/app/policies/route_policy.rb @@ -1,10 +1,13 @@ class RoutePolicy < BoivPolicy + extend Policies::Chain class Scope < Scope def resolve scope end end + chain_policies :archived?, :!, policies: %i{create? destroy? edit?} + def create? user.has_permission?('routes.create') # organisation match via referential is checked in the view end diff --git a/app/policies/routing_constraint_zone_policy.rb b/app/policies/routing_constraint_zone_policy.rb index 58cbaa9e1..abba5639c 100644 --- a/app/policies/routing_constraint_zone_policy.rb +++ b/app/policies/routing_constraint_zone_policy.rb @@ -1,10 +1,13 @@ class RoutingConstraintZonePolicy < BoivPolicy + extend Policies::Chain class Scope < Scope def resolve scope end end + chain_policies :archived?, :!, policies: %i{create? destroy? edit?} + def create? user.has_permission?('routing_constraint_zones.create') # organisation match via referential is checked in the view end diff --git a/app/policies/time_table_policy.rb b/app/policies/time_table_policy.rb index 1bb2add15..efab6ac00 100644 --- a/app/policies/time_table_policy.rb +++ b/app/policies/time_table_policy.rb @@ -1,24 +1,28 @@ class TimeTablePolicy < BoivPolicy + extend Policies::Chain + class Scope < Scope def resolve scope end end + chain_policies :archived?, :!, policies: %i{create? destroy? duplicate? edit?} + def create? - user.has_permission?('time_tables.create') # organisation match via referential is checked in the view + user.has_permission?('time_tables.create') # organisation match via referential is checked in the view end def edit? - organisation_match? && user.has_permission?('time_tables.edit') + organisation_match? && user.has_permission?('time_tables.edit') end def destroy? - organisation_match? && user.has_permission?('time_tables.destroy') + organisation_match? && user.has_permission?('time_tables.destroy') end def duplicate? - organisation_match? && create? + organisation_match? && create? end def update? ; edit? end diff --git a/app/views/referential_lines/_filters.html.slim b/app/views/referential_lines/_filters.html.slim index aa355884b..93d449507 100644 --- a/app/views/referential_lines/_filters.html.slim +++ b/app/views/referential_lines/_filters.html.slim @@ -9,7 +9,7 @@ .ffg-row .form-group.togglable = f.label Chouette::Route.human_attribute_name(:wayback), required: false, class: 'control-label' - = f.input :wayback_eq_any, as: :checkboxes, class: 'form-control', collection: Chouette::Route.wayback.values, as: :check_boxes, label: false, required: false, wrapper_html: { class: 'checkbox_list'}, label_method: lambda{|l| ("<span>" + t("enumerize.route.wayback.#{l}") + "</span>").html_safe} + = f.input :wayback_eq_any, class: 'form-control', collection: Chouette::Route.wayback.values, as: :check_boxes, label: false, required: false, wrapper_html: { class: 'checkbox_list'}, label_method: lambda{|l| ("<span>" + t("enumerize.route.wayback.#{l}") + "</span>").html_safe} .actions = link_to 'Effacer', referential_line_path(@referential, @line), class: 'btn btn-link' diff --git a/spec/features/line_footnotes_spec.rb b/spec/features/line_footnotes_spec.rb index 4d77cba41..6a359ad50 100644 --- a/spec/features/line_footnotes_spec.rb +++ b/spec/features/line_footnotes_spec.rb @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -require 'spec_helper' - describe 'Line Footnotes', type: :feature do login_user diff --git a/spec/policies/boiv_policy_spec.rb b/spec/policies/boiv_policy_spec.rb index 3af82ddfe..bf09cdcd9 100644 --- a/spec/policies/boiv_policy_spec.rb +++ b/spec/policies/boiv_policy_spec.rb @@ -1,16 +1,15 @@ RSpec.describe BoivPolicy, type: :policy do - permissions :index? do - it_behaves_like 'permitted and same organisation', 'boiv:read-offer' + it_behaves_like 'permitted policy and same organisation', 'boiv:read-offer' end permissions :boiv_read_offer? do - it_behaves_like 'permitted and same organisation', 'boiv:read-offer' + it_behaves_like 'permitted policy and same organisation', 'boiv:read-offer' end permissions :show? do - it_behaves_like 'permitted and same organisation', 'boiv:read-offer' + it_behaves_like 'permitted policy and same organisation', 'boiv:read-offer' end end diff --git a/spec/policies/line_policy_spec.rb b/spec/policies/line_policy_spec.rb new file mode 100644 index 000000000..ead5918aa --- /dev/null +++ b/spec/policies/line_policy_spec.rb @@ -0,0 +1,18 @@ +RSpec.describe LinePolicy, type: :policy do + + %w{create destroy edit}.each do | permission | + footnote_permission = "#{permission}_footnote" + permissions "#{footnote_permission}?".to_sym do + it_behaves_like 'permitted policy', "footnotes.#{permission}", archived: true + end + end + + permissions :new_footnote? do + it_behaves_like 'permitted policy', 'footnotes.create', archived: true + end + + permissions :update_footnote? do + it_behaves_like 'permitted policy', 'footnotes.edit', archived: true + end + +end diff --git a/spec/policies/route_policy_spec.rb b/spec/policies/route_policy_spec.rb new file mode 100644 index 000000000..baf14c9fc --- /dev/null +++ b/spec/policies/route_policy_spec.rb @@ -0,0 +1,22 @@ +RSpec.describe RoutePolicy, type: :policy do + + permissions :create? do + it_behaves_like 'permitted policy', 'routes.create', archived: true + end + + permissions :destroy? do + it_behaves_like 'permitted policy and same organisation', 'routes.destroy', archived: true + end + + permissions :edit? do + it_behaves_like 'permitted policy and same organisation', 'routes.edit', archived: true + end + + permissions :new? do + it_behaves_like 'permitted policy', 'routes.create', archived: true + end + + permissions :update? do + it_behaves_like 'permitted policy and same organisation', 'routes.edit', archived: true + end +end diff --git a/spec/policies/routing_constraint_zone_policy_spec.rb b/spec/policies/routing_constraint_zone_policy_spec.rb new file mode 100644 index 000000000..4b0f2cafe --- /dev/null +++ b/spec/policies/routing_constraint_zone_policy_spec.rb @@ -0,0 +1,22 @@ +RSpec.describe RoutingConstraintZonePolicy, type: :policy do + + permissions :create? do + it_behaves_like 'permitted policy', 'routing_constraint_zones.create', archived: true + end + + permissions :destroy? do + it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.destroy', archived: true + end + + permissions :edit? do + it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.edit', archived: true + end + + permissions :new? do + it_behaves_like 'permitted policy', 'routing_constraint_zones.create', archived: true + end + + permissions :update? do + it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.edit', archived: true + end +end diff --git a/spec/policies/time_table_policy_spec.rb b/spec/policies/time_table_policy_spec.rb index 48beea75d..1283a9fcf 100644 --- a/spec/policies/time_table_policy_spec.rb +++ b/spec/policies/time_table_policy_spec.rb @@ -1,7 +1,18 @@ RSpec.describe TimeTablePolicy, type: :policy do permissions :duplicate? do - it_behaves_like 'permitted and same organisation', 'time_tables.create' + it_behaves_like 'permitted policy and same organisation', 'time_tables.create', archived: true end + %w{destroy edit}.each do | permission | + permissions "#{permission}?".to_sym do + it_behaves_like 'permitted policy and same organisation', "time_tables.#{permission}", archived: true + end + end + + permissions :create? do + it_behaves_like 'permitted policy', 'time_tables.create', archived: true + end + + end diff --git a/spec/support/pundit/shared_examples.rb b/spec/support/pundit/shared_examples.rb index 9583ab30c..4d14c46da 100644 --- a/spec/support/pundit/shared_examples.rb +++ b/spec/support/pundit/shared_examples.rb @@ -1,27 +1,60 @@ -RSpec.shared_examples "permitted and same organisation" do |permission| +RSpec.shared_examples 'permitted policy and same organisation' do + | permission, archived: false| - context "permission absent → " do + context 'permission absent → ' do it "denies a user with a different organisation" do expect_it.not_to permit(user_context, referential) end - it "and also a user with the same organisation" do + it 'and also a user with the same organisation' do user.update_attribute :organisation, referential.organisation expect_it.not_to permit(user_context, referential) end end - context "permission present → " do + context 'permission present → ' do before do add_permissions(permission, for_user: user) end - it "denies a user with a different organisation" do + it 'denies a user with a different organisation' do expect_it.not_to permit(user_context, referential) end - it "but allows it for a user with the same organisation" do + it 'but allows it for a user with the same organisation' do user.update_attribute :organisation, referential.organisation expect_it.to permit(user_context, referential) end + + if archived + it 'removes the permission for archived referentials' do + user.update_attribute :organisation, referential.organisation + referential.update_attribute :archived_at, 42.seconds.ago + expect_it.not_to permit(user_context, referential) + end + end + end +end + +RSpec.shared_examples 'permitted policy' do + | permission, archived: false| + context 'permission absent → ' do + it "denies a user with a different organisation" do + expect_it.not_to permit(user_context, referential) + end + end + context 'permission present → ' do + before do + add_permissions(permission, for_user: user) + end + it 'allows a user with a different organisation' do + expect_it.to permit(user_context, referential) + end + + if archived + it 'removes the permission for archived referentials' do + referential.update_attribute :archived_at, 42.seconds.ago + expect_it.not_to permit(user_context, referential) + end + end end end |
