Where to override current_user helper method of devise gem - ruby-on-rails-3

How can i override current_user of devise gem.
Actually I need to add web services for mobile-app.
Currently devise is managing session and 'current_user' for web-application.
Now Mobile app will send user_id to the server. I need to override current user like this
def current_user
if params[:user_id].blank?
current_user
else
User.find(params[:user_id])
end
end
Should I need to modify devise gem as plugin ? or something else ?
Kindly explain in detail as I am new in rails.
Kind regards,

According to the module Devise::Controllers::Helpers, current_user (together with all other devise helpers) is added to ApplicationController, which means that you can override it in this way:
# in application_controller.rb
def devise_current_user
#devise_current_user ||= warden.authenticate(scope: :user)
end
def current_user
if params[:user_id].blank?
devise_current_user
else
User.find(params[:user_id])
end
end

The other answer suggesting aliasing the method is actually not the best solution. Doug English's comment is the best solution:
# In ApplicationHelper
def devise_current_user
#devise_current_user ||= warden.authenticate(:scope => :user)
end
Here's the reason:
Suppose you're including your ApplicationHelper in your controller. If you need a method in your ApplicationHelper that relies on devise_current_user, given the alias solution inside the controller, you're out of luck.
But with the explicit method definition above, you can move the definition to the helper and call it from other methods and you still get to include it in the controller.
This is useful, for example, when you're developing a user impersonation solution and you need to show two different users in the view, one for the real admin (devise_current_user) and the other, the impersonated user (current_user).

Limbo-Peng's answer is great, but can be improved a little to make sure only admins can do this:
You'll need to also define a is_admin? method or is_admin attribute on the User class.
You may also want to use a different key than user_id, so it will never conflict with your regular parameters.
# to impersonate another user, e.g. for customer support
# only admins can do this..
#
alias_method :devise_current_user, :current_user
def current_user
if ! params[:user_id].blank? \
&& devise_current_user && devise_current_user.is_admin?
User.find(params[:user_id])
else
devise_current_user
end
end

Assuming we can trust our session data (which relies on whether you put user input in there without proper authorization or not), this might work as a Concern:
module Concerns
module ControllerWithImpersonation
extend ActiveSupport::Concern
included do
helper_method :devise_current_user
end
def current_user
if session[:impersonated_user_id].blank?
devise_current_user
else
User.find(session[:impersonated_user_id])
end
end
def devise_current_user
#devise_current_user ||= warden.authenticate(:scope => :user)
end
end
end
I'm using this in a project for now.
A minor question (in the answer, sorry) ... should I be aware of any changes in Devise or Warden that make devise_current_user above outdated?

Related

How to apply abilities to a non-restful controller in cancan

I'm new to rails and for the life of me I don't "get" cancan.
I've read this tutorial but can't figure out how to apply instructions to my situation.
In the cancan wiki there is:
an AdminController
a roll_logs action
In the ability class is says:
can :roll, :logs if user.admin?
I don't get what the :roll and :logs symbols have to do with the controller and the action?
All I want to do is say, if a user is an admin, give them access to the AdminController actions, otherwise don't, is this possible?
Yes this is possible.
The statement
can :roll, :logs if user.admmin?
means that when calling authorize! :roll, :logs an unauthorized exception gets thrown if the user isn't an admin.
So it doesn't have anything to do with a controller or an action, untill you make it so.
If you have a logs_controller for example with an action roll you could do something like this.
class LogsController < ApplicationController
def roll
authorize! :roll, :logs
# Rest of the roll functionality.
end
So in your example, you want to give users who are admin permission to access all admin controller actions.
You can achieve this like this.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can(:manage, :admin) if user.admin?
end
end
admin_controller.rb
class AdminController < ApplicationController
authorize_resource :class => false
def foo
end
def bar
end
end
This will make sure that only admins can access the foo and bar actions of the admin_controller.
The :class => false statement means that you are not authorizing a resource, which is what we want since you are not for example authorizing a certain post or comment. You are just authorizing actions on a controller.

