VYPR
Medium severity5.3OSV Advisory· Published Jul 10, 2024· Updated Apr 15, 2026

CVE-2024-27090

CVE-2024-27090

Description

Decidim is a participatory democracy framework, written in Ruby on Rails, originally developed for the Barcelona City government online and offline participation website. If an attacker can infer the slug or URL of an unpublished or private resource, and this resource can be embbeded (such as a Participatory Process, an Assembly, a Proposal, a Result, etc), then some data of this resource could be accessed. This vulnerability is fixed in 0.27.6.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Decidim exposes unpublished or private resource data to attackers who can guess the resource slug or URL, fixed in version 0.27.6.

Vulnerability

Overview

CVE-2024-27090 is an information exposure vulnerability in Decidim, a Ruby on Rails participatory democracy framework [2]. The issue arises because unpublished or private resources—such as participatory processes, assemblies, proposals, or results—can still be embedded and accessed if an attacker correctly infers the resource's slug or URL. No authentication or special privileges are required to trigger the exposure, only knowledge of the resource identifier [1].

Exploitation

Method

An attacker who can guess or enumerate the slug or URL of an unpublished or private resource can access it via embedding. This is feasible because many Decidim deployments use predictable URL patterns, making it possible to discover hidden resources without authorization. The vulnerability does not require the attacker to be logged in or to have any special network position [3].

Impact

Successful exploitation allows the attacker to view data from otherwise restricted resources. This includes potentially sensitive information such as the titles, descriptions, and other metadata of draft or private participation components. The exposure is limited to what the resource's public embedding would show, but it still represents a breach of the intended access controls [1].

Mitigation

Decidim addressed the vulnerability in version 0.27.6. All installations should upgrade to this or a later release to prevent unauthorized access to unpublished or private content. No workarounds have been provided, and the vendor advises upgrading promptly [1][3].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
decidimRubyGems
< 0.27.60.27.6

Affected products

2

Patches

2
1756fa639ef3

Fix embeds for resources and spaces that shouldn't be embedded (#12528)

