diff options
39 files changed, 769 insertions, 103 deletions
| diff --git a/DEVNOTES.md b/DEVNOTES.md new file mode 100644 index 000000000..01f58fa0f --- /dev/null +++ b/DEVNOTES.md @@ -0,0 +1,62 @@ + +# Authorization Logic in Policies + +## Base Rules + +### ApplicationPolicy + +Policies inheriting from the `ApplicationPolicy` authorize _Undestructive_ _Permissions_ whiche are `index?` and +`show?`. And forbid _Destructive_ _Permissions_ which are `create?`, `destroy?` & `update`. + +These _CRUD_ permissions are tied to to _Action_ permissions, `delete?`→ `destroy?`, `edit?` → `update? and `new?`→ `create?`. + +These three _Action_ permissions are not supposed to be overriden in `ApplicationPolicy` subclasses. + + +### Common Policy Types + +There are two common policy types. + +#### Read Only Type Policy + +This corresponds to inheriting from  `ApplicationPolicy` without overriding one of the five aforementioned _CRUD_ permissions. + +The following Policies are of this type. + +  - `Company` +  - `GroupOfLine` +  - `Line` + custom +  - `Network` +  - `StopArea` + +#### Standard Type Policy + +The standard type policy inherits from `ApplicationPolicy` does not override any _Undesructive_ _Pemission_ but overrides the _Destructive_ ones. + +Normally, but not always they are overriden as follows + +```ruby +      def <destructive>? +        !archived? && organisation_match? && user.has_permission('<resource in plural form>.<action>') +      end +``` + +There are some variations (**TO BE CLARIFIED**) concerning `organisation_match?`. + +The following Policies are of this type. + +  - `AccessLink` +  - `AccessPoint` +  - `Calendar` (*) +  - `ConnectionLink` +  - `JourneyPattern` +  - `Referential` + custom +  - `Route` +  - `RoutingConstraintZone` +  - `TimeTable` + custom + +`Calendar` is a strange exception where no user permission is checked for the _destructive_ _permissions_. + + + + diff --git a/app/controllers/journey_patterns_collections_controller.rb b/app/controllers/journey_patterns_collections_controller.rb index 7b97e1408..837ac65e7 100644 --- a/app/controllers/journey_patterns_collections_controller.rb +++ b/app/controllers/journey_patterns_collections_controller.rb @@ -49,11 +49,10 @@ class JourneyPatternsCollectionsController < ChouetteController    end    def user_permissions -    @perms = {}.tap do |perm| -      ['journey_patterns.create', 'journey_patterns.edit', 'journey_patterns.destroy'].each do |name| -        perm[name] = policy(:journey_pattern).send("#{name.split('.').last}?") -      end -    end.to_json +    @perms = +      %w{create destroy edit}.inject({}) do | permissions, action | +        permissions.merge( "journey_patterns.#{action}" => policy.authorizes_action?(action) ) +      end.to_json    end    def update diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb index 6d2639981..0e0cade56 100644 --- a/app/controllers/time_tables_controller.rb +++ b/app/controllers/time_tables_controller.rb @@ -17,7 +17,6 @@ class TimeTablesController < ChouetteController        @time_table = @time_table.decorate(context: {          referential: @referential        }) -        build_breadcrumb :show      end    end diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index fe2e2137f..f7e2fcdc1 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -159,11 +159,10 @@ class VehicleJourneysController < ChouetteController    end    def user_permissions -    @perms = {}.tap do |perm| -      ['vehicle_journeys.create', 'vehicle_journeys.edit', 'vehicle_journeys.destroy'].each do |name| -        perm[name] = policy(:vehicle_journey).send("#{name.split('.').last}?") -      end -    end.to_json +    @perms = +      %w{create destroy update}.inject({}) do | permissions, action | +        permissions.merge( "vehicle_journeys.#{action}" => policy.authorizes_action?(action) ) +      end.to_json    end    private diff --git a/app/models/user.rb b/app/models/user.rb index 9cd4f64d6..1d9e435d5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,10 +28,21 @@ class User < ActiveRecord::Base    end    after_destroy :check_destroy_organisation -  @@edit_offer_permissions = ['routes.create', 'routes.edit', 'routes.destroy', 'journey_patterns.create', 'journey_patterns.edit', 'journey_patterns.destroy', -    'vehicle_journeys.create', 'vehicle_journeys.edit', 'vehicle_journeys.destroy', 'time_tables.create', 'time_tables.edit', 'time_tables.destroy', -    'footnotes.edit', 'footnotes.create', 'footnotes.destroy', 'routing_constraint_zones.create', 'routing_constraint_zones.edit', -    'routing_constraint_zones.destroy', 'referentials.create', 'referentials.edit', 'referentials.destroy', 'boiv:edit-offer'] +  def self.destructive_permissions_for(models) +    models.product( %w{create destroy update} ).map{ |model_action| model_action.join('.') } +  end + +  @@edit_offer_permissions = +    destructive_permissions_for( %w[ +      footnotes +      journey_patterns +      referentials +      routes +      routing_constraint_zones +      time_tables +      vehicle_journeys +    ]) << 'boiv:edit-offer' +    mattr_reader :edit_offer_permissions    def self.all_permissions diff --git a/app/policies/acces_point_policy.rb b/app/policies/acces_point_policy.rb deleted file mode 100644 index ce3a8a1ef..000000000 --- a/app/policies/acces_point_policy.rb +++ /dev/null @@ -1,25 +0,0 @@ -class AccessPointPolicy < ApplicationPolicy -  class Scope < Scope -    def resolve -      scope -    end -  end - -  def create? -    !archived? && -      organisation_match? && -      user.has_permission?('access_points.create') -  end - -  def update? -    !archived? && -      organisation_match? && -      user.has_permission?('access_points.edit') -  end - -  def destroy? -    !archived? && -      organisation_match? -    && user.has_permission?('access_points.destroy') -  end -end diff --git a/app/policies/access_link_policy.rb b/app/policies/access_link_policy.rb index a4f0e40e8..1f1147f60 100644 --- a/app/policies/access_link_policy.rb +++ b/app/policies/access_link_policy.rb @@ -6,11 +6,11 @@ class AccessLinkPolicy < ApplicationPolicy    end    def create? -    !archived? && oragnisation_mathc? && user.has_permission?('access_links.create') +    !archived? && organisation_match? && user.has_permission?('access_links.create')    end    def update? -    !archived? && organisation_match? && user.has_permission?('access_links.edit') +    !archived? && organisation_match? && user.has_permission?('access_links.update')    end    def destroy? diff --git a/app/policies/access_point_policy.rb b/app/policies/access_point_policy.rb index a1b57a3e5..41436e77c 100644 --- a/app/policies/access_point_policy.rb +++ b/app/policies/access_point_policy.rb @@ -10,7 +10,7 @@ class AccessPointPolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('access_points.edit') +    !archived? && organisation_match? && user.has_permission?('access_points.update')    end    def destroy? diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index d5c1039fd..dbe4542e7 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -82,13 +82,12 @@ class ApplicationPolicy    end    def organisation_match? -    user.organisation == organisation +    user.organisation_id == organisation_id    end -  def organisation +  def organisation_id      # When sending permission to react UI, we don't have access to record object for edit & destroy.. actions -    organisation = record.is_a?(Symbol) ? nil : record.try(:organisation) -    organisation or referential.try :organisation +    referential.try(:organisation_id) || record.try(:organisation_id)    end diff --git a/app/policies/connection_link_policy.rb b/app/policies/connection_link_policy.rb index acadc807d..240c2a804 100644 --- a/app/policies/connection_link_policy.rb +++ b/app/policies/connection_link_policy.rb @@ -14,6 +14,6 @@ class ConnectionLinkPolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('connection_links.edit') +    !archived? && organisation_match? && user.has_permission?('connection_links.update')    end  end diff --git a/app/policies/journey_pattern_policy.rb b/app/policies/journey_pattern_policy.rb index 810ead170..507a364b6 100644 --- a/app/policies/journey_pattern_policy.rb +++ b/app/policies/journey_pattern_policy.rb @@ -15,7 +15,7 @@ class JourneyPatternPolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('journey_patterns.edit') +    !archived? && organisation_match? && user.has_permission?('journey_patterns.update')    end  end diff --git a/app/policies/line_policy.rb b/app/policies/line_policy.rb index 674ed9b8d..b84c7a69f 100644 --- a/app/policies/line_policy.rb +++ b/app/policies/line_policy.rb @@ -11,7 +11,7 @@ class LinePolicy < ApplicationPolicy    end    def edit_footnote? -    !archived? && user.has_permission?('footnotes.edit') +    !archived? && user.has_permission?('footnotes.update')    end    def destroy_footnote? diff --git a/app/policies/referential_policy.rb b/app/policies/referential_policy.rb index 7f8c9e939..bf970c2b8 100644 --- a/app/policies/referential_policy.rb +++ b/app/policies/referential_policy.rb @@ -14,7 +14,7 @@ class ReferentialPolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('referentials.edit') +    !archived? && organisation_match? && user.has_permission?('referentials.update')    end @@ -24,11 +24,11 @@ class ReferentialPolicy < ApplicationPolicy    end    def archive? -    !archived? && update? +    record.archived_at.nil? && user.has_permission?('referentials.update')    end    def unarchive? -    archived? && update? +    !record.archived_at.nil? && user.has_permission?('referentials.update')    end    def common_lines? diff --git a/app/policies/route_policy.rb b/app/policies/route_policy.rb index a33551737..be3ffce4d 100644 --- a/app/policies/route_policy.rb +++ b/app/policies/route_policy.rb @@ -14,6 +14,6 @@ class RoutePolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('routes.edit') +    !archived? && organisation_match? && user.has_permission?('routes.update')    end  end diff --git a/app/policies/routing_constraint_zone_policy.rb b/app/policies/routing_constraint_zone_policy.rb index 3f2ad99a9..3cfcf46ff 100644 --- a/app/policies/routing_constraint_zone_policy.rb +++ b/app/policies/routing_constraint_zone_policy.rb @@ -14,6 +14,6 @@ class RoutingConstraintZonePolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('routing_constraint_zones.edit') +    !archived? && organisation_match? && user.has_permission?('routing_constraint_zones.update')    end  end diff --git a/app/policies/time_table_policy.rb b/app/policies/time_table_policy.rb index acdc2d13c..c9f3a3ec6 100644 --- a/app/policies/time_table_policy.rb +++ b/app/policies/time_table_policy.rb @@ -15,7 +15,7 @@ class TimeTablePolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('time_tables.edit') +    !archived? && organisation_match? && user.has_permission?('time_tables.update')    end    def actualize? diff --git a/app/policies/vehicle_journey_policy.rb b/app/policies/vehicle_journey_policy.rb index 27d96e43b..24040455f 100644 --- a/app/policies/vehicle_journey_policy.rb +++ b/app/policies/vehicle_journey_policy.rb @@ -14,6 +14,6 @@ class VehicleJourneyPolicy < ApplicationPolicy    end    def update? -    !archived? && organisation_match? && user.has_permission?('vehicle_journeys.edit') +    !archived? && organisation_match? && user.has_permission?('vehicle_journeys.update')    end  end diff --git a/app/views/time_tables/show.html.slim b/app/views/time_tables/show.html.slim index f596fd480..36b79cc25 100644 --- a/app/views/time_tables/show.html.slim +++ b/app/views/time_tables/show.html.slim @@ -1,6 +1,7 @@  - require 'calendar_helper'  / PageHeader +  = pageheader 'map-marker',               @time_table.comment,               '', diff --git a/spec/controllers/journey_patterns_collections_controller_spec.rb b/spec/controllers/journey_patterns_collections_controller_spec.rb index 888281036..442d73fb7 100644 --- a/spec/controllers/journey_patterns_collections_controller_spec.rb +++ b/spec/controllers/journey_patterns_collections_controller_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' -  RSpec.describe JourneyPatternsCollectionsController, :type => :controller do  end diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb index 18067dec7..000b799db 100644 --- a/spec/controllers/routes_controller_spec.rb +++ b/spec/controllers/routes_controller_spec.rb @@ -1,6 +1,4 @@ -require 'spec_helper' - -describe RoutesController, :type => :controller do +RSpec.describe RoutesController, :type => :controller do    login_user    let!(:route) { create(:route) } diff --git a/spec/features/time_tables_spec.rb b/spec/features/time_tables_spec.rb index 58a1dc98f..0fb4bb30d 100644 --- a/spec/features/time_tables_spec.rb +++ b/spec/features/time_tables_spec.rb @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -require 'spec_helper' - -describe "TimeTables", :type => :feature do +RSpec.describe "TimeTables", :type => :feature do    login_user    let!(:time_tables) { Array.new(2) { create(:time_table) } } diff --git a/spec/features/vehicle_journeys_spec.rb b/spec/features/vehicle_journeys_spec.rb index 5a3a9ad7d..16a79e2c5 100644 --- a/spec/features/vehicle_journeys_spec.rb +++ b/spec/features/vehicle_journeys_spec.rb @@ -43,7 +43,7 @@ describe 'VehicleJourneys', type: :feature do        context 'user does not have permission to edit vehicle journeys' do          it 'does not show an edit link for vehicle journeys' do -          @user.tap { |u| u.permissions.delete('vehicle_journeys.edit') }.save +          @user.tap { |u| u.permissions.delete('vehicle_journeys.update') }.save            visit referential_line_route_vehicle_journey_path(referential, line, route, vehicle_journey)            expect(page).not_to have_content(I18n.t('vehicle_journeys.actions.edit'))          end diff --git a/spec/helpers/table_builder_helper_spec.rb b/spec/helpers/table_builder_helper_spec.rb index 4afd0774c..c2c287b99 100644 --- a/spec/helpers/table_builder_helper_spec.rb +++ b/spec/helpers/table_builder_helper_spec.rb @@ -16,7 +16,7 @@ describe TableBuilderHelper, type: :helper do            organisation: referential.organisation,            permissions: [              'referentials.create', -            'referentials.edit', +            'referentials.update',              'referentials.destroy',            ]          ), diff --git a/spec/policies/access_link_policy_spec.rb b/spec/policies/access_link_policy_spec.rb new file mode 100644 index 000000000..6194ae55c --- /dev/null +++ b/spec/policies/access_link_policy_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe AccessLinkPolicy, type: :policy do + +  let( :record ){ build_stubbed :access_link } + +  permissions :create? do +      it_behaves_like 'permitted policy and same organisation', "access_links.create", archived: true +  end +  permissions :destroy? do +      it_behaves_like 'permitted policy and same organisation', "access_links.destroy", archived: true +  end +  permissions :edit? do +      it_behaves_like 'permitted policy and same organisation', "access_links.update", archived: true +  end +  permissions :new? do +      it_behaves_like 'permitted policy and same organisation', "access_links.create", archived: true +  end +  permissions :update? do +      it_behaves_like 'permitted policy and same organisation', "access_links.update", archived: true +  end +end diff --git a/spec/policies/access_point_policy_spec.rb b/spec/policies/access_point_policy_spec.rb new file mode 100644 index 000000000..b6bc46eb4 --- /dev/null +++ b/spec/policies/access_point_policy_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe AccessPointPolicy, type: :policy do + +  let( :record ){ build_stubbed :access_point } + +  permissions :create? do +      it_behaves_like 'permitted policy and same organisation', "access_points.create", archived: true +  end +  permissions :destroy? do +      it_behaves_like 'permitted policy and same organisation', "access_points.destroy", archived: true +  end +  permissions :edit? do +      it_behaves_like 'permitted policy and same organisation', "access_points.update", archived: true +  end +  permissions :new? do +      it_behaves_like 'permitted policy and same organisation', "access_points.create", archived: true +  end +  permissions :update? do +      it_behaves_like 'permitted policy and same organisation', "access_points.update", archived: true +  end +end diff --git a/spec/policies/calendar_policy_spec.rb b/spec/policies/calendar_policy_spec.rb new file mode 100644 index 000000000..f4423fb82 --- /dev/null +++ b/spec/policies/calendar_policy_spec.rb @@ -0,0 +1,47 @@ +RSpec.describe CalendarPolicy, type: :policy do + +  let( :record ){ build_stubbed :calendar } + +  shared_examples 'authorizes on archived and same organisation only' do +    | permission, archived: false| +    context 'same organisation →' do +      before do +        user.organisation_id = referential.organisation_id +      end +      it "allows a user with the same organisation" do +        expect_it.to permit(user_context, record) +      end +      if archived +        it 'removes permission for archived referentials' do +          referential.archived_at = 42.seconds.ago +          expect_it.not_to permit(user_context, record) +        end +      end +    end + +    context 'different organisations →' do +      before do +        add_permissions(permission, for_user: user) +      end +      it "denies a user with a different organisation" do +        expect_it.not_to permit(user_context, record) +      end +    end +  end + +  permissions :create? do +    it_behaves_like 'authorizes on archived and same organisation only', 'calendars.create', archived: true +  end +  permissions :destroy? do +    it_behaves_like 'authorizes on archived and same organisation only', 'calendars.destroy', archived: true +  end +  permissions :edit? do +    it_behaves_like 'authorizes on archived and same organisation only', 'calendars.update', archived: true +  end +  permissions :new? do +    it_behaves_like 'authorizes on archived and same organisation only', 'calendars.create', archived: true +  end +  permissions :update? do +    it_behaves_like 'authorizes on archived and same organisation only', 'calendars.update', archived: true +  end +end diff --git a/spec/policies/company_policy_spec.rb b/spec/policies/company_policy_spec.rb new file mode 100644 index 000000000..2d249a2be --- /dev/null +++ b/spec/policies/company_policy_spec.rb @@ -0,0 +1,42 @@ +RSpec.describe CompanyPolicy, type: :policy do + +  let( :record ){ build_stubbed :company } +  before { stub_policy_scope(record) } + + +  # +  #  Non Destructive +  #  --------------- + +  context 'Non Destructive actions →' do +    permissions :index? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +    permissions :show? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +  end + + +  # +  #  Destructive +  #  ----------- + +  context 'Destructive actions →' do +    permissions :create? do +      it_behaves_like 'always forbidden', 'companies.create', archived: true +    end +    permissions :destroy? do +      it_behaves_like 'always forbidden', 'companies.destroy', archived: true +    end +    permissions :edit? do +      it_behaves_like 'always forbidden', 'companies.update', archived: true +    end +    permissions :new? do +      it_behaves_like 'always forbidden', 'companies.create', archived: true +    end +    permissions :update? do +      it_behaves_like 'always forbidden', 'companies.update', archived: true +    end +  end +end diff --git a/spec/policies/connection_link_policy_spec.rb b/spec/policies/connection_link_policy_spec.rb new file mode 100644 index 000000000..23e40abe3 --- /dev/null +++ b/spec/policies/connection_link_policy_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe ConnectionLinkPolicy, type: :policy do + +  let( :record ){ build_stubbed :connection_link } + +  permissions :create? do +      it_behaves_like 'permitted policy and same organisation', "connection_links.create", archived: true +  end +  permissions :destroy? do +      it_behaves_like 'permitted policy and same organisation', "connection_links.destroy", archived: true +  end +  permissions :edit? do +      it_behaves_like 'permitted policy and same organisation', "connection_links.update", archived: true +  end +  permissions :new? do +      it_behaves_like 'permitted policy and same organisation', "connection_links.create", archived: true +  end +  permissions :update? do +      it_behaves_like 'permitted policy and same organisation', "connection_links.update", archived: true +  end +end diff --git a/spec/policies/group_of_line_policy_spec.rb b/spec/policies/group_of_line_policy_spec.rb new file mode 100644 index 000000000..29fbb1bfb --- /dev/null +++ b/spec/policies/group_of_line_policy_spec.rb @@ -0,0 +1,42 @@ +RSpec.describe GroupOfLinePolicy, type: :policy do + +  let( :record ){ build_stubbed :group_of_line } +  before { stub_policy_scope(record) } + + +  # +  #  Non Destructive +  #  --------------- + +  context 'Non Destructive actions →' do +    permissions :index? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +    permissions :show? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +  end + + +  # +  #  Destructive +  #  ----------- + +  context 'Destructive actions →' do +    permissions :create? do +      it_behaves_like 'always forbidden', 'group_of_lines.create', archived: true +    end +    permissions :destroy? do +      it_behaves_like 'always forbidden', 'group_of_lines.destroy', archived: true +    end +    permissions :edit? do +      it_behaves_like 'always forbidden', 'group_of_lines.update', archived: true +    end +    permissions :new? do +      it_behaves_like 'always forbidden', 'group_of_lines.create', archived: true +    end +    permissions :update? do +      it_behaves_like 'always forbidden', 'group_of_lines.update', archived: true +    end +  end +end diff --git a/spec/policies/journey_pattern_policy_spec.rb b/spec/policies/journey_pattern_policy_spec.rb new file mode 100644 index 000000000..39f849277 --- /dev/null +++ b/spec/policies/journey_pattern_policy_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe JourneyPatternPolicy, type: :policy do + +  let( :record ){ build_stubbed :journey_pattern } + +  permissions :create? do +      it_behaves_like 'permitted policy and same organisation', "journey_patterns.create", archived: true +  end +  permissions :destroy? do +      it_behaves_like 'permitted policy and same organisation', "journey_patterns.destroy", archived: true +  end +  permissions :edit? do +      it_behaves_like 'permitted policy and same organisation', "journey_patterns.update", archived: true +  end +  permissions :new? do +      it_behaves_like 'permitted policy and same organisation', "journey_patterns.create", archived: true +  end +  permissions :update? do +      it_behaves_like 'permitted policy and same organisation', "journey_patterns.update", archived: true +  end +end diff --git a/spec/policies/line_policy_spec.rb b/spec/policies/line_policy_spec.rb index e720b2bc7..d9e684847 100644 --- a/spec/policies/line_policy_spec.rb +++ b/spec/policies/line_policy_spec.rb @@ -1,21 +1,163 @@  RSpec.describe LinePolicy, type: :policy do    let( :record ){ build_stubbed :line } +  before { stub_policy_scope(record) } -  %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 +  # +  #  Non Destructive +  #  --------------- + +  context 'Non Destructive actions →' do +    permissions :index? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +    permissions :show? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +  end + + +  # +  #  Destructive +  #  ----------- + +  context 'Destructive actions →' do +    permissions :create? do +      it_behaves_like 'always forbidden', 'lines.create', archived: true +    end +    permissions :destroy? do +      it_behaves_like 'always forbidden', 'lines.destroy', archived: true +    end +    permissions :edit? do +      it_behaves_like 'always forbidden', 'lines.update', archived: true +    end +    permissions :new? do +      it_behaves_like 'always forbidden', 'lines.create', archived: true +    end +    permissions :update? do +      it_behaves_like 'always forbidden', 'lines.update', archived: true +    end +  end + + +  # +  #  Custom Footnote Permissions +  #  --------------------------- + +  permissions :create_footnote? do +    context 'permission present →' do +      before do +        add_permissions('footnotes.create', for_user: user) +      end + +      it 'authorized for unarchived referentials' do +        expect_it.to permit(user_context, record) +      end + +      it 'forbidden for archived referentials' do +        referential.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        expect_it.not_to permit(user_context, record) +      end +    end +  end + +  permissions :destroy_footnote? do +    context 'permission present →' do +      before do +        add_permissions('footnotes.destroy', for_user: user) +      end + +      it 'authorized for unarchived referentials' do +        expect_it.to permit(user_context, record) +      end + +      it 'forbidden for archived referentials' do +        referential.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        expect_it.not_to permit(user_context, record) +      end +    end +  end + +  permissions :edit_footnote? do +    context 'permission present →' do +      before do +        add_permissions('footnotes.update', for_user: user) +      end + +      it 'authorized for unarchived referentials' do +        expect_it.to permit(user_context, record) +      end + +      it 'forbidden for archived referentials' do +        referential.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        expect_it.not_to permit(user_context, record) +      end      end    end    permissions :new_footnote? do -    it_behaves_like 'permitted policy', 'footnotes.create', archived: true +    context 'permission present →' do +      before do +        add_permissions('footnotes.create', for_user: user) +      end + +      it 'authorized for unarchived referentials' do +        expect_it.to permit(user_context, record) +      end + +      it 'forbidden for archived referentials' do +        referential.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        expect_it.not_to permit(user_context, record) +      end +    end    end    permissions :update_footnote? do -    it_behaves_like 'permitted policy', 'footnotes.edit', archived: true +    context 'permission present →' do +      before do +        add_permissions('footnotes.update', for_user: user) +      end + +      it 'authorized for unarchived referentials' do +        expect_it.to permit(user_context, record) +      end + +      it 'forbidden for archived referentials' do +        referential.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        expect_it.not_to permit(user_context, record) +      end +    end    end  end diff --git a/spec/policies/network_policy_spec.rb b/spec/policies/network_policy_spec.rb new file mode 100644 index 000000000..ae4ffa03a --- /dev/null +++ b/spec/policies/network_policy_spec.rb @@ -0,0 +1,42 @@ +RSpec.describe NetworkPolicy, type: :policy do + +  let( :record ){ build_stubbed :network } +  before { stub_policy_scope(record) } + + +  # +  #  Non Destructive +  #  --------------- + +  context 'Non Destructive actions →' do +    permissions :index? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +    permissions :show? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +  end + + +  # +  #  Destructive +  #  ----------- + +  context 'Destructive actions →' do +    permissions :create? do +      it_behaves_like 'always forbidden', 'networks.create', archived: true +    end +    permissions :destroy? do +      it_behaves_like 'always forbidden', 'networks.destroy', archived: true +    end +    permissions :edit? do +      it_behaves_like 'always forbidden', 'networks.update', archived: true +    end +    permissions :new? do +      it_behaves_like 'always forbidden', 'networks.create', archived: true +    end +    permissions :update? do +      it_behaves_like 'always forbidden', 'networks.update', archived: true +    end +  end +end diff --git a/spec/policies/referential_policy_spec.rb b/spec/policies/referential_policy_spec.rb new file mode 100644 index 000000000..d060317f9 --- /dev/null +++ b/spec/policies/referential_policy_spec.rb @@ -0,0 +1,102 @@ +RSpec.describe ReferentialPolicy, type: :policy do + +  let( :record ){ build_stubbed :referential } + + +  # +  # Collection Based Permissions differ from standard as there is no referential yet +  # -------------------------------------------------------------------------------- + +  permissions :create? do +    it 'permissions present → allowed' do +      add_permissions('referentials.create', for_user: user) +      expect_it.to permit(user_context, record) +    end +    it 'permissions absent → forbidden' do +      expect_it.not_to permit(user_context, record) +    end +  end + +  permissions :new? do +    it 'permissions present → allowed' do +      add_permissions('referentials.create', for_user: user) +      expect_it.to permit(user_context, record) +    end +    it 'permissions absent → forbidden' do +      expect_it.not_to permit(user_context, record) +    end +  end + +  # +  # Standard Destructive Action Permissions +  # --------------------------------------- + +  permissions :destroy? do +    it_behaves_like 'permitted policy and same organisation', 'referentials.destroy', archived: true +  end +  permissions :edit? do +    it_behaves_like 'permitted policy and same organisation', 'referentials.update', archived: true +  end +  permissions :update? do +    it_behaves_like 'permitted policy and same organisation', 'referentials.update', archived: true +  end + +  # +  # Custom Permissions +  # ------------------ + +  permissions :clone? do +    it_behaves_like 'permitted policy and same organisation', 'referentials.create', archived: true +  end + +  permissions :archive? do + +    context 'permission present →' do +      before do +        add_permissions('referentials.update', for_user: user) +      end + +      it 'allowed for unarchived referentials' do +        expect_it.to permit(user_context, record) +      end + +      it 'forbidden for archived referentials' do +        record.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        expect_it.not_to permit(user_context, record) +      end +    end + +  end + +  permissions :unarchive? do + +    context 'permission present →' do +      before do +        add_permissions('referentials.update', for_user: user) +      end + +      it 'forbidden for unarchived referentials' do +        expect_it.not_to permit(user_context, record) +      end + +      it 'allowed for archived referentials' do +        record.archived_at = 1.second.ago +        expect_it.to permit(user_context, record) +      end +    end + +    context 'permission absent →' do  +      it 'is forbidden' do +        record.archived_at = 1.second.ago +        expect_it.not_to permit(user_context, record) +      end +    end + +  end +end diff --git a/spec/policies/route_policy_spec.rb b/spec/policies/route_policy_spec.rb index cc949ff45..6be517048 100644 --- a/spec/policies/route_policy_spec.rb +++ b/spec/policies/route_policy_spec.rb @@ -11,7 +11,7 @@ RSpec.describe RoutePolicy, type: :policy do    end    permissions :edit? do -    it_behaves_like 'permitted policy and same organisation', 'routes.edit', archived: true +    it_behaves_like 'permitted policy and same organisation', 'routes.update', archived: true    end    permissions :new? do @@ -19,6 +19,6 @@ RSpec.describe RoutePolicy, type: :policy do    end    permissions :update? do -    it_behaves_like 'permitted policy and same organisation', 'routes.edit', archived: true +    it_behaves_like 'permitted policy and same organisation', 'routes.update', archived: true    end  end diff --git a/spec/policies/routing_constraint_zone_policy_spec.rb b/spec/policies/routing_constraint_zone_policy_spec.rb index f91313390..2ef15fa95 100644 --- a/spec/policies/routing_constraint_zone_policy_spec.rb +++ b/spec/policies/routing_constraint_zone_policy_spec.rb @@ -12,7 +12,7 @@ RSpec.describe RoutingConstraintZonePolicy, type: :policy do    end    permissions :edit? do -    it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.edit', archived: true +    it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.update', archived: true    end    permissions :new? do @@ -20,6 +20,6 @@ RSpec.describe RoutingConstraintZonePolicy, type: :policy do    end    permissions :update? do -    it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.edit', archived: true +    it_behaves_like 'permitted policy and same organisation', 'routing_constraint_zones.update', archived: true    end  end diff --git a/spec/policies/stop_area_policy_spec.rb b/spec/policies/stop_area_policy_spec.rb new file mode 100644 index 000000000..8fe59c8e3 --- /dev/null +++ b/spec/policies/stop_area_policy_spec.rb @@ -0,0 +1,42 @@ +RSpec.describe StopAreaPolicy, type: :policy do + +  let( :record ){ build_stubbed :stop_area } +  before { stub_policy_scope(record) } + + +  # +  #  Non Destructive +  #  --------------- + +  context 'Non Destructive actions →' do +    permissions :index? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +    permissions :show? do +      it_behaves_like 'always allowed', 'anything', archived: true +    end +  end + + +  # +  #  Destructive +  #  ----------- + +  context 'Destructive actions →' do +    permissions :create? do +      it_behaves_like 'always forbidden', 'stop_areas.create', archived: true +    end +    permissions :destroy? do +      it_behaves_like 'always forbidden', 'stop_areas.destroy', archived: true +    end +    permissions :edit? do +      it_behaves_like 'always forbidden', 'stop_areas.update', archived: true +    end +    permissions :new? do +      it_behaves_like 'always forbidden', 'stop_areas.create', archived: true +    end +    permissions :update? do +      it_behaves_like 'always forbidden', 'stop_areas.update', archived: true +    end +  end +end diff --git a/spec/policies/time_table_policy_spec.rb b/spec/policies/time_table_policy_spec.rb index 6c19362d2..dad3c13bc 100644 --- a/spec/policies/time_table_policy_spec.rb +++ b/spec/policies/time_table_policy_spec.rb @@ -2,17 +2,23 @@ RSpec.describe TimeTablePolicy, type: :policy do    let( :record ){ build_stubbed :time_table } +  permissions :create? do +    it_behaves_like 'permitted policy and same organisation', 'time_tables.create', archived: true +  end + +  permissions :destroy? do +    it_behaves_like 'permitted policy and same organisation', 'time_tables.destroy', archived: true +  end -  %w{create duplicate}.each do | permission | -    permissions "#{permission}?".to_sym do -      it_behaves_like 'permitted policy and same organisation', 'time_tables.create', archived: true -    end +  permissions :edit? do +    it_behaves_like 'permitted policy and same organisation', 'time_tables.update', 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 +  permissions :new? do +    it_behaves_like 'permitted policy and same organisation', 'time_tables.create', archived: true    end +  permissions :update? do +    it_behaves_like 'permitted policy and same organisation', 'time_tables.update', archived: true +  end  end diff --git a/spec/support/devise.rb b/spec/support/devise.rb index d4a279a41..28703c072 100644 --- a/spec/support/devise.rb +++ b/spec/support/devise.rb @@ -4,12 +4,12 @@ module DeviseRequestHelper    def login_user      organisation = Organisation.where(:code => "first").first_or_create(attributes_for(:organisation))      @user ||= create(:user, :organisation => organisation, -      :permissions => ['routes.create', 'routes.edit', 'routes.destroy', 'journey_patterns.create', 'journey_patterns.edit', 'journey_patterns.destroy', -        'vehicle_journeys.create', 'vehicle_journeys.edit', 'vehicle_journeys.destroy', 'time_tables.create', 'time_tables.edit', 'time_tables.destroy', -        'footnotes.edit', 'footnotes.create', 'footnotes.destroy', 'routing_constraint_zones.create', 'routing_constraint_zones.edit', 'routing_constraint_zones.destroy', -        'access_points.create', 'access_points.edit', 'access_points.destroy', 'access_links.create', 'access_links.edit', 'access_links.destroy', -        'connection_links.create', 'connection_links.edit', 'connection_links.destroy', 'route_sections.create', 'route_sections.edit', 'route_sections.destroy', -        'referentials.create', 'referentials.edit', 'referentials.destroy']) +                     :permissions => ['routes.create', 'routes.update', 'routes.destroy', 'journey_patterns.create', 'journey_patterns.update', 'journey_patterns.destroy', +                                      'vehicle_journeys.create', 'vehicle_journeys.update', 'vehicle_journeys.destroy', 'time_tables.create', 'time_tables.update', 'time_tables.destroy', +                                      'footnotes.update', 'footnotes.create', 'footnotes.destroy', 'routing_constraint_zones.create', 'routing_constraint_zones.update', 'routing_constraint_zones.destroy', +                                      'access_points.create', 'access_points.update', 'access_points.destroy', 'access_links.create', 'access_links.update', 'access_links.destroy', +                                      'connection_links.create', 'connection_links.update', 'connection_links.destroy', 'route_sections.create', 'route_sections.update', 'route_sections.destroy', +                                      'referentials.create', 'referentials.update', 'referentials.destroy'])      login_as @user, :scope => :user      # post_via_redirect user_session_path, 'user[email]' => @user.email, 'user[password]' => @user.password    end @@ -36,25 +36,42 @@ module DeviseRequestHelper  end  module DeviseControllerHelper +    def setup_user +    _all_actions = %w{create destroy update} +    _all_resources = %w{ access_links +            access_points +            connection_links +            footnotes +            journey_patterns +            referentials +            route_sections +            routes +            routing_constraint_zones +            time_tables +            vehicle_journeys } +    join_with =  -> (separator) do  +      -> (ary) { ary.join(separator) } +    end +      before do        @request.env["devise.mapping"] = Devise.mappings[:user]        organisation = Organisation.where(:code => "first").first_or_create(attributes_for(:organisation)) -      @user = create(:user, :organisation => organisation, -        :permissions => ['routes.create', 'routes.edit', 'routes.destroy', 'journey_patterns.create', 'journey_patterns.edit', 'journey_patterns.destroy', -        'vehicle_journeys.create', 'vehicle_journeys.edit', 'vehicle_journeys.destroy', 'time_tables.create', 'time_tables.edit', 'time_tables.destroy', -        'footnotes.edit', 'footnotes.create', 'footnotes.destroy', 'routing_constraint_zones.create', 'routing_constraint_zones.edit', 'routing_constraint_zones.destroy', -        'access_points.create', 'access_points.edit', 'access_points.destroy', 'access_links.create', 'access_links.edit', 'access_links.destroy', -        'connection_links.create', 'connection_links.edit', 'connection_links.destroy', 'route_sections.create', 'route_sections.edit', 'route_sections.destroy', -        'referentials.create', 'referentials.edit', 'referentials.destroy']) +      @user = create(:user, +                     organisation: organisation, +                     permissions: _all_resources.product( _all_actions ).map(&join_with.('.')))      end    end +    def login_user()      setup_user      before do        sign_in @user      end    end + +  private +  end  RSpec.configure do |config| diff --git a/spec/support/pundit/shared_examples.rb b/spec/support/pundit/shared_examples.rb index 33ed1ffae..357004f4e 100644 --- a/spec/support/pundit/shared_examples.rb +++ b/spec/support/pundit/shared_examples.rb @@ -1,3 +1,69 @@ + +RSpec.shared_examples 'always allowed' do +  | permission, archived: false| +  context 'same organisation →' do +    before do +      user.organisation_id = referential.organisation_id +    end +    it "allows a user with the same organisation" do +      expect_it.to permit(user_context, record) +    end +    if archived +      it 'does not remove permission for archived referentials' do +        referential.archived_at = 42.seconds.ago +        expect_it.to permit(user_context, record) +      end +    end +  end + +  context 'different organisations →' 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, record) +    end +    if archived +      it 'does not remove permission for archived referentials' do +        referential.archived_at = 42.seconds.ago +        expect_it.to permit(user_context, record) +      end +    end +  end +end + +RSpec.shared_examples 'always forbidden' do +  | permission, archived: false| +  context 'same organisation →' do +    before do +      user.organisation_id = referential.organisation_id +    end +    it "allows a user with the same organisation" do +      expect_it.not_to permit(user_context, record) +    end +    if archived +      it 'still no permission for archived referentials' do +        referential.archived_at = 42.seconds.ago +        expect_it.not_to permit(user_context, record) +      end +    end +  end + +  context 'different organisations →' do +    before do +      add_permissions(permission, for_user: user) +    end +    it "denies a user with a different organisation" do +      expect_it.not_to permit(user_context, record) +    end +    if archived +      it 'still no permission for archived referentials' do +        referential.archived_at = 42.seconds.ago +        expect_it.not_to permit(user_context, record) +      end +    end +  end +end  RSpec.shared_examples 'permitted policy and same organisation' do    | permission, archived: false| @@ -6,11 +72,11 @@ RSpec.shared_examples 'permitted policy and same organisation' do        expect_it.not_to permit(user_context, record)      end      it 'and also a user with the same organisation' do -      user.organisation = referential.organisation +      user.organisation_id = referential.organisation_id        expect_it.not_to permit(user_context, record)      end    end -   +    context 'permission present → '  do      before do        add_permissions(permission, for_user: user) @@ -21,13 +87,13 @@ RSpec.shared_examples 'permitted policy and same organisation' do      end      it 'but allows it for a user with the same organisation' do -      user.organisation = referential.organisation +      user.organisation_id = referential.organisation_id        expect_it.to permit(user_context, record)      end      if archived        it 'removes the permission for archived referentials' do -        user.organisation = referential.organisation +        user.organisation_id = referential.organisation_id          referential.archived_at = 42.seconds.ago          expect_it.not_to permit(user_context, record)        end | 
