From 0e508268b9fc2043cf30c31f8410e5813cc20162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Fri, 26 Jul 2024 18:27:01 +0200 Subject: [PATCH] add preview specs --- ...ew_accountability_with_share_token_spec.rb | 4 +- .../decidim/admin/share_tokens_controller.rb | 28 +-- .../decidim/admin/share_tokens/edit.html.erb | 6 +- .../decidim/assemblies/permissions.rb | 9 + ...dmin_manages_assembly_share_tokens_spec.rb | 12 +- .../preview_assembly_with_share_token_spec.rb | 11 + .../preview_blogs_with_share_token_spec.rb | 4 +- .../preview_budgets_with_share_token_spec.rb | 4 +- .../decidim/conferences/permissions.rb | 13 +- ...in_manages_conference_share_tokens_spec.rb | 12 +- ...review_conference_with_share_token_spec.rb | 11 + .../concerns/decidim/needs_permission.rb | 3 +- .../decidim/application_controller.rb | 6 + .../decidim/components/base_controller.rb | 6 +- .../app/permissions/decidim/permissions.rb | 1 - decidim-core/lib/decidim/core/test.rb | 5 +- .../manage_component_share_tokens_examples.rb | 195 --------------- ...rticipatory_space_share_tokens_examples.rb | 28 --- .../manage_share_tokens_examples.rb | 226 ++++++++++++++++++ ...iew_component_with_share_token_examples.rb | 74 ------ .../preview_with_share_token_examples.rb | 99 ++++++++ .../preview_debates_with_share_token_spec.rb | 4 +- .../decidim/initiatives/permissions.rb | 7 + ...review_initiative_with_share_token_spec.rb | 11 + .../preview_meetings_with_share_token_spec.rb | 4 +- .../preview_pages_with_share_token_spec.rb | 4 +- .../participatory_processes/permissions.rb | 7 + ...participatory_process_share_tokens_spec.rb | 12 +- ...icipatory_process_with_share_token_spec.rb | 11 + ...preview_proposals_with_share_token_spec.rb | 4 +- ...review_sortitions_with_share_token_spec.rb | 4 +- decidim-surveys/spec/system/survey_spec.rb | 2 +- 32 files changed, 475 insertions(+), 352 deletions(-) create mode 100644 decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb create mode 100644 decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb delete mode 100644 decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens_examples.rb delete mode 100644 decidim-core/lib/decidim/core/test/shared_examples/manage_participatory_space_share_tokens_examples.rb create mode 100644 decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb delete mode 100644 decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb create mode 100644 decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb create mode 100644 decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb create mode 100644 decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb diff --git a/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb b/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb index e3701b7eace20..52e838eac9c96 100644 --- a/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb +++ b/decidim-accountability/spec/system/preview_accountability_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview accountability with share token" do +describe "preview accountability with a share token" do let(:manifest_name) { "accountability" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb b/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb index baf1ad30cb522..7b188284e48bc 100644 --- a/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb +++ b/decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb @@ -2,25 +2,25 @@ module Decidim module Admin - # This is an abstrac controller allows sharing unpublished things. + # This is an abstract controller allows sharing unpublished things. # Final implementation must inherit from this controller and implement the `resource` method. class ShareTokensController < Decidim::Admin::ApplicationController include Decidim::Admin::Filterable - helper_method :share_token, :resource, :resource_title, :share_tokens_path + helper_method :current_token, :resource, :resource_title, :share_tokens_path def index - enforce_permission_to :read, :share_token + enforce_permission_to :read, :share_tokens @share_tokens = filtered_collection end def new - enforce_permission_to :create, :share_token + enforce_permission_to :create, :share_tokens @form = form(ShareTokenForm).instance end def create - enforce_permission_to :create, :share_token + enforce_permission_to :create, :share_tokens @form = form(ShareTokenForm).from_params(params, resource:) CreateShareToken.call(@form) do @@ -37,15 +37,15 @@ def create end def edit - enforce_permission_to(:update, :share_token, share_token:) - @form = form(ShareTokenForm).from_model(share_token) + enforce_permission_to(:update, :share_tokens, share_token: current_token) + @form = form(ShareTokenForm).from_model(current_token) end def update - enforce_permission_to(:update, :share_token, share_token:) + enforce_permission_to(:update, :share_tokens, share_token: current_token) @form = form(ShareTokenForm).from_params(params, resource:) - UpdateShareToken.call(@form, share_token) do + UpdateShareToken.call(@form, current_token) do on(:ok) do flash[:notice] = I18n.t("share_tokens.update.success", scope: "decidim.admin") redirect_to share_tokens_path @@ -59,9 +59,9 @@ def update end def destroy - enforce_permission_to(:destroy, :share_token, share_token:) + enforce_permission_to(:destroy, :share_tokens, share_token: current_token) - Decidim::Commands::DestroyResource.call(share_token, current_user) do + Decidim::Commands::DestroyResource.call(current_token, current_user) do on(:ok) do flash[:notice] = I18n.t("share_tokens.destroy.success", scope: "decidim.admin") end @@ -80,7 +80,7 @@ def resource raise NotImplementedError end - # Override also this method if resouce does not respond to a translatable name or title + # Override also this method if resource does not respond to a translatable name or title def resource_title translated_attribute(resource.try(:name) || resource.title) end @@ -124,8 +124,8 @@ def filters [] end - def share_token - @share_token ||= collection.find(params[:id]) + def current_token + @current_token ||= collection.find(params[:id]) end end end diff --git a/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb b/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb index b79cbcd783aab..5047a74436d00 100644 --- a/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb +++ b/decidim-admin/app/views/decidim/admin/share_tokens/edit.html.erb @@ -6,7 +6,7 @@
- <%= decidim_form_for(@form, url: share_tokens_path(:update, id: share_token), html: { class: "form-defaults form edit_share_token" }) do |f| %> + <%= decidim_form_for(@form, url: share_tokens_path(:update, id: current_token), html: { class: "form-defaults form edit_share_token" }) do |f| %>
@@ -14,8 +14,8 @@
- <%= text_field_tag :token, share_token.token, id: "share_token-token", aria: { label: t("token", scope: "decidim.admin.models.share_token.fields") }, disabled: true %> - + <%= text_field_tag :token, current_token.token, id: "share_token-token", aria: { label: t("token", scope: "decidim.admin.models.share_token.fields") }, disabled: true %> +
<%= render partial: "form", object: f %> diff --git a/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb b/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb index 6cd2dd24093f6..15e8d771b5e68 100644 --- a/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb +++ b/decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb @@ -126,6 +126,7 @@ def public_read_assembly_action? return disallow! unless can_view_private_space? return allow! if user&.admin? return allow! if assembly.published? + return allow! if user_can_preview_space? toggle_allow(can_manage_assembly?) end @@ -266,6 +267,7 @@ def assembly_admin_action? :assembly_user_role, :assembly_member, :export_space, + :share_tokens, :import ].include?(permission_action.subject) allow! if is_allowed @@ -285,11 +287,18 @@ def org_admin_action? :assembly_user_role, :assembly_member, :export_space, + :share_tokens, :import ].include?(permission_action.subject) allow! if is_allowed end + def user_can_preview_space? + return allow! if context[:share_token].present? && Decidim::ShareToken.use!(token_for: assembly, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + # Checks if the permission_action is to read the admin assemblies list or # not. def read_assembly_list_permission_action? diff --git a/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb b/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb index 02f46dd93b9e4..47251ea9ba4b2 100644 --- a/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb +++ b/decidim-assemblies/spec/system/admin/admin_manages_assembly_share_tokens_spec.rb @@ -6,9 +6,15 @@ let!(:user) { create(:user, :admin, :confirmed, organization:) } let(:organization) { create(:organization) } let!(:assembly) { create(:assembly, organization:, private_space: true) } + let(:participatory_space) { assembly } + let(:participatory_space_path) { decidim_admin_assemblies.edit_assembly_path(assembly) } - it_behaves_like "manage participatory space share tokens" do - let(:participatory_space) { assembly } - let(:participatory_space_path) { decidim_admin_assemblies.edit_assembly_path(assembly) } + it_behaves_like "manage participatory space share tokens" + + context "when the user is an assembly admin" do + let(:user) { create(:user, :confirmed, :admin_terms_accepted, organization:) } + let!(:role) { create(:assembly_user_role, user:, assembly:, role: :admin) } + + it_behaves_like "manage participatory space share tokens" end end diff --git a/decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb b/decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb new file mode 100644 index 0000000000000..323eb2d49378e --- /dev/null +++ b/decidim-assemblies/spec/system/preview_assembly_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview assembly with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:assembly, organization:, published_at: nil) } + let(:resource_path) { decidim_assemblies.assembly_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb b/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb index a5ff8c5cc36e5..bc4669f1817df 100644 --- a/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb +++ b/decidim-blogs/spec/system/preview_blogs_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview blogs with share token" do +describe "preview blogs with a share token" do let(:manifest_name) { "blogs" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb b/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb index e76e3bdd615b5..dab5e601e5297 100644 --- a/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb +++ b/decidim-budgets/spec/system/preview_budgets_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview budgets with share token" do +describe "preview budgets with a share token" do let(:manifest_name) { "budgets" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-conferences/app/permissions/decidim/conferences/permissions.rb b/decidim-conferences/app/permissions/decidim/conferences/permissions.rb index 5da0b9465f4c6..b077784a9c4c3 100644 --- a/decidim-conferences/app/permissions/decidim/conferences/permissions.rb +++ b/decidim-conferences/app/permissions/decidim/conferences/permissions.rb @@ -127,6 +127,7 @@ def public_read_conference_action? return allow! if user&.admin? return allow! if conference.published? + return allow! if user_can_preview_space? toggle_allow(can_manage_conference?) end @@ -276,7 +277,8 @@ def conference_admin_action? :partner, :media_link, :registration_type, - :conference_invite + :conference_invite, + :share_tokens ].include?(permission_action.subject) allow! if is_allowed end @@ -299,11 +301,18 @@ def org_admin_action? :partner, :registration_type, :read_conference_registrations, - :export_conference_registrations + :export_conference_registrations, + :share_tokens ].include?(permission_action.subject) allow! if is_allowed end + def user_can_preview_space? + return allow! if context[:share_token].present? && Decidim::ShareToken.use!(token_for: conference, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + # Checks if the permission_action is to read the admin conferences list or # not. def read_conference_list_permission_action? diff --git a/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb b/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb index f9b938c408492..d2c5967a45c26 100644 --- a/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb +++ b/decidim-conferences/spec/system/admin/admin_manages_conference_share_tokens_spec.rb @@ -4,9 +4,15 @@ describe "Admin manages conference share tokens" do include_context "when admin administrating a conference" + let(:participatory_space) { conference } + let(:participatory_space_path) { decidim_admin_conferences.edit_conference_path(conference) } - it_behaves_like "manage participatory space share tokens" do - let(:participatory_space) { conference } - let(:participatory_space_path) { decidim_admin_conferences.edit_conference_path(conference) } + it_behaves_like "manage participatory space share tokens" + + context "when the user is a conference admin" do + let(:user) { create(:user, :confirmed, :admin_terms_accepted, organization:) } + let!(:role) { create(:conference_user_role, user:, conference:, role: :admin) } + + it_behaves_like "manage participatory space share tokens" end end diff --git a/decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb b/decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb new file mode 100644 index 0000000000000..714c761d7f5aa --- /dev/null +++ b/decidim-conferences/spec/system/preview_conference_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview conference with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:conference, organization:, published_at: nil) } + let(:resource_path) { decidim_conferences.conference_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-core/app/controllers/concerns/decidim/needs_permission.rb b/decidim-core/app/controllers/concerns/decidim/needs_permission.rb index c4b448a6623a7..c12e91c0d3188 100644 --- a/decidim-core/app/controllers/concerns/decidim/needs_permission.rb +++ b/decidim-core/app/controllers/concerns/decidim/needs_permission.rb @@ -40,7 +40,8 @@ def permissions_context current_settings: try(:current_settings), component_settings: try(:component_settings), current_organization: try(:current_organization), - current_component: try(:current_component) + current_component: try(:current_component), + share_token: try(:store_share_token) } end diff --git a/decidim-core/app/controllers/decidim/application_controller.rb b/decidim-core/app/controllers/decidim/application_controller.rb index 40bd3c53423eb..e033d94d49a40 100644 --- a/decidim-core/app/controllers/decidim/application_controller.rb +++ b/decidim-core/app/controllers/decidim/application_controller.rb @@ -56,6 +56,12 @@ class ApplicationController < ::DecidimController skip_before_action :disable_http_caching, unless: :user_signed_in? + def store_share_token + session[:share_token] = params[:share_token] if params.has_key?(:share_token) + + session[:share_token].presence + end + private # This overrides Devise's method for extracting the path from the URL. We diff --git a/decidim-core/app/controllers/decidim/components/base_controller.rb b/decidim-core/app/controllers/decidim/components/base_controller.rb index 07e415c455d4e..09bc0b75c6a7a 100644 --- a/decidim-core/app/controllers/decidim/components/base_controller.rb +++ b/decidim-core/app/controllers/decidim/components/base_controller.rb @@ -30,7 +30,7 @@ class BaseController < Decidim::ApplicationController :current_manifest before_action do - enforce_permission_to :read, :component, component: current_component, share_token: + enforce_permission_to :read, :component, component: current_component end before_action :redirect_unless_feature_private @@ -49,10 +49,6 @@ def current_manifest @current_manifest ||= current_component.manifest end - def share_token - params[:share_token] - end - def permission_scope :public end diff --git a/decidim-core/app/permissions/decidim/permissions.rb b/decidim-core/app/permissions/decidim/permissions.rb index c5bdc821d841f..9542640653edf 100644 --- a/decidim-core/app/permissions/decidim/permissions.rb +++ b/decidim-core/app/permissions/decidim/permissions.rb @@ -56,7 +56,6 @@ def component_public_action? return allow! if component.published? return allow! if user_can_preview_component? - return allow! if user_can_admin_component? return allow! if user_can_admin_component_via_space? disallow! diff --git a/decidim-core/lib/decidim/core/test.rb b/decidim-core/lib/decidim/core/test.rb index 502837644c172..99560aacd2995 100644 --- a/decidim-core/lib/decidim/core/test.rb +++ b/decidim-core/lib/decidim/core/test.rb @@ -59,9 +59,8 @@ require "decidim/core/test/shared_examples/permissions" require "decidim/core/test/shared_examples/admin_resource_gallery_examples" require "decidim/core/test/shared_examples/map_examples" -require "decidim/core/test/shared_examples/preview_component_with_share_token_examples" -require "decidim/core/test/shared_examples/manage_component_share_tokens_examples" -require "decidim/core/test/shared_examples/manage_participatory_space_share_tokens_examples" +require "decidim/core/test/shared_examples/preview_with_share_token_examples" +require "decidim/core/test/shared_examples/manage_share_tokens_examples" require "decidim/core/test/shared_examples/metric_manage_shared_context" require "decidim/core/test/shared_examples/resource_search_examples" require "decidim/core/test/shared_examples/static_pages_examples" diff --git a/decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens_examples.rb deleted file mode 100644 index 3907812949d9a..0000000000000 --- a/decidim-core/lib/decidim/core/test/shared_examples/manage_component_share_tokens_examples.rb +++ /dev/null @@ -1,195 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples "manage component share tokens" do - let!(:components_path) { participatory_space_engine.components_path(participatory_space) } - let!(:component) { create(:component, participatory_space:, published_at: nil) } - - before do - switch_to_host(organization.host) - login_as user, scope: :user - end - - def visit_share_component - visit components_path - click_on "Components" - click_on "Share" - end - - context "when visiting the share_tokens page for the component" do - let!(:share_token) { create(:share_token, token_for: component, organization:, user:, registered_only: true) } - - before do - visit components_path - end - - it "has a share button that opens the share tokens admin" do - click_on "Share" - expect(page).to have_content("Sharing tokens for: #{translated(component.name)}") - expect(page).to have_css("tbody tr", count: 1) - expect(page).to have_content(share_token.token) - end - end - - context "when visiting the share tokens index page" do - context "when there are no tokens" do - let(:last_token) { Decidim::ShareToken.last } - before do - visit_share_component - end - - it "displays empty message" do - expect(page).to have_content "There are no active tokens" - end - - it "can create a new token with default options" do - click_on "New token" - - click_on "Create" - - expect(page).to have_content("Token created successfully") - expect(page).to have_css("tbody tr", count: 1) - within "tbody tr:last-child td:first-child" do - expect(page).to have_content(last_token.token) - end - within "tbody tr:last-child td:nth-child(2)" do - expect(page).to have_content("Never") - end - within "tbody tr:last-child td:nth-child(3)" do - expect(page).to have_content("No") - end - end - - it "can create a new token with custom options" do - click_on "New token" - - find_by_id("share_token_automatic_token_false").click - find_by_id("share_token_no_expiration_false").click - find_by_id("share_token_registered_only_true").click - click_on "Create" - expect(page).to have_content("cannot be blank", count: 2) - - fill_in "share_token_token", with: " custom token " - fill_in "share_token_expires_at", with: 1.day.from_now, visible: :all - click_on "Create" - - expect(page).to have_content("Token created successfully") - expect(page).to have_css("tbody tr", count: 1) - within "tbody tr:last-child td:first-child" do - expect(page).to have_content("CUSTOM-TOKEN") - end - within "tbody tr:last-child td:nth-child(2)" do - expect(page).to have_content(1.day.from_now.strftime("%d/%m/%Y %H:%M")) - end - within "tbody tr:last-child td:nth-child(3)" do - expect(page).to have_content("Yes") - end - end - end - - context "when there are tokens" do - let!(:share_tokens) { create_list(:share_token, 3, token_for: component, organization: component.organization, registered_only: true) } - - before do - visit_share_component - end - - it "displays all tokens" do - within ".share_tokens" do - expect(page).to have_css("tbody tr", count: 3) - end - end - - it "displays relevant attributes for each token" do - share_tokens.each do |share_token| - within ".share_tokens tbody" do - expect(page).to have_content share_token.token - expect(page).to have_content share_token.expires_at - end - end - end - - it "can edit a share token" do - within "tbody tr:first-child td:nth-child(3)" do - expect(page).to have_content("Yes") - end - within ".share_tokens tbody tr:first-child" do - click_on "Edit" - end - - expect(page).to have_content("Edit sharing tokens for: #{translated(component.name)}") - find_by_id("share_token_no_expiration_false").click - find_by_id("share_token_registered_only_false").click - click_on "Update" - expect(page).to have_content("cannot be blank", count: 1) - - fill_in "share_token_expires_at", with: 1.day.from_now, visible: :all - click_on "Update" - - expect(page).to have_content("Token updated successfully") - expect(page).to have_css("tbody tr", count: 3) - within "tbody tr:first-child td:nth-child(2)" do - expect(page).to have_content(1.day.from_now.strftime("%d/%m/%Y %H:%M")) - end - within "tbody tr:first-child td:nth-child(3)" do - expect(page).to have_content("No") - end - end - - it "allows copying the share link from the share token" do - within ".share_tokens tbody tr:first-child" do - click_on "Copy link" - expect(page).to have_content("Copied!") - expect(page).to have_css("[data-clipboard-copy-label]") - expect(page).to have_css("[data-clipboard-copy-message]") - expect(page).to have_css("[data-clipboard-content]") - end - end - - it "has a share link for each token" do - urls = share_tokens.map(&:url) - within ".share_tokens tbody tr:first-child" do - share_window = window_opened_by { click_on "Preview" } - - within_window share_window do - expect(urls).to include(page.current_url) - end - end - end - - it "has a share button that opens the share url for the component" do - within ".share_tokens tbody tr:first-child" do - share_window = window_opened_by { click_on "Preview", wait: 2 } - - within_window share_window do - expect(current_url).to include(component.share_tokens.first.url) - end - end - end - - it "can delete tokens" do - within ".share_tokens tbody tr:first-child" do - accept_confirm { click_on "Delete" } - end - - expect(page).to have_admin_callout("Token successfully destroyed") - expect(page).to have_css("tbody tr", count: 2) - end - end - - context "when there are many pages" do - let!(:share_tokens) { create_list(:share_token, 16, token_for: component, organization: component.organization) } - - before do - visit_share_component - end - - it "displays pagination" do - expect(page).to have_css("tbody tr", count: 15) - within '[aria-label="Pagination"]' do - click_on "Next" - end - expect(page).to have_css("tbody tr", count: 1) - end - end - end -end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/manage_participatory_space_share_tokens_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/manage_participatory_space_share_tokens_examples.rb deleted file mode 100644 index dafea57055552..0000000000000 --- a/decidim-core/lib/decidim/core/test/shared_examples/manage_participatory_space_share_tokens_examples.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples "manage participatory space share tokens" do - before do - switch_to_host(organization.host) - login_as user, scope: :user - end - - def visit_share_tokens_path - visit participatory_space_path - click_on "Share tokens" - end - - context "when visiting the share_tokens page for the participatory space" do - let!(:share_token) { create(:share_token, token_for: participatory_space, organization:, user:, registered_only: true) } - - before do - visit participatory_space_path - end - - it "has a share button that opens the share tokens admin" do - click_on "Share" - expect(page).to have_content("Sharing tokens for: #{translated(participatory_space.title)}") - expect(page).to have_css("tbody tr", count: 1) - expect(page).to have_content(share_token.token) - end - end -end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb new file mode 100644 index 0000000000000..64ea10e80482a --- /dev/null +++ b/decidim-core/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +shared_examples "visit resource share tokens" do + let!(:share_token) { create(:share_token, token_for: resource, organization:, user:, registered_only: true) } + + before do + visit_resource_page + end + + it "has a share button that opens the share tokens admin" do + click_on "Share" + expect(page).to have_content("Sharing tokens for: #{resource_name}") + expect(page).to have_css("tbody tr", count: 1) + expect(page).to have_content(share_token.token) + end +end + +shared_examples "manage resource share tokens" do + context "when there are no tokens" do + let(:last_token) { Decidim::ShareToken.last } + before do + visit_share_tokens_page + end + + it "displays empty message" do + expect(page).to have_content "There are no active tokens" + end + + it "can create a new token with default options" do + click_on "New token" + + click_on "Create" + + expect(page).to have_content("Token created successfully") + expect(page).to have_css("tbody tr", count: 1) + within "tbody tr:last-child td:first-child" do + expect(page).to have_content(last_token.token) + end + within "tbody tr:last-child td:nth-child(2)" do + expect(page).to have_content("Never") + end + within "tbody tr:last-child td:nth-child(3)" do + expect(page).to have_content("No") + end + end + + it "can create a new token with custom options" do + click_on "New token" + + find_by_id("share_token_automatic_token_false").click + find_by_id("share_token_no_expiration_false").click + find_by_id("share_token_registered_only_true").click + click_on "Create" + expect(page).to have_content("cannot be blank", count: 2) + + fill_in "share_token_token", with: " custom token " + fill_in "share_token_expires_at", with: 1.day.from_now, visible: :all + click_on "Create" + + expect(page).to have_content("Token created successfully") + expect(page).to have_css("tbody tr", count: 1) + within "tbody tr:last-child td:first-child" do + expect(page).to have_content("CUSTOM-TOKEN") + end + within "tbody tr:last-child td:nth-child(2)" do + expect(page).to have_content(1.day.from_now.strftime("%d/%m/%Y %H:%M")) + end + within "tbody tr:last-child td:nth-child(3)" do + expect(page).to have_content("Yes") + end + end + end + + context "when there are tokens" do + let!(:share_tokens) { create_list(:share_token, 3, token_for: resource, organization:, registered_only: true) } + + before do + visit_share_tokens_page + end + + it "displays all tokens" do + within ".share_tokens" do + expect(page).to have_css("tbody tr", count: 3) + end + end + + it "displays relevant attributes for each token" do + share_tokens.each do |share_token| + within ".share_tokens tbody" do + expect(page).to have_content share_token.token + expect(page).to have_content share_token.expires_at + end + end + end + + it "can edit a share token" do + within "tbody tr:first-child td:nth-child(3)" do + expect(page).to have_content("Yes") + end + within ".share_tokens tbody tr:first-child" do + click_on "Edit" + end + + expect(page).to have_content("Edit sharing tokens for: #{resource_name}") + find_by_id("share_token_no_expiration_false").click + find_by_id("share_token_registered_only_false").click + click_on "Update" + expect(page).to have_content("cannot be blank", count: 1) + + fill_in "share_token_expires_at", with: 1.day.from_now, visible: :all + click_on "Update" + + expect(page).to have_content("Token updated successfully") + expect(page).to have_css("tbody tr", count: 3) + within "tbody tr:first-child td:nth-child(2)" do + expect(page).to have_content(1.day.from_now.strftime("%d/%m/%Y %H:%M")) + end + within "tbody tr:first-child td:nth-child(3)" do + expect(page).to have_content("No") + end + end + + it "allows copying the share link from the share token" do + within ".share_tokens tbody tr:first-child" do + click_on "Copy link" + expect(page).to have_content("Copied!") + expect(page).to have_css("[data-clipboard-copy-label]") + expect(page).to have_css("[data-clipboard-copy-message]") + expect(page).to have_css("[data-clipboard-content]") + end + end + + it "has a share link for each token" do + urls = share_tokens.map(&:url) + within ".share_tokens tbody tr:first-child" do + share_window = window_opened_by { click_on "Preview" } + + within_window share_window do + expect(urls).to include(page.current_url) + end + end + end + + it "has a share button that opens the share url for the resource" do + within ".share_tokens tbody tr:first-child" do + share_window = window_opened_by { click_on "Preview", wait: 2 } + + within_window share_window do + expect(current_url).to include(share_tokens.first.url) + end + end + end + + it "can delete tokens" do + within ".share_tokens tbody tr:first-child" do + accept_confirm { click_on "Delete" } + end + + expect(page).to have_admin_callout("Token successfully destroyed") + expect(page).to have_css("tbody tr", count: 2) + end + end + + context "when there are many pages" do + let!(:share_tokens) { create_list(:share_token, 16, token_for: resource, organization:) } + + before do + visit_share_tokens_page + end + + it "displays pagination" do + expect(page).to have_css("tbody tr", count: 15) + within '[aria-label="Pagination"]' do + click_on "Next" + end + expect(page).to have_css("tbody tr", count: 1) + end + end +end + +shared_examples "manage component share tokens" do + let!(:components_path) { participatory_space_engine.components_path(participatory_space) } + let!(:component) { create(:component, participatory_space:, published_at: nil) } + let(:resource) { component } + let(:resource_name) { translated(component.name) } + + before do + switch_to_host(organization.host) + login_as user, scope: :user + end + + def visit_share_tokens_page + visit components_path + click_on "Components" + click_on "Share" + end + + def visit_resource_page + visit components_path + end + + it_behaves_like "visit resource share tokens" + it_behaves_like "manage resource share tokens" +end + +shared_examples "manage participatory space share tokens" do + let(:resource) { participatory_space } + let(:resource_name) { translated(resource.title) } + + before do + switch_to_host(organization.host) + login_as user, scope: :user + end + + def visit_share_tokens_page + visit participatory_space_path + click_on "Share tokens" + end + + def visit_resource_page + visit participatory_space_path + end + + it_behaves_like "visit resource share tokens" + it_behaves_like "manage resource share tokens" +end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb deleted file mode 100644 index fe58f8f092655..0000000000000 --- a/decidim-core/lib/decidim/core/test/shared_examples/preview_component_with_share_token_examples.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -shared_examples_for "preview component with share_token" do - context "when component is unpublished" do - before do - component.unpublish! - end - - context "when no share_token is provided" do - before do - visit_component - end - - it "does not allow visiting component" do - expect(page).to have_content "You are not authorized" - expect(page).to have_no_current_path(main_component_path(component), ignore_query: true) - end - end - - context "when a share_token is provided" do - let(:share_token) { create(:share_token, token_for: component) } - let(:params) { { share_token: share_token.token } } - - before do - uri = URI(main_component_path(component)) - uri.query = URI.encode_www_form(params.to_a) - visit uri - end - - context "when a valid share_token is provided" do - it "allows visiting component" do - expect(page).to have_no_content "You are not authorized" - expect(page).to have_current_path(main_component_path(component), ignore_query: true) - end - end - - context "when an invalid share_token is provided" do - let(:share_token) { create(:share_token, :expired, token_for: component) } - - it "does not allow visiting component" do - expect(page).to have_content "You are not authorized" - expect(page).to have_no_current_path(main_component_path(component), ignore_query: true) - end - end - - context "when the token requires the user to be registered" do - let(:share_token) { create(:share_token, token_for: component, registered_only: true) } - - it "does not allow visiting component" do - expect(page).to have_content "You are not authorized" - expect(page).to have_no_current_path(main_component_path(component), ignore_query: true) - end - - context "when a user is logged" do - let(:user) { create(:user, :confirmed, organization:) } - - before do - login_as user, scope: :user - uri = URI(main_component_path(component)) - uri.query = URI.encode_www_form(params.to_a) - visit uri - end - - it "allows visiting component" do - expect(page).to have_no_content "You are not authorized" - expect(page).to have_current_path(main_component_path(component), ignore_query: true) - end - end - end - end - end -end diff --git a/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb b/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb new file mode 100644 index 0000000000000..b58a615da9684 --- /dev/null +++ b/decidim-core/lib/decidim/core/test/shared_examples/preview_with_share_token_examples.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +shared_examples "visit unpublished resource with a share token" do + context "when no share_token is provided" do + before do + visit_resource_page + end + + it "does not allow visiting resource" do + expect(page).to have_content "You are not authorized" + expect(page).to have_no_current_path(resource_path, ignore_query: true) + end + end + + context "when a share_token is provided" do + let(:share_token) { create(:share_token, token_for: resource) } + let(:params) { { share_token: share_token.token } } + + before do + uri = URI(resource_path) + uri.query = URI.encode_www_form(params.to_a) + visit uri + end + + context "when a valid share_token is provided" do + it "allows visiting the resource" do + expect(page).to have_no_content "You are not authorized" + expect(current_url).to include(params[:share_token]) + expect(page).to have_current_path(resource_path, ignore_query: true) + + # repeat visit without the token in the params to check for the session + visit resource_path + expect(page).to have_no_content "You are not authorized" + expect(current_url).not_to include(params[:share_token]) + expect(page).to have_current_path(resource_path, ignore_query: true) + end + end + + context "when an invalid share_token is provided" do + let(:share_token) { create(:share_token, :expired, token_for: resource) } + + it "does not allow visiting resource" do + expect(page).to have_content "You are not authorized" + expect(page).to have_no_current_path(resource_path, ignore_query: true) + end + end + + context "when the token requires the user to be registered" do + let(:share_token) { create(:share_token, token_for: resource, registered_only: true) } + + it "does not allow visiting resource" do + expect(page).to have_content "You are not authorized" + expect(page).to have_no_current_path(resource_path, ignore_query: true) + end + + context "when a user is logged" do + let(:user) { create(:user, :confirmed, organization:) } + + before do + login_as user, scope: :user + uri = URI(resource_path) + uri.query = URI.encode_www_form(params.to_a) + visit uri + end + + it "allows visiting resource" do + expect(page).to have_no_content "You are not authorized" + expect(page).to have_current_path(resource_path, ignore_query: true) + end + end + end + end +end + +shared_examples "preview component with a share_token" do + let!(:component) { create(:component, manifest_name:, participatory_space:, published_at: nil) } + let(:resource) { component } + let(:resource_path) { main_component_path(component) } + + def visit_resource_page + visit_component + end + + it_behaves_like "visit unpublished resource with a share token" +end + +shared_examples "preview participatory space with a share_token" do + let(:resource) { participatory_space } + + before do + switch_to_host(organization.host) + end + + def visit_resource_page + visit resource_path + end + + it_behaves_like "visit unpublished resource with a share token" +end diff --git a/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb b/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb index a6785e2c6ea28..01c8e63c83750 100644 --- a/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb +++ b/decidim-debates/spec/system/preview_debates_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview debates with share token" do +describe "preview debates with a share token" do let(:manifest_name) { "debates" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a hare_token" end diff --git a/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb b/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb index e14ef4363ed30..b4b2ee71c0c49 100644 --- a/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb +++ b/decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb @@ -52,6 +52,7 @@ def read_public_initiative? permission_action.action == :read return allow! if initiative.open? || initiative.rejected? || initiative.accepted? + return allow! if user_can_preview_space? return allow! if user && authorship_or_admin? disallow! @@ -181,6 +182,12 @@ def can_user_support?(initiative) ) end + def user_can_preview_space? + return allow! if context[:share_token].present? && Decidim::ShareToken.use!(token_for: initiative, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + def initiative_committee_action? return unless permission_action.subject == :initiative_committee_member diff --git a/decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb b/decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb new file mode 100644 index 0000000000000..bf19aec9acd24 --- /dev/null +++ b/decidim-initiatives/spec/system/preview_initiative_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview initiative with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:initiative, :created, organization:) } + let(:resource_path) { decidim_initiatives.initiative_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb b/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb index 844f7892e22e4..5e6bacd873887 100644 --- a/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb +++ b/decidim-meetings/spec/system/preview_meetings_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview meetings with share token" do +describe "preview meetings with a share token" do let(:manifest_name) { "meetings" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb b/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb index 657244defaed9..273ee7869d23f 100644 --- a/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb +++ b/decidim-pages/spec/system/preview_pages_with_share_token_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -describe "Preview pages with share token" do +describe "preview pages with a share token" do let(:manifest_name) { "pages" } let(:body) do @@ -16,5 +16,5 @@ let!(:page_component) { create(:page, component:, body:) } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb b/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb index da7fc5b7a36a7..529bc4c38808c 100644 --- a/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb +++ b/decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb @@ -117,6 +117,7 @@ def public_read_process_action? return disallow! unless can_view_private_space? return allow! if user&.admin? return allow! if process.published? + return allow! if user_can_preview_space? toggle_allow(can_manage_process?) end @@ -263,6 +264,12 @@ def org_admin_action? allow! if is_allowed end + def user_can_preview_space? + return allow! if context[:share_token].present? && Decidim::ShareToken.use!(token_for: process, token: context[:share_token], user:) + rescue ActiveRecord::RecordNotFound, StandardError + nil + end + def participatory_process_type_action? return unless permission_action.subject == :participatory_process_type return disallow! unless user.admin? diff --git a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb index 83b6b32557ea8..9b4623ec361da 100644 --- a/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb +++ b/decidim-participatory_processes/spec/system/admin/admin_manages_participatory_process_share_tokens_spec.rb @@ -4,9 +4,15 @@ describe "Admin manages participatory process share tokens" do include_context "when admin administrating a participatory process" + let(:participatory_space) { participatory_process } + let(:participatory_space_path) { decidim_admin_participatory_processes.edit_participatory_process_path(participatory_process) } - it_behaves_like "manage participatory space share tokens" do - let(:participatory_space) { participatory_process } - let(:participatory_space_path) { decidim_admin_participatory_processes.edit_participatory_process_path(participatory_process) } + it_behaves_like "manage participatory space share tokens" + + context "when the user is a process admin" do + let(:user) { create(:user, :confirmed, :admin_terms_accepted, organization:) } + let!(:role) { create(:participatory_process_user_role, user:, participatory_process:, role: :admin) } + + it_behaves_like "manage participatory space share tokens" end end diff --git a/decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb b/decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb new file mode 100644 index 0000000000000..7dadba5d57f27 --- /dev/null +++ b/decidim-participatory_processes/spec/system/preview_participatory_process_with_share_token_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Preview participatory process with share token" do + let(:organization) { create(:organization) } + let!(:participatory_space) { create(:participatory_process, organization:, published_at: nil) } + let(:resource_path) { decidim_participatory_processes.participatory_process_path(participatory_space) } + + it_behaves_like "preview participatory space with a share_token" +end diff --git a/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb b/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb index 6b92928203077..fc36958a99eba 100644 --- a/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb +++ b/decidim-proposals/spec/system/preview_proposals_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview proposals with share token" do +describe "preview proposals with a share token" do let(:manifest_name) { "proposals" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb b/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb index a3cbcc00a7d1b..cdf94b1a9d781 100644 --- a/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb +++ b/decidim-sortitions/spec/system/decidim/sortitions/preview_sortitions_with_share_token_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" -describe "Preview sortitions with share token" do +describe "preview sortitions with a share token" do let(:manifest_name) { "sortitions" } include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" end diff --git a/decidim-surveys/spec/system/survey_spec.rb b/decidim-surveys/spec/system/survey_spec.rb index f38404f4f534f..d845b4051e030 100644 --- a/decidim-surveys/spec/system/survey_spec.rb +++ b/decidim-surveys/spec/system/survey_spec.rb @@ -33,7 +33,7 @@ include_context "with a component" - it_behaves_like "preview component with share_token" + it_behaves_like "preview component with a share_token" context "when the survey does not allow answers" do it "does not allow answering the survey" do