https://github.com/decidim/decidimAndrés Pereira de LucenaMar 29, 2024via ghsa
44 files changed · +799 57
  • decidim-assemblies/app/controllers/decidim/assemblies/widgets_controller.rb+11 1 modified
    @@ -5,10 +5,16 @@ module Assemblies
         class WidgetsController < Decidim::WidgetsController
           helper Decidim::SanitizeHelper
     
    +      def show
    +        enforce_permission_to :embed, :participatory_space, current_participatory_space: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Assembly.find_by(slug: params[:assembly_slug])
    +        @model ||= Assembly.where(organization: current_organization).public_spaces.find_by(slug: params[:assembly_slug])
           end
     
           def current_participatory_space
    @@ -18,6 +24,10 @@ def current_participatory_space
           def iframe_url
             @iframe_url ||= assembly_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        ::Decidim.permissions_registry.chain_for(::Decidim::Assemblies::ApplicationController)
    +      end
         end
       end
     end
    
  • decidim-assemblies/app/permissions/decidim/assemblies/permissions.rb+12 0 modified
    @@ -16,6 +16,7 @@ def permissions
             if permission_action.scope == :public
               public_list_assemblies_action?
               public_read_assembly_action?
    +          public_embed_assembly_action?
               public_list_members_action?
               return permission_action
             end
    @@ -136,6 +137,17 @@ def public_list_members_action?
             allow!
           end
     
    +      def public_embed_assembly_action?
    +        return unless permission_action.action == :embed &&
    +                      [:assembly, :participatory_space].include?(permission_action.subject) &&
    +                      assembly
    +
    +        return disallow! unless assembly.published?
    +        return disallow! if assembly.private_space && !assembly.is_transparent?
    +
    +        allow!
    +      end
    +
           # All users with a relation to a assembly and organization admins can enter
           # the space area. The sapce area is considered to be the assemblies zone,
           # not the assembly groups one.
    
  • decidim-assemblies/spec/permissions/decidim/assemblies/permissions_spec.rb+30 0 modified
    @@ -127,6 +127,36 @@
           end
         end
     
    +    context "when embedding an assembly" do
    +      let(:action) do
    +        { scope: :public, action: :embed, subject: :assembly }
    +      end
    +      let(:context) { { assembly: assembly } }
    +
    +      context "when the assembly is published" do
    +        let(:user) { create(:user, organization: organization) }
    +
    +        it { is_expected.to be true }
    +      end
    +
    +      context "when the assembly is not published" do
    +        let(:user) { create(:user, organization: organization) }
    +        let(:assembly) { create(:assembly, :unpublished, organization: organization) }
    +
    +        context "when the user doesn't have access to it" do
    +          it { is_expected.to be false }
    +        end
    +
    +        context "when the user has access to it" do
    +          before do
    +            create(:assembly_user_role, user: user, assembly: assembly)
    +          end
    +
    +          it { is_expected.to be false }
    +        end
    +      end
    +    end
    +
         context "when listing assemblies" do
           let(:action) do
             { scope: :public, action: :list, subject: :assembly }
    
  • decidim-assemblies/spec/system/assembly_embeds_spec.rb+3 0 modified
    @@ -4,6 +4,9 @@
     
     describe "Assembly embeds", type: :system do
       let(:resource) { create(:assembly) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(resource).assembly_widget_path }
     
       it_behaves_like "an embed resource", skip_space_checks: true
    +  it_behaves_like "a private embed resource"
    +  it_behaves_like "a transparent private embed resource"
     end
    
  • decidim-conferences/app/controllers/decidim/conferences/conference_widgets_controller.rb+11 1 modified
    @@ -5,10 +5,16 @@ module Conferences
         class ConferenceWidgetsController < Decidim::WidgetsController
           helper Decidim::SanitizeHelper
     
    +      def show
    +        enforce_permission_to :embed, :conference, conference: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Conference.find_by(slug: params[:conference_slug])
    +        @model ||= Conference.where(organization: current_organization).published.find_by(slug: params[:conference_slug])
           end
     
           def current_participatory_space
    @@ -18,6 +24,10 @@ def current_participatory_space
           def iframe_url
             @iframe_url ||= conference_conference_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        ::Decidim.permissions_registry.chain_for(::Decidim::Conferences::ApplicationController)
    +      end
         end
       end
     end
    
  • decidim-conferences/app/permissions/decidim/conferences/permissions.rb+11 0 modified
    @@ -16,6 +16,7 @@ def permissions
             if permission_action.scope == :public
               public_list_conferences_action?
               public_read_conference_action?
    +          public_embed_conference_action?
               public_list_speakers_action?
               public_list_program_action?
               public_list_media_links_action?
    @@ -131,6 +132,16 @@ def public_read_conference_action?
             toggle_allow(can_manage_conference?)
           end
     
    +      def public_embed_conference_action?
    +        return unless permission_action.action == :embed &&
    +                      [:conference, :participatory_space].include?(permission_action.subject) &&
    +                      conference
    +
    +        return disallow! unless conference.published?
    +
    +        allow!
    +      end
    +
           def public_list_speakers_action?
             return unless permission_action.action == :list &&
                           permission_action.subject == :speakers
    
  • decidim-conferences/spec/permissions/decidim/conferences/permissions_spec.rb+36 0 modified
    @@ -125,6 +125,42 @@
           end
         end
     
    +    context "when embedding a conference" do
    +      let(:action) do
    +        { scope: :public, action: :embed, subject: :conference }
    +      end
    +      let(:context) { { conference: conference } }
    +
    +      context "when the user is an admin" do
    +        let(:user) { create :user, :admin }
    +
    +        it { is_expected.to be true }
    +      end
    +
    +      context "when the conference is published" do
    +        let(:user) { create :user, organization: organization }
    +
    +        it { is_expected.to be true }
    +      end
    +
    +      context "when the conference is not published" do
    +        let(:user) { create :user, organization: organization }
    +        let(:conference) { create :conference, :unpublished, organization: organization }
    +
    +        context "when the user doesn't have access to it" do
    +          it { is_expected.to be false }
    +        end
    +
    +        context "when the user has access to it" do
    +          before do
    +            create :conference_user_role, user: user, conference: conference
    +          end
    +
    +          it { is_expected.to be false }
    +        end
    +      end
    +    end
    +
         context "when listing conferences" do
           let(:action) do
             { scope: :public, action: :list, subject: :conference }
    
  • decidim-conferences/spec/system/conference_embeds_spec.rb+3 13 modified
    @@ -3,18 +3,8 @@
     require "spec_helper"
     
     describe "Conference embeds", type: :system do
    -  let!(:conference) { create(:conference) }
    +  let!(:resource) { create(:conference) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(resource).conference_conference_widget_path }
     
    -  context "when visiting the embed page for an conference" do
    -    before do
    -      switch_to_host(conference.organization.host)
    -      visit resource_locator(conference).path
    -      visit "#{current_path}/embed"
    -    end
    -
    -    it "renders the page correctly" do
    -      expect(page).to have_i18n_content(conference.title)
    -      expect(page).to have_content(conference.organization.name)
    -    end
    -  end
    +  it_behaves_like "an embed resource", skip_space_checks: true, skip_link_checks: true
     end
    
  • decidim-consultations/app/controllers/decidim/consultations/consultation_widgets_controller.rb+12 1 modified
    @@ -4,13 +4,20 @@ module Decidim
       module Consultations
         class ConsultationWidgetsController < Decidim::WidgetsController
           helper Decidim::SanitizeHelper
    +      helper ConsultationsHelper
     
           layout false
     
    +      def show
    +        enforce_permission_to :embed, :participatory_space, current_participatory_space: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Consultation.find_by(slug: params[:consultation_slug])
    +        @model ||= Consultation.where(organization: current_organization).published.find_by(slug: params[:consultation_slug])
           end
     
           def current_participatory_space
    @@ -20,6 +27,10 @@ def current_participatory_space
           def iframe_url
             @iframe_url ||= consultation_consultation_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        ::Decidim.permissions_registry.chain_for(::Decidim::Consultations::ApplicationController)
    +      end
         end
       end
     end
    
  • decidim-consultations/app/controllers/decidim/consultations/question_widgets_controller.rb+11 1 modified
    @@ -8,10 +8,16 @@ class QuestionWidgetsController < Decidim::WidgetsController
     
           helper Decidim::SanitizeHelper
     
    +      def show
    +        enforce_permission_to :embed, :question, question: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= current_question
    +        @model ||= current_question if current_question.published?
           end
     
           def current_participatory_space
    @@ -21,6 +27,10 @@ def current_participatory_space
           def iframe_url
             @iframe_url ||= question_question_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        ::Decidim.permissions_registry.chain_for(::Decidim::Consultations::ApplicationController)
    +      end
         end
       end
     end
    
  • decidim-consultations/app/permissions/decidim/consultations/permissions.rb+21 1 modified
    @@ -5,6 +5,8 @@ module Consultations
         class Permissions < Decidim::DefaultPermissions
           def permissions
             allowed_public_anonymous_action?
    +        allowed_public_embed_consultation_action?
    +        allowed_public_embed_question_action?
     
             return permission_action unless user
     
    @@ -22,7 +24,7 @@ def question
           end
     
           def consultation
    -        @consultation ||= context.fetch(:consultation, nil)
    +        @consultation ||= context.fetch(:current_participatory_space, nil) || context.fetch(:consultation, nil)
           end
     
           def authorized?(permission_action, resource: nil)
    @@ -45,6 +47,24 @@ def allowed_public_anonymous_action?
             end
           end
     
    +      def allowed_public_embed_consultation_action?
    +        return unless permission_action.action == :embed &&
    +                      [:consultation, :participatory_space].include?(permission_action.subject) &&
    +                      consultation
    +
    +        return disallow! unless consultation.published?
    +
    +        allow!
    +      end
    +
    +      def allowed_public_embed_question_action?
    +        return unless permission_action.action == :embed && permission_action.subject == :question && question
    +
    +        return disallow! unless question.published?
    +
    +        allow!
    +      end
    +
           def allowed_public_action?
             return unless permission_action.scope == :public
             return unless permission_action.subject == :question
    
  • decidim-consultations/app/views/decidim/consultations/consultation_widgets/show.html.erb+3 0 modified
    @@ -1,3 +1,6 @@
    +<p><%= translated_attribute(model.title) %></p>
    +<p><%= current_organization.name %></p>
    +
     <%= render partial: "decidim/consultations/consultations/consultation_details", locals: { consultation: model } %>
     <%= render partial: "decidim/consultations/consultations/highlighted_questions", locals: { consultation: model } %>
     <%= render partial: "decidim/consultations/consultations/regular_questions", locals: { consultation: model } %>
    
  • decidim-consultations/spec/permissions/decidim/consultations/permissions_spec.rb+54 0 modified
    @@ -52,6 +52,33 @@
           end
         end
     
    +    context "when embedding a consultation" do
    +      let(:action_name) { :embed }
    +      let(:action_subject) { :consultation }
    +
    +      context "when the consultation is published" do
    +        let(:consultation) { create :consultation, :published }
    +
    +        it { is_expected.to be true }
    +      end
    +
    +      context "when the consultation is not published" do
    +        let(:consultation) { create :consultation, :unpublished }
    +
    +        context "when the user is not an admin" do
    +          let(:user) { nil }
    +
    +          it { is_expected.to be false }
    +        end
    +
    +        context "when the user is an admin" do
    +          let(:user) { create :user, :admin, organization: organization }
    +
    +          it { is_expected.to be false }
    +        end
    +      end
    +    end
    +
         context "when reading a question" do
           let(:action_subject) { :question }
     
    @@ -78,6 +105,33 @@
           end
         end
     
    +    context "when embedding a question" do
    +      let(:action_name) { :embed }
    +      let(:action_subject) { :question }
    +
    +      context "when the question is published" do
    +        let(:question) { create :question, :published, consultation: consultation }
    +
    +        it { is_expected.to be true }
    +      end
    +
    +      context "when the question is not published" do
    +        let(:question) { create :question, :unpublished, consultation: consultation }
    +
    +        context "when the user is not an admin" do
    +          let(:user) { nil }
    +
    +          it { is_expected.to be false }
    +        end
    +
    +        context "when the user is an admin" do
    +          let(:user) { create :user, :admin, organization: organization }
    +
    +          it { is_expected.to be false }
    +        end
    +      end
    +    end
    +
         context "when voting a question" do
           let(:action_subject) { :question }
           let(:action_name) { :vote }
    
  • decidim-consultations/spec/system/consultation_embeds_spec.rb+10 0 added
    @@ -0,0 +1,10 @@
    +# frozen_string_literal: true
    +
    +require "spec_helper"
    +
    +describe "Consultation embeds", type: :system do
    +  let(:resource) { create(:consultation) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(resource).consultation_consultation_widget_path }
    +
    +  it_behaves_like "an embed resource", skip_space_checks: true, skip_link_checks: true
    +end
    
  • decidim-consultations/spec/system/question_embeds_spec.rb+3 12 modified
    @@ -3,17 +3,8 @@
     require "spec_helper"
     
     describe "Question embeds", type: :system do
    -  let(:question) { create(:question) }
    +  let(:resource) { create(:question) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(resource).question_question_widget_path }
     
    -  context "when visiting the embed page for a question" do
    -    before do
    -      switch_to_host(question.organization.host)
    -      visit "#{decidim_consultations.question_path(question)}/embed"
    -    end
    -
    -    it "renders the page correctly" do
    -      expect(page).to have_i18n_content(question.title)
    -      expect(page).to have_content(question.organization.name)
    -    end
    -  end
    +  it_behaves_like "an embed resource", skip_space_checks: true, skip_link_checks: true
     end
    
  • decidim-core/app/controllers/decidim/widgets_controller.rb+6 0 modified
    @@ -11,6 +11,8 @@ class WidgetsController < Decidim::ApplicationController
         helper_method :model, :iframe_url, :current_participatory_space
     
         def show
    +      raise ActionController::RoutingError, "Not Found" if model.nil?
    +
           respond_to do |format|
             format.js { render "decidim/widgets/show" }
             format.html
    @@ -19,6 +21,10 @@ def show
     
         private
     
    +    def current_component
    +      @current_component ||= request.env["decidim.current_component"]
    +    end
    +
         def current_participatory_space
           @current_participatory_space ||= model.component.participatory_space
         end
    
  • decidim-core/lib/decidim/core/test/shared_examples/embed_resource_examples.rb+187 11 modified
    @@ -1,5 +1,50 @@
     # frozen_string_literal: true
     
    +require "decidim/admin/test/admin_participatory_space_access_examples"
    +
    +shared_examples "rendering the embed page correctly" do
    +  before do
    +    visit widget_path
    +  end
    +
    +  it "renders" do
    +    if resource.title.is_a?(Hash)
    +      expect(page).to have_i18n_content(resource.title)
    +    else
    +      expect(page).to have_content(resource.title)
    +    end
    +
    +    expect(page).to have_content(organization.name)
    +  end
    +end
    +
    +shared_examples "rendering the embed link in the resource page" do
    +  before do
    +    visit resource_locator(resource).path
    +  end
    +
    +  it "has the embed link" do
    +    expect(page).to have_button("Embed")
    +  end
    +end
    +
    +shared_examples "showing the unauthorized message in the widget_path" do
    +  it do
    +    visit widget_path
    +    expect(page).to have_content "You are not authorized to perform this action"
    +  end
    +end
    +
    +shared_examples "not rendering the embed link in the resource page" do
    +  before do
    +    visit resource_locator(resource).path
    +  end
    +
    +  it "does not have the embed link" do
    +    expect(page).to have_no_button("Embed")
    +  end
    +end
    +
     shared_examples_for "an embed resource" do |options|
       if options.is_a?(Hash) && options[:skip_space_checks]
         let(:organization) { resource.organization }
    @@ -11,22 +56,29 @@
         include_context "with a component"
       end
     
    -  context "when visiting the embed page for a resource" do
    -    before do
    -      visit resource_locator(resource).path
    -      visit "#{current_path}/embed"
    -    end
    +  unless options.is_a?(Hash) && options[:skip_publication_checks]
    +    context "when the resource is not published" do
    +      before do
    +        resource.unpublish!
    +      end
    +
    +      it_behaves_like "not rendering the embed link in the resource page"
     
    -    it "renders the page correctly" do
    -      if resource.title.is_a?(Hash)
    -        expect(page).to have_i18n_content(resource.title)
    -      else
    -        expect(page).to have_content(resource.title)
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
           end
    +    end
    +  end
    +
    +  it_behaves_like "rendering the embed link in the resource page" unless options.is_a?(Hash) && options[:skip_link_checks]
     
    -      expect(page).to have_content(organization.name)
    +  context "when visiting the embed page for a resource" do
    +    before do
    +      visit widget_path
         end
     
    +    it_behaves_like "rendering the embed page correctly"
    +
         unless options.is_a?(Hash) && options[:skip_space_checks]
           context "when the participatory_space is a process" do
             it "shows the process name" do
    @@ -47,3 +99,127 @@
         end
       end
     end
    +
    +shared_examples_for "a private embed resource" do
    +  let(:organization) { resource.organization }
    +  let!(:other_user) { create(:user, :confirmed, organization: organization) }
    +  let!(:participatory_space_private_user) { create(:participatory_space_private_user, user: other_user, privatable_to: resource) }
    +
    +  before do
    +    switch_to_host(organization.host)
    +  end
    +
    +  context "when the resource is private" do
    +    before do
    +      resource.update!(private_space: true)
    +      resource.update!(is_transparent: false) if resource.respond_to?(:is_transparent)
    +    end
    +
    +    context "and user is a visitor" do
    +      let(:user) { nil }
    +
    +      it_behaves_like "not rendering the embed link in the resource page"
    +
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
    +      end
    +    end
    +
    +    context "and user is a registered user" do
    +      let(:user) { create(:user, :confirmed, organization: organization) }
    +
    +      before do
    +        sign_in user, scope: :user
    +      end
    +
    +      it_behaves_like "not rendering the embed link in the resource page"
    +
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
    +      end
    +    end
    +
    +    context "and user is a private user" do
    +      let(:user) { other_user }
    +
    +      before do
    +        sign_in user, scope: :user
    +      end
    +
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
    +      end
    +    end
    +  end
    +end
    +
    +shared_examples_for "a transparent private embed resource" do
    +  let(:organization) { resource.organization }
    +  let!(:other_user) { create(:user, :confirmed, organization: organization) }
    +  let!(:participatory_space_private_user) { create(:participatory_space_private_user, user: other_user, privatable_to: resource) }
    +
    +  before do
    +    switch_to_host(organization.host)
    +  end
    +
    +  context "when the resource is private" do
    +    before do
    +      resource.update!(private_space: true)
    +      resource.update!(is_transparent: true) if resource.respond_to?(:is_transparent)
    +    end
    +
    +    context "and user is a visitor" do
    +      let(:user) { nil }
    +
    +      it_behaves_like "rendering the embed page correctly"
    +    end
    +
    +    context "and user is a registered user" do
    +      let(:user) { create(:user, :confirmed, organization: organization) }
    +
    +      before do
    +        sign_in user, scope: :user
    +      end
    +
    +      it_behaves_like "rendering the embed page correctly"
    +    end
    +
    +    context "and user is a private user" do
    +      let(:user) { other_user }
    +
    +      before do
    +        sign_in user, scope: :user
    +      end
    +
    +      it_behaves_like "rendering the embed page correctly"
    +    end
    +  end
    +end
    +
    +shared_examples_for "a moderated embed resource" do
    +  include_context "with a component"
    +
    +  context "when the resource is moderated" do
    +    let!(:moderation) { create(:moderation, reportable: resource, hidden_at: 2.days.ago) }
    +
    +    it_behaves_like "a 404 page" do
    +      let(:target_path) { widget_path }
    +    end
    +  end
    +end
    +
    +shared_examples_for "a withdrawn embed resource" do
    +  include_context "with a component"
    +
    +  context "when the resource is withdrawn" do
    +    before do
    +      resource.update!(state: "withdrawn")
    +    end
    +
    +    it_behaves_like "not rendering the embed link in the resource page"
    +
    +    it_behaves_like "a 404 page" do
    +      let(:target_path) { widget_path }
    +    end
    +  end
    +end
    
  • decidim-debates/app/controllers/decidim/debates/widgets_controller.rb+11 1 modified
    @@ -5,15 +5,25 @@ module Debates
         class WidgetsController < Decidim::WidgetsController
           helper Debates::ApplicationHelper
     
    +      def show
    +        enforce_permission_to :embed, :debate, debate: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Debate.where(component: params[:component_id]).find(params[:debate_id])
    +        @model ||= Debate.not_hidden.where(component: current_component).find(params[:debate_id])
           end
     
           def iframe_url
             @iframe_url ||= debate_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        [Decidim::Debates::Permissions]
    +      end
         end
       end
     end
    
  • decidim-debates/app/permissions/decidim/debates/permissions.rb+6 0 modified
    @@ -21,6 +21,8 @@ def permissions
               can_endorse_debate?
             when :close
               can_close_debate?
    +        when :embed
    +          can_embed_debate?
             end
     
             permission_action
    @@ -45,6 +47,10 @@ def can_close_debate?
             disallow!
           end
     
    +      def can_embed_debate?
    +        allow!
    +      end
    +
           def can_endorse_debate?
             return disallow! if debate.closed?
     
    
  • decidim-debates/spec/permissions/decidim/debates/permissions_spec.rb+14 0 modified
    @@ -106,4 +106,18 @@
           it { is_expected.to be false }
         end
       end
    +
    +  context "when embedding a debate" do
    +    let(:action) do
    +      { scope: :public, action: :embed, subject: :debate }
    +    end
    +
    +    it { is_expected.to be true }
    +
    +    context "when the debate is closed" do
    +      let(:debate) { create :debate, :closed, component: debates_component }
    +
    +      it { is_expected.to be true }
    +    end
    +  end
     end
    
  • decidim-debates/spec/system/debate_embeds_spec.rb+4 1 modified
    @@ -4,8 +4,11 @@
     
     describe "Debate embeds", type: :system do
       include_context "with a component"
    +
       let(:manifest_name) { "debates" }
       let!(:resource) { create(:debate, component: component, skip_injection: true) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(component).debate_widget_path(resource) }
     
    -  it_behaves_like "an embed resource"
    +  it_behaves_like "an embed resource", skip_publication_checks: true
    +  it_behaves_like "a moderated embed resource"
     end
    
  • decidim-initiatives/app/controllers/decidim/initiatives/widgets_controller.rb+15 1 modified
    @@ -12,10 +12,20 @@ class WidgetsController < Decidim::WidgetsController
     
           include NeedsInitiative
     
    +      def show
    +        enforce_permission_to :embed, :participatory_space, current_participatory_space: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= current_initiative
    +        @model ||= if current_initiative.created? || current_initiative.validating? || current_initiative.discarded?
    +                     nil
    +                   else
    +                     current_initiative
    +                   end
           end
     
           def current_participatory_space
    @@ -25,6 +35,10 @@ def current_participatory_space
           def iframe_url
             @iframe_url ||= initiative_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        ::Decidim.permissions_registry.chain_for(::Decidim::Initiatives::ApplicationController)
    +      end
         end
       end
     end
    
  • decidim-initiatives/app/permissions/decidim/initiatives/permissions.rb+10 0 modified
    @@ -11,6 +11,7 @@ def permissions
             # Non-logged users permissions
             list_public_initiatives?
             read_public_initiative?
    +        embed_public_initiative?
             search_initiative_types_and_scopes?
             request_membership?
     
    @@ -57,6 +58,15 @@ def read_public_initiative?
             disallow!
           end
     
    +      def embed_public_initiative?
    +        return unless [:initiative, :participatory_space].include?(permission_action.subject) &&
    +                      permission_action.action == :embed
    +
    +        return disallow! if initiative.created? || initiative.validating? || initiative.discarded?
    +
    +        allow!
    +      end
    +
           def search_initiative_types_and_scopes?
             return unless permission_action.action == :search
             return unless [:initiative_type, :initiative_type_scope, :initiative_type_signature_types].include?(permission_action.subject)
    
  • decidim-initiatives/app/views/decidim/initiatives/initiatives/show.html.erb+3 1 modified
    @@ -65,7 +65,9 @@ edit_link(
           </div>
         <% end %>
         <%= render partial: "decidim/shared/share_modal" %>
    -    <%= embed_modal_for initiative_widget_url(current_initiative, format: :js) %>
    +    <% if allowed_to? :embed, :initiative, initiative: current_initiative %>
    +      <%= embed_modal_for initiative_widget_url(current_initiative, format: :js) %>
    +    <% end %>
         <%= resource_reference(current_initiative) %>
         <%= resource_version(current_initiative, versions_path: initiative_versions_path(current_initiative)) %>
       </div>
    
  • decidim-initiatives/spec/permissions/decidim/initiatives/permissions_spec.rb+70 0 modified
    @@ -149,6 +149,76 @@
         end
       end
     
    +  context "when emeding an initiative" do
    +    let(:initiative) { create(:initiative, :accepted, organization: organization) }
    +    let(:action) do
    +      { scope: :public, action: :embed, subject: :initiative }
    +    end
    +    let(:context) do
    +      { initiative: initiative }
    +    end
    +
    +    context "when initiative is created" do
    +      let(:initiative) { create(:initiative, :created, organization: organization) }
    +
    +      it { is_expected.to be false }
    +    end
    +
    +    context "when initiative is validating" do
    +      let(:initiative) { create(:initiative, :validating, organization: organization) }
    +
    +      it { is_expected.to be false }
    +    end
    +
    +    context "when initiative is discarded" do
    +      let(:initiative) { create(:initiative, :discarded, organization: organization) }
    +
    +      it { is_expected.to be false }
    +    end
    +
    +    context "when initiative is published" do
    +      let(:initiative) { create(:initiative, :published, organization: organization) }
    +
    +      it { is_expected.to be true }
    +    end
    +
    +    context "when initiative is rejected" do
    +      let(:initiative) { create(:initiative, :rejected, organization: organization) }
    +
    +      it { is_expected.to be true }
    +    end
    +
    +    context "when initiative is accepted" do
    +      let(:initiative) { create(:initiative, :accepted, organization: organization) }
    +
    +      it { is_expected.to be true }
    +    end
    +
    +    context "when user is admin" do
    +      let(:user) { create(:user, :admin, organization: organization) }
    +
    +      it { is_expected.to be true }
    +    end
    +
    +    context "when user is author of the initiative" do
    +      let(:initiative) { create(:initiative, author: user, organization: organization) }
    +
    +      it { is_expected.to be true }
    +    end
    +
    +    context "when user is committee member of the initiative" do
    +      before do
    +        create(:initiatives_committee_member, initiative: initiative, user: user)
    +      end
    +
    +      it { is_expected.to be true }
    +    end
    +
    +    context "when any other condition" do
    +      it { is_expected.to be true }
    +    end
    +  end
    +
       context "when listing committee members of the initiative as author" do
         let(:initiative) { create(:initiative, organization: organization, author: user) }
         let(:action) do
    
  • decidim-initiatives/spec/system/initiative_embeds_spec.rb+64 2 modified
    @@ -3,7 +3,69 @@
     require "spec_helper"
     
     describe "Initiative embeds", type: :system do
    -  let(:resource) { create(:initiative) }
    +  let(:state) { :published }
    +  let(:resource) { create(:initiative, state: state) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(resource).initiative_widget_path }
     
    -  it_behaves_like "an embed resource", skip_space_checks: true
    +  it_behaves_like "an embed resource", skip_space_checks: true, skip_publication_checks: true
    +
    +  context "when the user is the initiative author" do
    +    let(:organization) { resource.organization }
    +    let(:user) { resource.author }
    +
    +    before do
    +      switch_to_host(organization.host)
    +    end
    +
    +    context "when the state is created" do
    +      let(:state) { :created }
    +
    +      it_behaves_like "not rendering the embed link in the resource page"
    +
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
    +      end
    +    end
    +
    +    context "when the state is validating" do
    +      let(:state) { :validating }
    +
    +      it_behaves_like "not rendering the embed link in the resource page"
    +
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
    +      end
    +    end
    +
    +    context "when the state is discarded" do
    +      let(:state) { :discarded }
    +
    +      # A discarded initiative is not available anymore to authors
    +
    +      it_behaves_like "a 404 page" do
    +        let(:target_path) { widget_path }
    +      end
    +    end
    +
    +    context "when the state is published" do
    +      let(:state) { :published }
    +
    +      it_behaves_like "rendering the embed link in the resource page"
    +      it_behaves_like "rendering the embed page correctly"
    +    end
    +
    +    context "when the state is rejected" do
    +      let(:state) { :rejected }
    +
    +      it_behaves_like "rendering the embed link in the resource page"
    +      it_behaves_like "rendering the embed page correctly"
    +    end
    +
    +    context "when the state is accepted" do
    +      let(:state) { :accepted }
    +
    +      it_behaves_like "rendering the embed link in the resource page"
    +      it_behaves_like "rendering the embed page correctly"
    +    end
    +  end
     end
    
  • decidim-meetings/app/controllers/decidim/meetings/widgets_controller.rb+11 1 modified
    @@ -6,15 +6,25 @@ class WidgetsController < Decidim::WidgetsController
           helper MeetingsHelper
           helper Decidim::SanitizeHelper
     
    +      def show
    +        enforce_permission_to :embed, :meeting, meeting: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Meeting.where(component: params[:component_id]).find(params[:meeting_id])
    +        @model ||= Meeting.except_withdrawn.published.not_hidden.where(component: current_component).find(params[:meeting_id])
           end
     
           def iframe_url
             @iframe_url ||= meeting_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        [Decidim::Meetings::Permissions]
    +      end
         end
       end
     end
    
  • decidim-meetings/app/permissions/decidim/meetings/permissions.rb+10 0 modified
    @@ -4,6 +4,7 @@ module Decidim
       module Meetings
         class Permissions < Decidim::DefaultPermissions
           def permissions
    +        allow_embed_meeting?
             return permission_action unless user
     
             # Delegate the admin permission checks to the admin permissions class
    @@ -57,6 +58,15 @@ def question
             @question ||= context.fetch(:question, nil)
           end
     
    +      # As this is a public action, we need to run this before other checks
    +      def allow_embed_meeting?
    +        return unless permission_action.action == :embed && permission_action.subject == :meeting && meeting
    +        return disallow! if meeting.withdrawn?
    +        return allow! if meeting.published?
    +
    +        disallow!
    +      end
    +
           def can_join_meeting?
             meeting.can_be_joined_by?(user) &&
               authorized?(:join, resource: meeting)
    
  • decidim-meetings/app/views/decidim/meetings/meetings/show.html.erb+3 1 modified
    @@ -105,7 +105,9 @@ edit_link(
         <%= resource_version(meeting, versions_path: meeting_versions_path(meeting)) %>
         <%= cell "decidim/meetings/cancel_registration_meeting_button", meeting %>
         <%= render partial: "decidim/shared/share_modal" %>
    -    <%= embed_modal_for meeting_widget_url(meeting, format: :js) %>
    +    <% if allowed_to? :embed, :meeting, meeting: @meeting %>
    +      <%= embed_modal_for meeting_widget_url(meeting, format: :js) %>
    +    <% end %>
         <%= render partial: "calendar_modal", locals: { ics_url: calendar_meeting_url(meeting), google_url: google_calendar_event_url(meeting) } %>
       </div>
       <div class="columns mediumlarge-8 mediumlarge-pull-4">
    
  • decidim-meetings/spec/permissions/decidim/meetings/permissions_spec.rb+22 0 modified
    @@ -82,6 +82,28 @@
         end
       end
     
    +  context "when embedding a meeting" do
    +    let(:action) do
    +      { scope: :public, action: :embed, subject: :meeting }
    +    end
    +
    +    context "when meeting isn't published" do
    +      before do
    +        allow(meeting).to receive(:published?).and_return(false)
    +      end
    +
    +      it { is_expected.to be false }
    +    end
    +
    +    context "when meeting is published" do
    +      before do
    +        allow(meeting).to receive(:published?).and_return(true)
    +      end
    +
    +      it { is_expected.to be true }
    +    end
    +  end
    +
       context "when joining a meeting" do
         let(:action) do
           { scope: :public, action: :join, subject: :meeting }
    
  • decidim-meetings/spec/system/meeting_embeds_spec.rb+4 1 modified
    @@ -4,9 +4,12 @@
     
     describe "Meeting embeds", type: :system do
       include_context "with a component"
    -  let(:manifest_name) { "meetings" }
     
    +  let(:manifest_name) { "meetings" }
       let!(:resource) { create(:meeting, :published, component: component) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(component).meeting_widget_path(resource) }
     
       it_behaves_like "an embed resource"
    +  it_behaves_like "a moderated embed resource"
    +  it_behaves_like "a withdrawn embed resource"
     end
    
  • decidim-participatory_processes/app/controllers/decidim/participatory_processes/widgets_controller.rb+12 2 modified
    @@ -5,13 +5,19 @@ module ParticipatoryProcesses
         class WidgetsController < Decidim::WidgetsController
           helper Decidim::SanitizeHelper
     
    +      def show
    +        enforce_permission_to :embed, :participatory_space, current_participatory_space: model
    +
    +        super
    +      end
    +
           private
     
           def model
             return unless params[:participatory_process_slug]
     
    -        @model ||= ParticipatoryProcess.where(slug: params[:participatory_process_slug]).or(
    -          ParticipatoryProcess.where(id: params[:participatory_process_slug])
    +        @model ||= ParticipatoryProcess.where(organization: current_organization).public_spaces.where(slug: params[:participatory_process_slug]).or(
    +          ParticipatoryProcess.where(organization: current_organization).public_spaces.where(id: params[:participatory_process_slug])
             ).first!
           end
     
    @@ -22,6 +28,10 @@ def current_participatory_space
           def iframe_url
             @iframe_url ||= participatory_process_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        ::Decidim.permissions_registry.chain_for(::Decidim::ParticipatoryProcesses::ApplicationController)
    +      end
         end
       end
     end
    
  • decidim-participatory_processes/app/permissions/decidim/participatory_processes/permissions.rb+12 0 modified
    @@ -19,6 +19,7 @@ def permissions
               public_list_process_groups_action?
               public_read_process_group_action?
               public_read_process_action?
    +          public_embed_process_action?
               return permission_action
             end
     
    @@ -112,6 +113,17 @@ def public_read_process_action?
             toggle_allow(can_manage_process?)
           end
     
    +      def public_embed_process_action?
    +        return unless permission_action.action == :embed &&
    +                      [:process, :participatory_space].include?(permission_action.subject) &&
    +                      process
    +
    +        return disallow! unless process.published?
    +        return disallow! if process.private_space
    +
    +        allow!
    +      end
    +
           def can_view_private_space?
             return true unless process.private_space
             return false unless user
    
  • decidim-participatory_processes/spec/permissions/decidim/participatory_processes/permissions_spec.rb+30 0 modified
    @@ -135,6 +135,36 @@
           end
         end
     
    +    context "when embedding an process" do
    +      let(:action) do
    +        { scope: :public, action: :embed, subject: :process }
    +      end
    +      let(:context) { { process: process } }
    +
    +      context "when the process is published" do
    +        let(:user) { create(:user, organization: organization) }
    +
    +        it { is_expected.to be true }
    +      end
    +
    +      context "when the process is not published" do
    +        let(:user) { create(:user, organization: organization) }
    +        let(:process) { create(:participatory_process, :unpublished, organization: organization) }
    +
    +        context "when the user doesn't have access to it" do
    +          it { is_expected.to be false }
    +        end
    +
    +        context "when the user has access to it" do
    +          before do
    +            create(:participatory_process_user_role, user: user, participatory_process: process)
    +          end
    +
    +          it { is_expected.to be false }
    +        end
    +      end
    +    end
    +
         context "when listing processes" do
           let(:action) do
             { scope: :public, action: :list, subject: :process }
    
  • decidim-participatory_processes/spec/system/process_embeds_spec.rb+2 0 modified
    @@ -4,6 +4,8 @@
     
     describe "Process embeds", type: :system do
       let(:resource) { create(:participatory_process) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(resource).participatory_process_widget_path }
     
       it_behaves_like "an embed resource", skip_space_checks: true
    +  it_behaves_like "a private embed resource"
     end
    
  • decidim-proposals/app/controllers/decidim/proposals/widgets_controller.rb+11 1 modified
    @@ -5,15 +5,25 @@ module Proposals
         class WidgetsController < Decidim::WidgetsController
           helper Proposals::ApplicationHelper
     
    +      def show
    +        enforce_permission_to :embed, :proposal, proposal: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Proposal.where(component: params[:component_id]).find(params[:proposal_id])
    +        @model ||= Proposal.not_hidden.except_withdrawn.where(component: current_component).find(params[:proposal_id])
           end
     
           def iframe_url
             @iframe_url ||= proposal_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        [Decidim::Proposals::Permissions]
    +      end
         end
       end
     end
    
  • decidim-proposals/app/permissions/decidim/proposals/permissions.rb+9 0 modified
    @@ -4,6 +4,7 @@ module Decidim
       module Proposals
         class Permissions < Decidim::DefaultPermissions
           def permissions
    +        allow_embed_proposal?
             return permission_action unless user
     
             # Delegate the admin permission checks to the admin permissions class
    @@ -47,6 +48,14 @@ def proposal
             @proposal ||= context.fetch(:proposal, nil) || context.fetch(:resource, nil)
           end
     
    +      # As this is a public action, we need to run this before other checks
    +      def allow_embed_proposal?
    +        return unless permission_action.action == :embed && permission_action.subject == :proposal && proposal
    +        return disallow! if proposal.withdrawn?
    +
    +        allow!
    +      end
    +
           def voting_enabled?
             return unless current_settings
     
    
  • decidim-proposals/app/views/decidim/proposals/proposals/show.html.erb+3 1 modified
    @@ -129,7 +129,9 @@ extra_admin_link(
         <%= resource_version(proposal_presenter, versions_path: proposal_versions_path(@proposal)) %>
         <%= cell("decidim/fingerprint", @proposal) %>
         <%= render partial: "decidim/shared/share_modal", locals: { resource: @proposal } %>
    -    <%= embed_modal_for proposal_widget_url(@proposal, format: :js) %>
    +    <% if allowed_to? :embed, :proposal, proposal: @proposal %>
    +      <%= embed_modal_for proposal_widget_url(@proposal, format: :js) %>
    +    <% end %>
         <%= cell "decidim/proposals/proposal_link_to_collaborative_draft", @proposal %>
         <%= cell "decidim/proposals/proposal_link_to_rejected_emendation", @proposal %>
       </div>
    
  • decidim-proposals/spec/permissions/decidim/proposals/permissions_spec.rb+8 0 modified
    @@ -110,6 +110,14 @@
         end
       end
     
    +  context "when emebeding a proposal" do
    +    let(:action) do
    +      { scope: :public, action: :embed, subject: :proposal }
    +    end
    +
    +    it { is_expected.to be true }
    +  end
    +
       describe "voting" do
         let(:action) do
           { scope: :public, action: :vote, subject: :proposal }
    
  • decidim-proposals/spec/system/proposal_embeds_spec.rb+5 1 modified
    @@ -4,8 +4,12 @@
     
     describe "Proposal embeds", type: :system do
       include_context "with a component"
    +
       let(:manifest_name) { "proposals" }
       let(:resource) { create(:proposal, component: component) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(component).proposal_widget_path(resource) }
     
    -  it_behaves_like "an embed resource"
    +  it_behaves_like "an embed resource", skip_publication_checks: true
    +  it_behaves_like "a moderated embed resource"
    +  it_behaves_like "a withdrawn embed resource"
     end
    
  • decidim-sortitions/app/controllers/decidim/sortitions/widgets_controller.rb+11 1 modified
    @@ -6,15 +6,25 @@ class WidgetsController < Decidim::WidgetsController
           helper Decidim::SanitizeHelper
           helper Sortitions::SortitionsHelper
     
    +      def show
    +        enforce_permission_to :embed, :sortition, sortition: model if model
    +
    +        super
    +      end
    +
           private
     
           def model
    -        @model ||= Sortition.where(component: params[:component_id]).find(params[:sortition_id])
    +        @model ||= Sortition.where(component: current_component).find(params[:sortition_id])
           end
     
           def iframe_url
             @iframe_url ||= sortition_widget_url(model)
           end
    +
    +      def permission_class_chain
    +        [Decidim::Sortitions::Permissions]
    +      end
         end
       end
     end
    
  • decidim-sortitions/app/permissions/decidim/sortitions/permissions.rb+14 0 modified
    @@ -4,12 +4,26 @@ module Decidim
       module Sortitions
         class Permissions < Decidim::DefaultPermissions
           def permissions
    +        allow_embed_sortition?
             return permission_action unless user
     
             return Decidim::Sortitions::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
     
             permission_action
           end
    +
    +      private
    +
    +      def sortition
    +        @sortition ||= context.fetch(:sortition, nil) || context.fetch(:resource, nil)
    +      end
    +
    +      # As this is a public action, we need to run this before other checks
    +      def allow_embed_sortition?
    +        return unless permission_action.action == :embed && permission_action.subject == :sortition && sortition
    +
    +        allow!
    +      end
         end
       end
     end
    
  • decidim-sortitions/spec/permissions/decidim/sortitions/permissions_spec.rb+8 0 modified
    @@ -25,6 +25,14 @@
         it_behaves_like "delegates permissions to", Decidim::Sortitions::Admin::Permissions
       end
     
    +  context "when emebedding a sortition" do
    +    let(:action) do
    +      { scope: :public, action: :embed, subject: :sortition }
    +    end
    +
    +    it { is_expected.to be true }
    +  end
    +
       context "when any other condition" do
         let(:action) do
           { scope: :foo, action: :blah, subject: :sortition }
    
  • decidim-sortitions/spec/system/decidim/sortitions/sortition_embeds_spec.rb+3 1 modified
    @@ -4,8 +4,10 @@
     
     describe "Sortition embeds", type: :system do
       include_context "with a component"
    +
       let(:manifest_name) { "sortitions" }
       let(:resource) { create(:sortition, component: component) }
    +  let(:widget_path) { Decidim::EngineRouter.main_proxy(component).sortition_widget_path(resource) }
     
    -  it_behaves_like "an embed resource"
    +  it_behaves_like "an embed resource", skip_publication_checks: true
     end
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.