Share tokens

Share tokens can be assigned to any model to provide a system to share unpublished resources with expiration dates through the creation/destruction of tokens.

A share token is created by a user with an expiration time, and can be added as a query param to access otherwise restricted locations.

Add share tokens to a model

The model must include Decidim::ShareableWithToken and implement shareable_url(share_token), which should return the public url for the resource you want to share, including the token as a query parameter.

# Public: Public URL for your_resource with given share token as query parameter
def shareable_url(share_token)
  your_resource_public_url(self, share_token: share_token.token)
end

Set permissions

You should change permissions logic for the resource to check if there is a share_token query parameter in the request url, and call Decidim::ShareToken.use! to both check if the token is valid, and if it is, to use it (which increments times_used variable and sets last_used_at to current time).

It should do something similar to this:

token = context[:share_token]

return unless token.present?

allow! if Decidim::ShareToken.use!(token_for: your_resource, token: token)

Note that, if you are using a controller who is inheriting from Decidim::ApplicationController, you do not need to include the :share_token in the context when calling methods like enforce_permission_to, as it is already included in the Decidim::NeedsPermissions class through the method store_share_token.

Manage tokens

By default, participatory spaces like process, assemblies, conferences and initiatives are configured to have share tokens, as well as the individual components that are included in them. Participatory spaces have a "Share tokens" tab in the admin view, where you can create new tokens, see the list of existing ones, and revoke them. Tokens can also be managed in the components view similarly as other resources to give pre-access (with and action icon like permissions for instance).

Tokens generated for a participatory space are valid for all the components included in it (regardless of their publication status), and tokens generated for a component are valid for that component only.

Implementation for participatory spaces

In order to implement share tokens for a participatory spaces, you need to:

1. Routes

Add the share_tokens CRUD routes in your admin_engine.rb file:

scope "/assemblies/:assembly_slug" do
  ...
  resources :assembly_share_tokens, except: [:show], path: "share_tokens"
  ...
end

2. Controller

Add the controller for the participatory space, it only requires to inherit from Decidim::Admin::ShareTokensController and define the resource method to return the participatory space:

# frozen_string_literal: true

module Decidim
  module Assemblies
    module Admin
      # This controller allows sharing unpublished things.
      # It is targeted for customizations for sharing unpublished things that lives under
      # an assembly.
      class AssemblyShareTokensController < Decidim::Admin::ShareTokensController
        include Concerns::AssemblyAdmin

        def resource
          current_assembly
        end
      end
    end
  end
end

3. Menu entry

Add the menu entry for the share tokens in the participatory space admin view. In Decidim we do this in the menu.rb file for each participatory space:

Decidim.menu :admin_assembly_menu do |menu|
  ...
  menu.add_item :assembly_share_tokens,
                I18n.t("menu.share_tokens", scope: "decidim.admin"),
                decidim_admin_assemblies.assembly_share_tokens_path(current_participatory_space),
                active: is_active_link?(decidim_admin_assemblies.assembly_share_tokens_path(current_participatory_space)),
                icon_name: "share-line",
                if: allowed_to?(:read, :share_tokens, current_participatory_space:)
  ...
end

4. Model

Ensure your participatory space model includes the Decidim::ShareableWithToken module and implements the shareable_url method:

module Decidim
  class Assembly < ApplicationRecord
    ...
    include Decidim::ShareableWithToken
    ...
    def shareable_url(share_token)
      EngineRouter.main_proxy(self).assembly_url(self, share_token: share_token.token)
    end
    ...
  end
end

5. Permissions

Add the permissions logic to the participatory space controller in the permissions.rb file:

For admin controllers:

allow! if permission_action.subject == :share_tokens

For frontend controllers:

token = context[:share_token]

return unless token.present?

allow! if Decidim::ShareToken.use!(token_for: current_assembly, token: token)

Implementation for components

Components all inherit from Decidim::Component, so they already have the Decidim::ShareableWithToken module included. But you still need to do some steps:

1. Routes

Add the share_tokens CRUD routes in your admin_engine.rb file:

scope "/assemblies/:assembly_slug" do
  ...
  resources :components do
    ...
    resources :component_share_tokens, except: [:show], path: "share_tokens", as: "share_tokens"
    ...
  end
end

2. Controller

Add the controller for the component, it only requires to inherit from Decidim::Admin::ShareTokensController and define the resource method to return the component:

# frozen_string_literal: true

module Decidim
  module Assemblies
    module Admin
      # This controller allows sharing unpublished things.
      # It is targeted for customizations for sharing unpublished things that lives under
      # an assembly.
      class ComponentShareTokensController < Decidim::Admin::ShareTokensController
        include Concerns::AssemblyAdmin

        def resource
          @resource ||= current_participatory_space.components.find(params[:component_id])
        end
      end
    end
  end
end

3. Permissions

Similarly, add the same permissions logic to the component controller in the permissions.rb file as for participatory spaces.

Other implementations

You can implement share tokens for any other model by following the same steps as for participatory spaces and components. In that case, however, you might have to override some methods from the Decidim::Admin::ShareTokensController to adapt them to your model (check the source code for details).