Devise + Declarative_authorization + role_model + different users model name : undefined method `current_user'

I do have this famous error : "undefined method `current_user'" with declarative authorization, though I set up this variable in the application_controller.rb :
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_current_user
protected
def set_current_user
Authorization.current_user = current_admin_utilisateur
end
end
I'm using a table called "admin_utilisateurs" instead of "users". Which was activated in Devise with : "rails generate devise admin_utilisateur"
Devise is working great.
For info, I customized my users table (admin_utilisateurs) with "roles_model" gem, So that I do have an attribut roles_mask that allows me to manage different roles while providing a role_symbols method for declarative authorization.
The problem is now that I got this strange error though the Authorization.current_user is set by the application_controller.rb.
This is the begning of one my resource controllers that procude the error :
class PubResponsablesController < ApplicationController
before_filter :authenticate_admin_utilisateur!
filter_resource_access
...
end
I search by google for this error, but none of the results provide a working solution.
Could anybody help me on this ?
Many Thanks
Ok this is the final answer.
I modified my app/controller/application_controller.rb because I don't use the #current_user instance variable in the views :
class ApplicationController < ActionController::Base
protect_from_forgery
# This is mandatory if you want to secure as well your app/models
before_filter :set_current_user
# This method is required by declarative_authorization on every controller
# that is using filter_resource_access (or any other declarative_auth.. mechanism)
def current_user
current_admin_utilisateur
end
protected
def set_current_user
Authorization.current_user = current_admin_utilisateur
end
end
As I said I'm using the following gem in collaboration :
gem devise for the authentication
The user-model-name is "admin_utilisateur" instead of "user", but it could have been : account, member, group or what you need.
gem role_model to provide a brillant role method "role_symbols" to my user model
*The method role_symbols was returning a "Set" subclass instead of an "Array" but after quick post on Github, the developer (martinrehfeld) fixed this compatibility issue in a lightning matter of minutes. Great !*
gem declarative_authorization to provide access management based on roles.
My will to use a different model name than "user" is confirmed to work by the following post.
The only thing that declarative_authorization needs is the current_user method on each controller. As I'm using a different model name with Devise (such as admin_utilisateur, account, member, ...) the helper created by devise have a different name. Instead of current_user, it is current_admin_utilisateur (or current_account, current_member). So I have to create my own current_user method.
The role_symbols method required by declarative_authorization is provided by role_model gem.
I hope this will help other developer cause I spent two days to sort out how all this fabric works together. Devise took me even more with routing issues.
My few cents to RoRrrr ;-)
Ok I managed to solve this error by modifying my app/controller/application_controller.rb :
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_current_user
def current_user
#current_user = current_admin_utilisateur
end
protected
def set_current_user
Authorization.current_user = current_admin_utilisateur
end
end
I just created my own current_user method that create an instance variable #current_user. This one is initialized with the value of current_admin_utilisateur which is based on Devise helpers and my customized user model called admin_utilisateur. (my model could as well be called acount, member, or whatever...)
I placed my current_user method in application_controller in order that it to be available in every controller of my application.
Now, I'm getting another error :
User.role_symbols doesn't return an Array of Symbols (#<RoleModel::Roles: {:developer, :admin, :coordinator, :manager, :assistant, :distributor, :exporter, :historian}>)
I don't understand because the roles_model gem provide an alias method 'role_symbols' to the admin_utilisateur model.

Ruby on Rails – CanCan ability to only let an admin view published blog posts

