module Scrivito # # @api public # class UserDefinition def initialize(user_id) @user_id = user_id @explicit_rules = [] end # # Adds an explicit rule that allows the user to _always_ execute an action. # A rule consists of an action verb, the subject of the action, and an optional message. # # @api public # # @note Usually, the memberships of a workspace decide whether a user is allowed or not to # perform a specific action. This method lets you add an exception to this logic and thus # should be used carefully. # @note By default, all users are allowed to create a new workspace. # @see https://scrivito.com/user-permissions Defining users and their permissions # # @param [Symbol] verb the verb of the action (see {Scrivito::User::VERBS}). # @param [Symbol] subject the subject of the action. Currently, only +:workspace+ is supported. # @param [String] message optional message to be displayed in the UI. # # @raise [Scrivito::ScrivitoError] if the given verb is invalid # @raise [Scrivito::ScrivitoError] if the specified rule conflicts with a # {Scrivito::UserDefinition#can_never} rule. # # @see Scrivito::User::VERBS # @see Scrivito::UserDefinition#can_never # # @example User can _always_ read from, write to, and publish a workspace, ignoring the memberships: # Scrivito::User.define('alice') do |user| # user.can_always(:read, :workspace) # user.can_always(:write, :workspace) # user.can_always(:publish, :workspace, 'You can always publish a workspace.') # end # def can_always(verb, subject, message = nil) assert_no_conflict(:never, verb, subject) @explicit_rules << [:always, verb, subject, message] end # # Adds an explicit rule that forbids the user to execute an action. # A rule consists of an action verb, the subject of the action, and an optional message. # # @api public # # @note Usually, the memberships of a workspace decide whether a user is allowed or not to # perform a specific action. This method lets you add an exception to this logic and thus # should be used carefully. # @note By default, all users are allowed to create a new workspace. Use this method to forbid a # user to create a new workspace. # # @param [Symbol] verb the verb of the action (see {Scrivito::User::VERBS}). # @param [Symbol] subject the subject of the action. Currently, only +:workspace+ is supported. # @param [String] message optional message to be displayed in the UI. # # @raise [Scrivito::ScrivitoError] if the given verb is invalid # @raise [Scrivito::ScrivitoError] if the specified rule conflicts with a # {Scrivito::UserDefinition#can_always} rule. # # @see Scrivito::User::VERBS # @see Scrivito::UserDefinition#can_always # # @example User can _never_ publish a workspace, even if they're a workspace owner: # Scrivito::User.define('alice') do |user| # user.can_never(:publish, :workspace, 'You cannot publish workspaces.') # end # # @example User cannot create a workspace: # Scrivito::User.define('bob') do |user| # user.can_never(:create, :workspace) # end # def can_never(verb, subject, message = nil) assert_no_conflict(:always, verb, subject) @explicit_rules << [:never, verb, subject, message] end # # Enable the user to create workspaces, to read from, write to, publish, delete workspaces, # and to invite others to collaborate on any workspace. # # @api public # def is_admin! @explicit_rules = User::VERBS.map { |verb| [:always, verb, :workspace, nil] } end # # Defines the user description to be displayed in the in-place GUI, e.g. in the workspace menu. # # @api public # # @param [String] description_string the description, defaults to the the user id. # @param [Proc] description_proc proc to calculate the description, defaults to the the user id. # # @note The description is calculated "lazyly". The calculated description is cached. # # @see Scrivito::User.define # # @example Display the user +alice+ as "alice" in the in-place GUI: # alice = Scrivito::User.define('alice') # # @example Display the user +bob+ as "Bob Doe" in the in-place GUI: # bob = Scrivito::User.define('bob') do |user| # user.description('Bob Doe') # end # # bob = Scrivito::User.define('bob') do |user| # user.description do # 'Bob Doe' # end # end # def description(description_string = nil, &description_proc) if description_string description { description_string } else @description_proc = description_proc end end # # Defines the proc for fetching users for autocompletion purposes in the in-place GUI. # User autocompletion is used in the settings dialog of a workspace. # If the proc is not set, {Scrivito::User.find} is used to fetch the suggested users, # assuming the input is part of the user id. # # @api public # # @note Only the first 20 users returned are displayed in the in-place GUI. # @note +suggest_users_proc+ may also be invoked with an empty string. # # @param [Proc] suggest_users_proc proc for fetching users to be suggested in the in-place GUI # @yieldparam [String] input an arbitrary string originating from the user autocompletion input field, # e.g. the first letters of a user name # @yieldreturn [Array] users that were found on account of the given input string # # @example # class MyUserModel # def to_scrivito_user # Scrivito::User.define(id) do |user| # user.suggest_users do |input| # MyUserModel.find_by_prefix(input).map(&:to_scrivito_user) # end # end # end # end # def suggest_users(&suggest_users_proc) @suggest_users_proc = suggest_users_proc end # # Lets you restrict a user's capability to publish a specific CMS object. Each # registered callback may refer to one attribute of an object. Multiple # callbacks are possible. # # @api public # # @note The callback is only executed for {BasicObj Objs} equipped with the attribute # specified using the +:using+ option. It is not executed for a {BasicWidget Widget} # with this attribute. # # @param [Hash] options # @option options [Symbol] :using the attribute you with to refer to in the callback # @yield [attribute] the value of the specified attribute # @yieldreturn [String, false] either return a message for the user or +false+ if # no restriction applies # # @example # class MyUserModel # def to_scrivito_user # Scrivito::User.define(id) do |user| # user.restrict_obj_publish(using: :_path) do |path| # if path.start_with?("/en") # false # else # "You are only allowed to edit the English site." # end # end # # user.restrict_obj_publish(using: :_obj_class) do |obj_class| # if obj_class == 'BlogPost' # false # else # 'You are only allowed to edit Blog Posts.' # end # end # end # end # end # def restrict_obj_publish(options, &block) restriction_set.add(options, &block) end def user User.new( id: @user_id, explicit_rules: @explicit_rules, description_proc: @description_proc, suggest_users_proc: @suggest_users_proc, restriction_set: restriction_set ) end private def restriction_set @restriction_set ||= RestrictionSet.new end def assert_no_conflict(type, verb, subject) if @explicit_rules.detect { |rule| rule.take(3) == [type, verb, subject] } raise ScrivitoError.new("Conflicting rules for verb '#{verb}' and subject '#{subject}'") end end end end