Learn how Scrivito CMS can help you deliver amazing digital experiences
See Scrivito CMS in action

Defining Users and Their Permissions

Defining Users and Their Permissions

For the purpose of granting users specific levels of access to individual working copies, Scrivito includes a customizable permission control system.

To make use of this system, it needs to be integrated into your Rails application, which is expected to be already connected to an authorization back end. We don't provide code that communicates with your back end. Instead, we'll shed some light on the layer on top.

Converting application users to Scrivito users

For the purpose of this guide, let us assume the users of your application are represented by the following class.

class MyUser attr_reader :id, :first_name, :last_name def self.find(id) # ... (to be implemented) end def self.search_by_prefix(prefix, options) # ... (to be implemented) end # Construct a user instance from locally cached data. def self.build_from_cached_data(data) # ... (to be implemented) end def has_permission?(permission) # ... (to be implemented) end def admin? has_permission?(:admin) end end

For giving Scrivito access to the users of your application, they need to be converted to Scrivito users. Every Scrivito user must have a unique, unalterable ID. The ID needs to be passed as a string and is persisted in the CMS to associate the user with their CMS resources.

In the example below, MyUserConversion.to_scrivito_user converts a user by means of Scrivito::User.define. Also, a human-readable description is generated for presenting the user in the UI.

module MyUserConversion # Take a 'MyUser' and convert it to a 'Scrivito::User'. def self.to_scrivito_user(my_user) Scrivito::User.define(my_user.id.to_s) do |user_definition| user_definition.description { "#{my_user.first_name} #{my_user.last_name}" } # Extend the user definition here end end end

Overriding role permissions

Working copies can be associated with role memberships to grant or deny users permission to perform specific actions, e.g. publish a working copy. Currently, only one role exists, “owner.” A user who was given this role for a working copy has full control over it. However, you can override a user's permissions using the can_always and can_never methods of the user definition, like so:

user_definition.can_always(:read, :workspace) # Require an individual permission for creating working copies unless my_user.has_permission?(:create_workspace) user_definition.can_never(:create, :workspace) end

The following permission verbs are available:

  • read makes the working copy and its content visible to the user.
  • write lets the user modify the settings of the working copy and its content.
  • create lets the user create new working copies.
  • delete permits the user to delete the working copy.
  • publish permits the user to publish the working copy.
  • read_history lets the user open the publishing history.
  • invite_to permits the user to invite other users to collaborate on the working copy.

Suggesting collaborators

Using the in-place editing interface, an editor may invite other users to collaborate on a working copy. This can be done using the “Settings” dialog.

To have proper user names show up after entering some letters names are supposed to start with, implement the suggest_users callback as part of the user definition.

user_definition.suggest_users do |input| MyUser.search_by_prefix(input, limit: 20).map do |my_user|    to_scrivito_user(my_user) end end

Above, the input is what the user has typed into the search field. This input should be used to narrow down the selection (“search while you type”).

Defining content-specific publishing restrictions

Sometimes, specific sections of a website are meant to be edited by specialized users only. To prevent others from publishing changes to the content of such sections, Scrivito lets you define publishing restrictions based on the properties of CMS objects.

The restrictions can be implemented as restrict_obj_publish callbacks which should either return nothing (the CMS object may be published), or a string describing why the user cannot publish the object.

On an attempt to publish a working copy, the restrict_obj_publish callback is executed for each CMS object that has been modified, allowing you to interrupt this process depending on the rules you've defined.
In the example below, the _path attribute of a changed “Obj” is used to determine whether the user attempts to publish changes to the “investor relations” section, which requires a special permission.
user_definition.restrict_obj_publish(using: :_path) do |path| if path.starts_with?('/investor_relations') && !my_user.has_permission?('publish_investor_relations') "You are not permitted to publish changes to the 'investor relations' section." end end

As you can see, the attribute of the object to check can be specified by means of the using argument. In addition to the restriction above, we don't want users to publish the homepage unless they have been given a corresponding permission. This time, we use the class name of the CMS object:

user_definition.restrict_obj_publish(using: :_obj_class) do |obj_class| if obj_class == 'Homepage' && !my_user.has_permission?('publish_homepage') 'You are not permitted to publish the homepage.' end end
The callback will prevent publishing if the conditions are not met in either the current or the published working copy. This ensures that an editor cannot grant themselves the permission to publish an object by changing the value of an attribute so that it meets the condition of the callback.

You can, of course, restrict publishing depending on a custom attribute, too. In the following example, publishing pages flagged as admin_only requires admin permissions:

user_definition.restrict_obj_publish(using: :admin_only) do |admin_only| if admin_only == 'yes' && !my_user.admin? "This page can only be published by an administrator." end end

If an unauthorized editor sees the message from the callback above for the first time, they could change the attribute value using the page properties dialog. So, if the callback checked the attribute value only in the current working copy, the editor could publish it afterwards. By also checking the attribute value in the published content, the callback makes sure that the editor cannot bypass the restrictions.

To check from within the in-place editing interface whether the user is permitted to publish the current page, the following JavaScript API method is available. The method returns true if publishing is allowed, else false.

scrivito.is_current_page_restricted()

Injecting the policy into the SDK

We now need to hook up MyUserConversion to the Scrivito SDK. This is done by defining the required callbacks in the Configuration.

The SDK calls the editing_auth callback inside the initializer (i.e. config/initializers/scrivito.rb) to determine the user from the session, create the application-specific user instance, and convert it to a Scrivito user. The find_user callback should also be provided. It lets Scrivito find your users by their ID.

Scrivito.configure do |config| # The 'editing_auth' callback expects you to return a 'Scrivito::User' # (or nil, if the request does not originate from a user). config.editing_auth do |env| user_data = env['rack.session'][:my_user] if user_data # Note: avoid requests to the user back end in the `editing_auth` callback # as it is evaluated on every request to your application! my_user = MyUser.build_from_cached_data(user_data) MyUserConversion.to_scrivito_user(my_user) end end config.find_user do |user_id| MyUserConversion.to_scrivito_user(MyUser.find(user_id)) end # … The rest of the configuration does not involve user-related functionality end

That's it! You've successfully integrated Scrivito with the user back end of your application.