Making an Editor a Supereditor

After setting up a Scrivito website project, all users who edit content in place have full control over the working copies they create. They may invite other users to collaborate with them by adding these new collaborators to the list of working copy owners. This can be done by means of the working copy settings.

If you feel comfortable with letting every editor publish their content autonomously but would like to give one or more individuals access to all working copies then this article is for you.

We'll use an imaginary user management back end here that delivers users as contact instances.

Defining Scrivito users' scope of action

Scrivito keeps its own record of individual CMS users and their working copies. Whenever a particular permission is required to perform an action such as publishing a working copy, you can have one or more callbacks executed to determine whether to allow or deny the action. This makes it possible to freely define rules that optionally take account of permissions, properties, or criteria defined elsewhere, e.g. in your user management back end.

Scrivito users originate from the users maintained by your back end. In order to impose CMS permission logic on the original users, we need to additionally represent them as Scrivito users. In this process, we can check the criteria to be met for being a supereditor.

So, what we need is a method that translates a back end user to a Scrivito user. We'll call it from and put it into a module, ScrivitoUser. Later on, we'll use the method to determine the current user and equip them with sweeping powers if they deserve it.

Copy
# app/concerns/scrivito_user.rb

module ScrivitoUser
  SUPEREDITOR = "tim@example.org"

  def self.from(contact)
    Scrivito::User.define(contact.id) do |user_definition|
      if contact.email == SUPEREDITOR
        Scrivito::User::VERBS.each { |action| user_definition.can_always action, :workspace }
      end
      user_definition.description { "#{contact.first_name} #{contact.last_name}" }
      …
    end
  end
end

As you can see, the rule that makes a user a supereditor is defined inside Scrivito::User.define. Above, we merely check the e-mail address of the current contact originating from the user management back end against a static e-mail address. If it matches, the Scrivito user is given all permissions by calling can_always for every permission verb (e.g. create or publish). Note that a Scrivito user who has been given at least one permission for a working copy has access to it through the working copy menu. We're making the supereditor omnipotent here by letting them do everything.

Instead of comparing a user property with a static value such as the user's e-mail address, we could have used an external property, e.g. a role:

Copy
Scrivito::User.define(contact.id) do |user_definition|
  …
  if contact.role_names.include?('supereditor')
    Scrivito::User::VERBS.each { |action| user_definition.can_always action, :workspace }
  end
end

So, Scrivito::User.define lets you implement exceptions to the default behavior using the can_always and can_never methods. Rules for publishing CMS objects can be defined using restrict_obj_publish. Furthermore, the suggest_users callback allows you to calculate the list of users presented to an editor who tries to find someone to invite by means of the settings dialog (auto-completion). Take a look at the examples if you like.

Integrating the rules

ScrivitoUsers.from is called when the Scrivito SDK needs to determine whether or not to permit editing content in place. For this, and for enabling the SDK to associate a working copy with a Scrivito user originating from the user management backend, the application needs to provide the editing_auth callback:

Copy
# config/initializers/scrivito.rb

Scrivito.configure do |config|
  …
  config.editing_auth do |env|
    contact = … # read contact from cache
    ScrivitoUser.from(contact) if contact
  end

  config.find_user do |user_id|
    contact = … # read contact from user management backend
    ScrivitoUser.from(contact)
  end
end

The find_user callback is called by the SDK whenever user details, e.g. the description of working copy owners, need to be displayed.