module Shamu module Rails # Adds convenience methods to a controller to access services and process # entities in response to common requests. The mixin is automatically added # to all controllers. # # ``` # class UsersController < ApplicationController # service :users_service, Users::UsersService # end # ``` module Controller extend ActiveSupport::Concern included do include Scorpion::Rails::Controller # ActionController::API does not have #helper_method if respond_to?( :helper_method ) helper_method :permit? helper_method :current_user end # In `included` block so that it overrides Scorpion controller method. def prepare_scorpion( scorpion ) super scorpion.prepare do |s| s.hunt_for Shamu::Security::Principal do security_principal end end end end private # The currently logged in user. Must respond to #id when logged in. def current_user_id end # @!visibility public # # @return [Array] the list of services available to the # controller. def services @services ||= self.class.services.map { |n| send n } end # @!visibility public # # @return [Array] the list of services that can # determine permissions for the controller. def secure_services @services ||= services.select { |s| s.respond_to?( :permit? ) } end # @!visibility public # # Checks if the requested behavior is permitted by any one of the # {#secure_services}. # # See {Security::Policy#permit?} for details. # # @overload permit?( action, resource, additional_context = nil ) # @param (see Security::Policy#permit?) # @return (see Security::Policy#permit?) def permit?( *args ) secure_services.any? { |s| s.permit?( *args ) } end # @!visibility public # # Gets the security principal for the current request. # # @return [Shamu::Security::Principal] def security_principal @security_principal ||= begin Shamu::Security::Principal.new \ user_id: current_user_id, remote_ip: remote_ip, elevated: session_elevated? end end # @!visibility public # # @return [String] the IP address that the request originated from. def remote_ip request.env["HTTP_X_REAL_IP"] || request.remote_ip end # @!visibility public # # Override to indicate if the user has offerred their credentials this # session rather than just using a 'remember me' style token # # @return [Boolean] true if the session has been elevated. def session_elevated? end class_methods do # @return [Array] the list of service names on the controller. def services @services ||= begin superclass.respond_to?( :services ) ? superclass.services.dup : [] end end # Define a service dependency on the controller. Each request will get # its own instance of the service. # # @param [Symbol] name of the attribute the service should be accessible # through. # @param [Class] contract the class of the service that should be # resolved at runtime. # @param [Hash] options additional dependency options. See Scorpion # attr_dependency for details. # @option options [Boolean] :lazy true if the service should be resolved # the first time it's used instead of when the controller is # initialized. # @return [name] def service( name, contract, **options, &block ) services << name attr_dependency name, contract, options.merge( private: true ) name end end end end end