tl;dr
I use CanCan for authorization in a single-author blog. I want non-admin users to not be able to view unpublished posts. The following does not do the trick:
can :read, Post do |post|
post.published_at && post.published_at <= Time.zone.now
end
Why doesn't it work, and what can I do to make it work?
Thanks. ;-)
The long version
Hello World,
I have a single-user blogging application and use CanCan for authorization purposes. I want administrators (user.admin? # => true) to be able to do whatever they wish with everything (they are administrators after all…). I also want regular users (both those who are logged in, but does not have the admin role, and those who are not logged in) to be able to view blog posts that have been published. I do not want them to see those that are not published.
Blog posts (of the model Post) each have an attribute called published_at (which is a DateTime and nil by default). Needless to say: when published_at is nil, the post is not published, otherwise it is published at the set date and time.
I have the following in my Ability class:
class Ability
include CanCan::Ability
def initialize user
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Post do |post|
post.published_at && post.published_at <= Time.zone.now
end
end
end
end
However, this does not seem to work as I intend it to. I have read on the CanCan wiki that this might not always work. However, I believe it should work in my case here, as I do have an instance of the Post model called #post in my PostsController#show action:
class PostsController < ApplicationController
authorize_resource
respond_to :html, :json
# other actions omitted ...
def show
#post = Post.find params[:id]
respond_with #post
end
# other actions omitted ...
end
Even with this code I am able to visit the blog post through the show action and view. I have also tried removing the authorize_resource call from the PostsController, realizing it might override some abilities or something, but it didn't help.
I have figured out a temporary solution, although I find it ugly and really want to utilize the CanCan abilities. My ugly temporary solution checks internally in the PostsController#show if the user has access to view the resource:
def show
#post = Post.find params[:id]
unless #post.published_at
raise CanCan::AccessDenied unless current_user && current_user.admin?
end
respond_with #post
end
As I said, this works. But I don't really want to go with this solution, as I believe there's a better way of doing this as a CanCan ability.
I'd much appreciate an explanation of why my approach does not work as well as a good solution to the problem. Thanks in advance. :-)
At the point where authorize_resource is being called (before_filter) you don't have a post object to authorize.
Assuming CanCan 1.6 or later, try this..
In your Post model
class Post < ActiveRecord::Base
scope :published, lambda { where('published_at IS NOT NULL AND published_at <= ?', Time.zone.now) }
# the rest of your model code
end
In your Ability model
class Ability
include CanCan::Ability
def initialize user
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Post, Post.published do |post|
post.published_at && post.published_at <= Time.zone.now
end
end
end
end
In your controller
class PostsController < ApplicationController
load_and_authorize_resource
respond_to :html, :json
# other actions omitted ...
def show
respond_with #post
end
end

Can I use cancan with many resources?

I use devise and created two models, Doctor and Patient.
When I write load_and_authorize_resource cancan gives an exception
undefined local variable or method `current_user
How can I use cancan for both of them?
There is a method called current_ability in the Application Controller, you can simply overwrite it.
Changing current_ability documentation
It will look something like this
def current_ability
#current_ability ||= Ability.new(current_user)
end
But you may change it to
def current_ability
if current_doctor
#current_ability ||= Ability.new(current_doctor)
else
#current_ability ||= Ability.new(current_patient)
end
end
That will allow the initialize method in the app/models/ability.rb to recieve either the current_patient or the current_doctor depending on the case.
You could customize it to look something like this
class Ability
include CanCan::Ability
def initialize(resource)
if resource.class == Patient
can, Read Something
can, Edit Other
.
.
.
else
can, Read Something
can, Edit Other
.
.
.
end
.
.
.
I hope this help, you can also see the cancan Full doc
You can find a complete configuration of Devise and Cancan here: Part 1 Part 2

Where to put business logic that requires the current_user to be known? (Rails)

I have a model (say Car) in which a method needs access to the current_user to determine if the user is allowed to perform the things that the method does.
For example, a method might want to do these things:
Check that current_user owns this object
Check that the object status == 1 (Active)
Check that a related object exists and it's X field is not NULL
I need this business logic to be in the model, not in the controller, so that it's the one place where my business logic will be. The method might get called from places other than a controller.
I know that there are gems like cancan, declarative_authorization etc. but they seem to be overkill for what I need to do. And also, accessing current_user in a model is not considered the "right way".
Then, how do I make that check in the model but still feel "clean"?
I have experienced a situation where "current_user" needs be tightly connected to a model, but I handled it all in the Controller and it works pretty well. Here are some examples:
My model is "Photos". Photos are owned by users, and how people interact with photos is obviously tightly related to whether or not they own the photo.
In the show action I need to load either the existing rating a user has given to a photo (so they can edit it) or allow them to create a new one:
def show
#photo = Photo.find(params[:id])
if user_signed_in?
if #rating = current_user.ratings.find_by_photo_id(params[:id])
#rating
#current_user_rating = #rating.value
else
#rating = current_user.ratings.new
#current_user_rating = "n/a"
end
end
end
When people create photos I want them to be automatically assigned to the current user.
def new
#photo = Photo.new
end
def create
#photo = current_user.photos.create(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.created')
else
render 'new'
end
end
Only the owners of a photo can change them:
def edit
#photo = Photo.find(params[:id])
if #photo.user == current_user
render 'edit'
else
redirect_to user_path(current_user), :alert => t('application.error.unauthorized')
end
end
def update
#photo = current_user.photos.find_by_id(params[:id])
#photo.update_attributes(params[:photo])
if #photo.save
redirect_to user_path(current_user), :notice => t('photo.notice.updated')
else
render 'edit'
end
end
This approach is based on the constraints that a "current_user" object is tied to the session, which only the controller knows about. So, in short, I have yet to find a good way to integrate "current_user" into a model, but I've been able to find (I think) pretty clean ways to tie the model and controller together so that this can be provided by the controller.
One fairly simple solution to most problems, if your controller is starting to get messy, would be to take a chunk of logic and define as a method in the model, but require one argument = a user object. Then you can just feed "current_user" to that method from your controller and the model handles the rest.
Good luck! Also, if anyone else has any better ideas for this, I'd love to hear them!
Handle auth'ing in a Controller.
Example: Putting auth logic in parent ApplicationController.
class ApplicationController < ActionController::Base
protect_from_forgery
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
# Filter method to enforce a login requirement
# Apply as a before_filter on any controller you want to protect
def authenticate
logged_in? ? true : access_denied
end
# Predicate method to test for a logged in user
def logged_in?
current_user.is_a? User
end
# Make logged_in? available in templates as a helper
helper_method :logged_in?
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
end
Now that current_user is an accessor to the logged in user and you can access it in any controller, you can do your authorization logic in the appropriate controller before you do anything with the model.
Your right, though. Models don't care about authorization or who is accessing them.