I want to create a default scope to filter all queries depending on the current user. Is it possible to pass the current user as an argument to the default_scope? (I know this can be done with regular scopes) If not, what would be another solution?
Instead of using default_scope which has a few pitfalls, you should consider using a named scope with a lambda. For example scope :by_user, -> (user) { where('user_id = ?', user.id) }
You can then use a before_filter in your controllers to easily use this scope in all the actions you need.
This is also the proper way to do it since you won't have access to helper methods in your model. Your models should never have to worry about session data, either.
Edit: how to use the scope in before_filter inside a controller:
before_filter :set_object, only: [:show, :edit, :update, :destroy]
[the rest of your controller code here]
private
def set_object
#object = Object.by_user(current_user)
end
obviously you'd change this depending on your requirements. Here we're assuming you only need a valid #object depending on current_user inside your show, edit, update, and destroy actions
You cannot pass an argument to a default scope, but you can have a default scope's conditions referencing a proxy hash that executes its procs every time a value is retrieved:
module Proxies
# ProxyHash can be used when there is a need to have procs inside hashes, as it executes all procs before returning the hash
# ==== Example
# default_scope(:conditions => Proxies::ProxyHash.new(:user_id => lambda{Thread.current[:current_user].try(:id)}))
class ProxyHash < ::Hash
instance_methods.each{|m| undef_method m unless m =~ /(^__|^nil\?$|^method_missing$|^object_id$|proxy_|^respond_to\?$|^send$)/}
def [](_key)
call_hash_procs(#target, #original_hash)
ProxyHash.new(#original_hash[_key])
end
# Returns the \target of the proxy, same as +target+.
def proxy_target
#target
end
# Does the proxy or its \target respond to +symbol+?
def respond_to?(*args)
super(*args) || #target.respond_to?(*args)
end
# Returns the target of this proxy, same as +proxy_target+.
def target
#target
end
# Sets the target of this proxy to <tt>\target</tt>.
def target=(target)
#target = target
end
def send(method, *args)
if respond_to?(method)
super
else
#target.send(method, *args)
end
end
def initialize(*_find_args)
#original_hash = _find_args.extract_options!
#target = #original_hash.deep_dup
end
private
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
if #target.respond_to?(method)
call_hash_procs(#target, #original_hash)
#target.send(method, *args, &block)
else
super
end
end
def call_hash_procs(_hash, _original_hash)
_hash.each do |_key, _value|
if _value.is_a?(Hash)
call_hash_procs(_value, _original_hash[_key]) if _original_hash.has_key?(_key)
else
_hash[_key] = _original_hash[_key].call if _original_hash[_key].is_a?(Proc)
end
end
end
end
end
Then in ApplicationController you can use an around_filter to set/unset Thread.current[:current_user] at the begin/end of each request:
class ApplicationController < ActionController::Base
around_filter :set_unset_current_user
protected
def set_unset_current_user
Thread.current[:current_user] = current_user if logged_in?
yield
ensure
Thread.current[:current_user] = nil
end
end
Related
I have a few singular resources in my app, e.g.:
# routes.rb
MySite::Application.routes.draw do
resource :thing
end
# things_controller.rb
class ThingsController < ApplicationController
def edit
load_thing
end
def update
load_thing
if #thing.update_attributes(thing_params)
...
else
...
end
end
private
def load_thing
#thing ||= current_user.thing
end
def thing_params
params.require(:thing).permit(...)
end
end
I'm wondering how to enforce policy scoping using Pundit (before_action :verify_policy_scoped has been set in ApplicationController).
I'm not sure how to form my policy scope for singular resources, i.e.:
# thing_policy.rb
class ThingPolicy < ApplicationPolicy
Scope < Scope
def resolve
# What to do here...
# scope => ?
end
end
end
# things_controller.rb
def load_thing
# ...and what to do here
#thing ||= policy_scope(...)
end
According to Pundit's docs:
...the method resolve...should return some kind of result which can be
iterated over.
However, with singular resources, this iterability clause isn't really valid and there is no AR-style scope as such... just a single record.
Anyone have any suggestions for how to go about this?
Scoping is generally performed on the index action to restrict the base set of records the user can access before filtering is applied. For individual records, you should authorize them instead.
In any case, you are already scoping #thing to current_user
Would suggest to limit before_action :verify_policy_scoped to only: [:index]
I am aware that several gems are made to handle authorization in Rails. But is it really worth it to use these gems for simple access controls ?
I only have a few "roles" in my application, and I feel that a powerful gem would be useless and even slow down the response time.
I have already implemented a solution, but then I took some security classes (:p) and I realized my model was wrong ("Allow by default, then restrict" instead of "Deny by default, then allow").
Now how can I simply implement a "deny by default, allow on specific cases" ?
Basically I'd like to put at the very top of my ApplicationController
class ApplicationController < ApplicationController::Base
before_filter :deny_access
And at the very top of my other controllers :
class some_controller < ApplicationController
before_filter :allow_access_to_[entity/user]
These allow_access_to_ before_filters should do something like skip_before_filter
def allow_access_to_[...]
skip_before_filter(:deny_access) if condition
end
But this doesn't work, because these allow_access before filters are not evaluated before the deny_access before_filter
Any workaround, better solution for this custom implementation of access control ?
EDIT
Many non-RESTful actions
I need per-action access control
undefined method 'skip_before_filter' for #<MyController... why ?
My before_filters can get tricky
before_action :find_project, except: [:index, :new, :create]
before_action(except: [:show, :index, :new, :create]) do |c|
c.restrict_access_to_manager(#project.manager)
end
I would really advise using a proper battle tested gem for authentication & authorisation instead of rolling your own. These gems have enormous test suites and aren't really all that hard to setup.
I've recently implemented an action based authorization using roles with Pundit & Devise
Devise is changeable as long as the gem you are using provides a current_user method if you don't want to further configure pundit.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :rescue_unauthorized
# Lock actions untill authorization is performed
before_action :authorize_user
# Fallback when not authorized
def rescue_unauthorized(exception)
policy_name = exception.policy.class.to_s.underscore
flash[:notice] = t(
"#{policy_name}.#{exception.query}",
scope: "pundit",
default: :default
)
redirect_to(request.referrer || root_path)
end
end
# app/models/user.rb
class User < ActiveRecord::Base
has_many :roles, through: :memberships
def authorized?(action)
claim = String(action)
roles.pluck(:claim).any? { |role_claim| role_claim == claim }
end
end
# app/policies/user_policy.rb => maps to user_controller#actions
class UserPolicy < ApplicationPolicy
class Scope < Scope
attr_reader :user, :scope
# user is automagically set to current_user
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.all
end
end
def index?
# If user has a role which has the claim :view_users
# Allow this user to use the user#index action
#user.authorized? :view_users
end
def new?
#user.authorized? :new_users
end
def edit?
#user.authorized? :edit_users
end
def create?
new?
end
def update?
edit?
end
def destroy?
#user.authorized? :destroy_users
end
end
Long story short:
If you configure pundit to force authorization on each request which is described in detail on the github page, the controller evaluates a policy based on the used controller.
UserController -> UserPolicy
Actions get defined with a question mark, even non restful routes.
def index?
# authorization is done inside the method.
# true = authorization succes
# false = authorization failure
end
This is my solution to action based authorization hope it helps you out.
Optimisations & feedback are welcome !
Rolling your own implementation isn't necessarily bad as long as you're committed to it.
It won't get tested and maintained by the community so you must be willing to maintain it yourself in the long run, and if it compromises security you need to be really sure of what you're doing and take extra care. If you have that covered and the existing alternatives don't really fit your needs, making your own isn't such a bad idea. And generally it's an incredibly good learning experience.
I rolled my own with ActionAccess and I couldn't be happier with the results.
Locked by default aproach:
class ApplicationController < ActionController::Base
lock_access
# ...
end
Per-action access control:
class ArticlesController < ApplicationController
let :admins, :all
let :editors, [:index, :show, :edit, :update]
let :all, [:index, :show]
def index
# ...
end
# ...
end
Really lighweight implementation.
I encourage you not to use it but to check out the source code, it has a fare share of comments and should be a good source of inspiration. ControllerAdditions might be a good place to start.
ActionAccess follows a different approach internally, but you can refactor your answer to mimic it's API with something like this:
module AccessControl
extend ActiveSupport::Concern
included do
before_filter :lock_access
end
module ClassMethods
def lock_access
unless #authorized
# Redirect user...
end
end
def allow_manager_to(actions = [])
prepend_before_action only: actions do
#authorized = true if current_user_is_a_manager?
end
end
end
end
class ApplicationController < ActionController::Base
include AccessControl # Locked by default
# ...
end
class ProjectController < ApplicationController
allow_managers_to [:edit, :update] # Per-action access control
# ...
end
Take this example as pseudo-code, I haven't tested it.
Hope this helps.
I didn't like my previous solution using prepend_before_action, here is a nice implementation using ActionController callbacks
module AccessControl
extend ActiveSupport::Concern
class UnauthorizedException < Exception
end
class_methods do
define_method :access_control do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:access_control, :before, name, options)
end
end
end
included do
define_callbacks :access_control
before_action :deny_by_default
around_action :perform_if_access_granted
def perform_if_access_granted
run_callbacks :access_control do
if #access_denied and not #access_authorized
#request_authentication = true unless user_signed_in?
render(
file: File.join(Rails.root, 'app/views/errors/403.html'),
status: 403,
layout: 'error')
else
yield
end
end
end
def deny_by_default
#access_denied ||= true
end
def allow_access
#access_authorized = true
end
end
end
Then you can add your own allow_access_to_x methods (for example in the same AccessControl concern) :
def allow_access_to_participants_of(project)
return unless user_signed_in?
allow_access if current_user.in?(project.executants)
end
Use it in your controllers the following way (don't forget to include AccessControl in your ApplicationController
class ProjectsController < ApplicationController
access_control(only: [:show, :edit, :update]) do
set_project
allow_access_to_participants_of(#project)
allow_access_to_project_managers
end
def index; ...; end;
def show; ...; end;
def edit; ...; end;
def update; ...; end;
def set_project
#project = Project.find(params[:project_id])
end
end
EDIT : Outdated answer, I have a friendlier implementation that involves using an access_control block
Going with evanbikes suggestion, for now I'll be using prepend_before action. I find it quite simple & flexible, but if I ever realize it's not good enough I will try other things.
Also if you find security issues/other problems with the solution below, please comment and/or downvote. I don't like leaving bad examples in SO.
class ApplicationController < ApplicationController::Base
include AccessControl
before_filter :access_denied
...
My Access Control module
module AccessControl
extend ActiveSupport::Concern
included do
def access_denied(message: nil)
unless #authorized
flash.alert = 'Unauthorized access'
flash.info = "Authorized entities : #{#authorized_entities.join(', '}" if #authorized_entities
render 'static_pages/home', :status => :unauthorized
end
end
def allow_access_to_managers
(#authorized_entities ||= []) << "Project managers"
#authorized = true if manager_logged_in?
end
...
How I use the AC in controllers :
class ProjectController < ApplicationController
# In reverse because `prepend_` is LIFO
prepend_before_action(except: [:show, :index, :new, :create]) do |c|
c.allow_access_to_manager(#manager.administrateur)
end
prepend_before_action :find_manager, except: [:index, :new, :create]
A user can has many profiles. Each profile has its own properties. And the user can change from one profile to another anytime, anywhere.
So I want to set a method or variable available in controllers and views where I can set the user's current_profile (like devise's current_user helper).
We've tried using a ApplicationController private method and a ApplicationHelper method, but it doesn't work when the user's nickname it's not available (is set through a URL param).
This is the AppController method
...
private
def set_profile
if params[:username]
#current_profile ||= Profile.where(username: params[:username]).entries.first
else
nil
end
end
And this is the AppHelper method
def current_profile
unless #current_profile.nil?
#current_profile
end
end
Any idea?
Create a lib (for organization purposes) that extends ActionController::Base and define "set_profile" and "current_profile" there as a helper method, then require it and call it on ApplicationController.
application_controller.rb
require 'auth'
before_filter :set_profile # sets the profile on every request, if any
lib/auth.rb
class ActionController::Base
helper_method :set_profile, :current_profile
protected
def set_profile
if params[:username]
session[:user_profile] = params[:username]
...
end
end
def current_profile
#current_profile ||= if session[:user_profile]
...
else
...
end
end
end
That way you can call current_profile anywhere in your code (view and controllers).
If you have a relation where User has_many :profiles you can add a current:boolean column on profiles. Then:
def set_profile
if params[:profile_id]
#current_profile = Profile.find(params[:profile_id])
#current_profile.current = true
else
nil
end
end
# helper_method
def current_profile
#current_profile
end
#current_profile as memeber variable of ApplicationController is not visible in your helper. You should create accessor method in Appcontroller like:
def current_profile
#current_profile
end
or via
attr_accessor :current_profile
And in helper (make sure that accessor in controller is not private):
def current_profile
controller.current_profile
end
But you also free to define this as helper only, without involving controller at all:
def current_profile
if params[:username]
#current_profile ||= Profile.where(username: params[:username]).first
end
end
This will automagically cache you database query in #current_profile and automagically return nil if there is no param specified. So no need for extra else clause and extra set_... method
The respond_with accepts some parameters, e.g. respond_with(#resource, methods: [:method])
These options should be used in every action. So instead of putting it into every method by hand, is there a possibility to set some default options for just this controller?
The easy and customizable way to do this is by creating a new response method that wraps responds_with.
For example:
class ResourcesController < ApplicationController
def index
#resources = Resource.all
custom_respond_with #resources
end
private
def custom_respond_with(data, options={})
options.reverse_merge!({
# Put your default options here
:methods => [ :method ],
:callback => params[:callback]
})
respond_with data, options
end
end
You could, of course, also overwrite the respond_with completely, however, I find it to be much clearer in the code if you change the name of the method. It also will allow you to use a custom_respond_with in most actions but the standard respond_with in one or two if necessary.
Taking this one step further, if you move the custom_respond_with method to the ApplicationController, you can use it in all of your controllers as necessary.
If you want to specify different default options on a per controller basis, you can do so easily:
class ResourcesController < ApplicationController
def index
custom_respond_with Resource.all
end
private
def custom_respond_options
{ :methods => [ :method ] }
end
end
class ApplicationController < ActionController::Base
protected
def default_custom_respond_options
{}
end
def custom_respond_with(data, options={})
options.reverse_merge! default_custom_respond_options
respond_with data, options
end
end
Suppose I have some logic in a base controller to pass information to the view to build something like a breadcrumb:
class ContextAwareController < ApplicationController
after_filter :build_breadcrumb
def build_breadcumb
#...
end
end
I want this build_breadcrumb method to run after the main controller logic, but before the view is rendered.
The above code runs too late, but a before_filter would be too early.
Can anybody suggest a way to accomplish this without explicitly calling build_breadcumb at the end of each of the actions in the child controllers?
Thanks
I had the same problem and solved it like this:
class ApplicationController < ActionController::Base
def render *args
add_breadcrumbs
super
end
end
There are also some gems to achieve this. One of them is rails3_before_render.
It works similarly to filters, for example:
class PostsController < ApplicationController
before_render :ping, :except => [:destroy]
def index; end
def new; end
def show; end
def destroy; end
private
def ping
Rails.logger.info "Ping-Pong actions"
end
end
(code snipped copied from gem documentation)
I believe rendering starts when render is called, and there's no default way to defer it. Here's one thing you could do:
filters are applied in the same order declared. So make a second after-filter that calls render with an array args stored in a class variable. Then anywhere you would normally call render, set the variable.
If we're overriding render, we're not really using the filter chain at all, so it might be simpler to determine which action we're in using the #_action_name.
StuffController < ApplicationController
def my_filter
# Do the stuff
end
def render(*args)
my_filter if #_action_name == "show"
super
end
end
You can use alias_method_chain like this
alias_method_chain :render, :before_render_action
this will create 2 methods :render_with_before_render_action and :render_without_before_render_action. If you call render, then :render_with_before_render_action will be called. You can override this method
def render_with_before_render_action(*options, &block)
<your code>
render_without_before_render_action(*options, &block)
end
If you don't want your code to be executed and you want to have default render then you should directly call the :render_without_before_render_action
You can do like this to fake a before_render:
class StuffController < ApplicationController
before_filter :my_filter, only: [:index, :show]
def my_filter
#my_filter = true
end
def _my_filter
# Do the actual stuff here
end
def render(*args)
_my_filter if #my_filter
super
end
end
Thanks to #joshua-muheim for the tip about using render