Limit users to only access their profiles using Devise - ruby-on-rails-3

I have currently set my app so that on successful sign in the app redirects the user to their profile at localhost:3000/users/id however if I am the first user id => 1 and type users/2 I have full access to this profile. I have been trying to find how to stop this using devise. I'm pretty new to rails so I'm sure I'm missing something simple, I have used the before_filter :authenticate_user! but this is obviously just checking if a user is signed in, but doesn't limit access to other users' profiles. I have read a bit on CanCan but this seems a bit overkill for what I am trying to achieve. Any pointers much appreciated.
users_controller.rb
class UsersController < ApplicationController
before_filter :authenticate_user!
before_filter :user_authorization
def index
#users = User.all
end
def show
#user = User.find(current_user[:id])
end
private
def user_authorization
redirect_to(root_url) unless current_user.id == params[:id]
end
end
This is being reported from the server:
Started GET "/users/2" for 127.0.0.1 at 2012-06-24 13:00:38 +0200
Processing by UsersController#show as HTML
Parameters: {"id"=>"2"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
Redirected to http://localhost:3000/
Filter chain halted as :user_authorization rendered or redirected
Completed 302 Found in 20ms (ActiveRecord: 0.8ms)

In your controller:
class UsersController < ApplicationController
before_filter :validate_user, :only => :show
def show
#user = User.find(params[:id]
end
def validate_user
redirect_to courses_path unless current_user.id.to_s == params[:id]
end
end
Notice the current_user.id.to_s since current_user.id is an integer and params[:id] is a string.

In general, I'd say there are two approaches to solving this kind of problem:
Rolling your own code and implementing checks in your controllers (or potentially in your model classes), and
using a gem that enforces rules for you.
If you want to role-your-own, the simplest way would be to simply put checks in your controller that makes sure they get redirected if they try to look at a profile that isn't theirs. One way to do this using a before filter is this, though you'd want to adapt it for the behavior that makes sense for your app.
before_filter :validate_user
def validate_user
redirect_to home_path unless current_user and current_user.id == params[:id]
end
If you want to use a gem, then I'd recommend cancan as you've mentioned or another gem called Acts as Tenant. I've seen it used for similar things. But if all you want is to lock down the user profile, adding code to the controller probably works fine.

And voilà:
before_filter :user_authorization
private
def user_authorization
redirect_to(root_url) unless current_user.id == params[:id]
end
current_user is an helper that contains current logged user.

Related

Applying cancan to custom actions

I'm working on a polling app and use Devise and Cancan for authentication and authorization. I have a User model generated by Devise and a Poll model. A poll belongs to a user. My problem is that I have a custom action in my Polls controller and I can't get Cancan to work with that custom action. This is what I tried to do in my code:
config/routes.rb:
match 'users/:user_id/polls' => 'polls#show_user'
Ability.rb:
def initialize(user)
user ||= User.new
if user.is? :admin
can :manage, :all
else # default
can :read, :all
can :manage, Poll, :user_id => user.id
can :show_user, Poll, :user_id => user.id
end # if else admin
end
polls_controller.rb:
class PollsController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource :except => :show_user
def show_user
authorize! :show_user, user
#polls = Poll.find_all_by_user_id(params[:user_id])
render "index"
end
<...>
end
The idea is that a user's polls can be viewed only when the owner of the poll is signed in. However, with this code, when a poll's owner is signed in, that user gets kicked out of that page with a message that says authorization failed. If I remove the line authorize! :show_user, user, then a user who's signed in can view all other user's polls (the authorization doesn't work at all).
Can anyone see what I might be missing? Thanks in advance!
In abiltity.rb, you're verb/noun combination is :show_user and Poll, but in your controller you're using :show_user and user--you would need to use a Poll instead.
If, instead you want to allow the user to view all their own Polls, you might go with something like:
ability.rb:
can :show_polls_for, User, :id => user.id
polls_controller.rb:
def show_user
authorize! :show_polls_for, user
...
end

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

Writing spec for devise RegistrationsController that will make sure users can't change their role - cryptic error message

I have a rails 3.1 app using Devise and CanCan to mange users and roles. I want to make sure that users can update their password, but not their roles. (So ordinary users can't give themselves an admin role, basically). I have overridden the Devise class "RegistrationsController" with some code like this:
def update
# this is my attempt to stop people from updating their roles
# and giving themselves "Admin" priveledges.
params.delete("role_ids")
super
end
I'm hoping this will prevent hackers from updating the "role_ids" field in the user to change their priviledges. (If there is a better way to achieve this, please say!) My problem is I can't seem to write a spec that will test that this code works. My spec looks like this:
require 'spec_helper'
describe RegistrationsController do
before (:each) do
#user = Factory(:user)
sign_in #user
end
it "should update the user attributes but not the roles" do
user_params = {"name" => "new_name", "role_ids" => ["2"],}
put :update, { :id => #user.id, :user => user_params}
#user = User.find(#user.id)
#user.name.should == "new_name"
#user.roles.should be_empty
end
end
The trouble is this test doesn't execute. I get an error message like this:
Failures:
1) RegistrationsController should update the user attributes but not the roles
Failure/Error: put :update, { :id => #user.id, :user => user_params}
AbstractController::ActionNotFound:
Could not find devise mapping for path "/user?id=29&user%5Bname%5D=new_name&user%5Brole_ids%5D%5B%5D=2".
Maybe you forgot to wrap your route inside the scope block? For example:
devise_scope :user do
match "/some/route" => "some_devise_controller"
end
# ./spec/controllers/registrations_controller_spec.rb:13:in `block (2 levels) in <top (required)>'
I don't understand what the error message is asking me to do. My routes seem fine and my application seems to work otherwise. Can anyone help?
Try this in your setup
#request.env["devise.mapping"] = Devise.mappings[:user]
For details see How To: Controllers and Views tests with Rails 3

Rails & Devise: How to authenticate specific user?

I'm using Devise for the first time with rails, and I'm having trouble with one thing:
I used the provided authenticate_user! method in my user's controller to restrict access to pages like so:
before_filter :authenticate_user!, :only => [:edit, :show, :update, :create, :destroy]
But this allows any signed in user to access any other users :edit action, which I want to restrict to only that user. How would I do that?
In your edit method, you need to do a check to see if the user owns the record:
def edit
#record = Record.find(params[:id])
if #record.user == current_user
#record.update_attributes(params[:record])
else
redirect_to root_path
end
end
You should look into Authorization such as CanCan. Or alternatively create a new method like so:
# Create an admin boolean column for your user table.
def authenticate_admin!
authenticate_user! and current_user.admin?
end

has_many association rails

I have a User model and a Job_Category model, the Job_Category model
belongs_to :user
The User model
has_many :job_categories, :dependent => :destroy
I have a Dashboard Controller and am trying to display all the Job_Categories for a specific logged in User.
class DashboardController < ApplicationController
before_filter :authenticate_user!
def index
#user = User.find(params[:id])
#job_categories = #user.job_categories
##job_categories = JobCategory.all
#respond_to do |format|
# format.html # index.html.erb
# format.xml { render :xml => #job_categories }
# end
end
However, when I am trying to display this I get the error 'Couldn't find User without an ID' . I see this in the log:
Processing by DashboardController#index as HTML
User Load (0.3ms) SELECT "users".* FROM "users" WHERE ("users"."id" = 2) LIMIT 1
Completed in 22ms
ActiveRecord::RecordNotFound (Couldn't find User without an ID):
app/controllers/dashboard_controller.rb:9:in `index'
First of all, you cannot try to find by params in console, because there ain't such thing.
Try this in console:
User.find(2)
(..default column where find method looks the value is primary key which I assume is id as usual)
Then another thing, if you're already authenticating user in before_filter, you might already have #current_user or something set, are you using some authentication plugin like devise or something? So there's no need to pass current users id to dashboard controller. Application controller already knows who this user is and your dashboard controller is a subclass of it.
And ensure your job_category table has user_id column too.