module Scrivito # @api public class User # Valid action verbs for the explicit rules. # @api public VERBS = [ :create, :delete, :invite_to, :publish, :read, :write, ].freeze class << self # Defines a new user. # @api public # @param [String] id the unique, unalterable id of the user. # The user id is used to associate the user with the corresponding CMS resources. # It will be persisted in the CMS. # @raise [Scrivito::ScrivitoError] if id is blank # @raise [Scrivito::ScrivitoError] if id is more than 64 characters long # @yieldparam [Scrivito::UserDefinition] user object to define rules on # @see Scrivito::UserDefinition#can_always # @see Scrivito::UserDefinition#can_never # @see Scrivito::UserDefinition#description # @see Scrivito::UserDefinition#restrict_obj_publish # @see Scrivito::UserDefinition#suggest_users # @example # Scrivito::User.define('alice') do |user| # user.description { 'Alice Almighty' } # user.can_always(:read, :workspace) # user.can_always(:write, :workspace) # user.can_always(:publish, :workspace, 'You can always publish workspaces.') # end # # Scrivito::User.define('bob') do |user| # user.description { 'Bob Doe' } # user.can_never(:create, :workspace, 'You are not allowed to create workspaces.') # user.can_always(:read, :workspace) # user.restrict_obj_publish(using: :_obj_class) do |obj_class| # if obj_class.name == 'BlogPost' # false # else # 'You are not allowed to publish blog posts.' # end # end # end def define(id, &block) assert_valid_id(id) define_user(id, &block) end # # Returns an anonymous system user, who can always create workspaces, can always read, write, # publish, delete and invite to any workspace. # # @api public # @return [Scrivito::User] the system user # def system_user define_user { |user| user.is_admin! } end def unknown_user(id) new(id: id, explicit_rules: {}) end def find(id) if Configuration.find_user_proc user = Scrivito::Configuration.find_user_proc.call(id) assert_valid_user(user) user end end private def define_user(id = nil) user_definition = UserDefinition.new(id) yield user_definition if block_given? user_definition.user end def assert_valid_id(id) raise ScrivitoError.new('User id can not be blank') if id.blank? raise ScrivitoError.new('User id is too long (max length 64)') if id.length > 64 end def assert_valid_user(user) unless user.is_a?(User) || user.nil? raise ScrivitoError.new("Expected an instance of #{self} or nil, but got #{user.inspect}") end end end attr_reader :id, :explicit_rules, :description_proc, :suggest_users_proc, :restriction_set def initialize(options) @id = options[:id] @explicit_rules = options[:explicit_rules] @description_proc = options[:description_proc] @suggest_users_proc = options[:suggest_users_proc] @restriction_set = options[:restriction_set] @explicit_rules.each_key { |rule| assert_valid_verb(rule.second) } end def can?(verb, workspace) assert_valid_verb(verb) verb == :read && workspace.published? || can_always?(verb, :workspace) || verb != :create && owner_of?(workspace) && !can_never?(verb, :workspace) end def can_always?(verb, subject) assert_valid_verb(verb) @explicit_rules.has_key?([:can_always, verb, subject]) end def can_never?(verb, subject) assert_valid_verb(verb) @explicit_rules.has_key?([:can_never, verb, subject]) end def owner_of?(workspace) membership = workspace.memberships[self] membership ? membership.role == 'owner' : false end # Verfies if the User is able to publish changes to a certain {BasicObj Obj} # # @api public # @param [BasicObj] obj the obj that should be published # @return [Boolean] true if the user is allowed to publish otherwise false def can_publish?(obj) restriction_messages_for(obj).empty? end # Checks if the User is able to publish changes and returns the message # specified in a {UserDefinition#restrict_obj_publish} callback if they are not # If the user can publish the obj an empty array is returned # # @api public # @param [BasicObj] obj the obj that should be published # @return [Array] Hints why the user can't publish def restriction_messages_for(obj) assert_restrictions_applicable(obj) return [] if can_always?(:publish, :workspace) if obj.modification == Modification::EDITED base_revision_obj = obj.in_revision(obj.revision.workspace.base_revision) restriction_set.restriction_messages_for(obj) | restriction_set.restriction_messages_for(base_revision_obj) else restriction_set.restriction_messages_for(obj) end end def description @description ||= calculate_description end def suggest_users(input) if suggest_users_proc suggested_users = sandbox_suggest_user_proc(input) suggested_users.nil? ? [] : suggested_users else user = self.class.find(input) user ? [user] : [] end end # Per convention only anonymous admin has a nil id. def system_user? id.nil? end private def calculate_description description_proc ? description_proc.call : id end def assert_valid_verb(verb) raise ScrivitoError.new("Invalid verb '#{verb}'") unless VERBS.include?(verb) end def assert_restrictions_applicable(obj) if obj.revision.base? raise ScrivitoError.new("The revision of #{obj.inspect} may not be a base revision") end end def sandbox_suggest_user_proc(input) suggest_users_proc.call(input) rescue Exception => e Rails.logger.error(%{ Method `suggest_users' of the user "#{id}" raised an error on input "#{input}": #{e} #{e.backtrace.join("\n")} }) nil end end end