In Rails 2, how can I prevent a user from just changing the id # and accessing other Objects?
For example :
website.com/users/1231/edit
How do I prevent a user from changing the 1231 and accessing another account?
#user = User.find params[:id]
redirect_to :back unless current_user == #user
Use a before_filter in your controllers.
class Users < ApplicationController
before_filter :require_user, :only => [:show]
private
def require_user
#user = User.find_by_id(params[:id])
redirect_to root_url if #user.nil?
end
end
Use a permissions-checking gem like CanCan or Aegis. Both have conventions that add permissions checking to every method on every controller automatically.
Related
I am currently building a simple web app with Ruby on Rails that allows logged in users to perform CRUD actions to the User model. I would like to add a function where:
Users can select which actions they can perform per controller;
Ex: User A can perform actions a&b in controller A, whereas User B can only perform action B in controller A. These will be editable via the view.
Only authorized users will have access to editing authorization rights of other users. For example, if User A is authorized, then it can change what User B will be able to do, but User B, who is unauthorized, will not be able to change its own, or anyone's performable actions.
I already have my users controller set up with views and a model
class UsersController < ApplicationController
skip_before_action :already_logged_in?
skip_before_action :not_authorized, only: [:index, :show]
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to users_path
else
render :new
end
end
def show
set_user
end
def edit
set_user
end
def update
if set_user.update(user_params)
redirect_to user_path(set_user)
else
render :edit
end
end
def destroy
if current_user.id == set_user.id
set_user.destroy
session[:user_id] = nil
redirect_to root_path
else
set_user.destroy
redirect_to users_path
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
def set_user
#user = User.find(params[:id])
end
end
My sessions controller:
class SessionsController < ApplicationController
skip_before_action :login?, except: [:destroy]
skip_before_action :already_logged_in?, only: [:destroy]
skip_before_action :not_authorized
def new
end
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to user_path(user.id), notice: 'You are now successfully logged in.'
else
flash.now[:alert] = 'Email or Password is Invalid'
render :new
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: 'You have successfully logged out'
end
end
The login/logout function works, no problem there.
I started off by implementing a not_authorized method in the main application controller which by default prevents users from accessing the respective actions if the user role is not equal to 1.
def not_authorized
return if current_user.nil?
redirect_to users_path, notice: 'Not Authorized' unless current_user.role == 1
end
the problem is that I would like to make this editable. So users with role = 1 are able to edit each user's access authorization, if that makes sense.
How would I go about developing this further? I also do not want to use gems, as the sole purpose of this is for me to learn.
Any insights are appreciated. Thank you!
The basics of an authorization system is an exception class:
# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end
And a rescue which will catch when your application raises the error:
class ApplicationController < ActionController::Base
rescue_from 'AuthorizationError', with: :deny_access
private
def deny_access
# see https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses
redirect_to '/somewhere', status: :forbidden
end
end
This avoids repeating the logic all over your controllers while you can still override the deny_access method in subclasses to customize it.
You would then perform authorization checks in your controllers:
class ThingsController
before_action :authorize!, only: [:update, :edit, :destroy]
def create
#thing = current_user.things.new(thing_params)
if #thing.save
redirect_to :thing
else
render :new
end
end
# ...
private
def authorize!
#thing.find(params[:id])
raise AuthorizationError unless #thing.user == current_user || current_user.admin?
end
end
In this pretty typical scenario anybody can create a Thing, but the users can only edit things they have created unless they are admins. "Inlining" everything like this into your controllers can quickly become an unwieldy mess through as the level of complexity grows - which is why gems such as Pundit and CanCanCan extract this out into a separate layer.
Creating a system where the permissions are editable by users of the application is several degrees of magnitude harder to both conceptualize and implement and is really beyond what you should be attempting if you are new to authorization (or Rails). You would need to create a separate table to hold the permissions:
class User < ApplicationRecord
has_many :privileges
end
class Privilege < ApplicationRecord
belongs_to :thing
belongs_to :user
end
class ThingsController
before_action :authorize!, only: [:update, :edit, :destroy]
# ...
private
def authorize!
#thing.find(params[:id])
raise AuthorizationError unless owner? || admin? || privileged?
end
def owner?
#thing.user == current_user
end
def admin?
current_user.admin?
end
def privileged?
current_user.privileges.where(
thing: #thing,
name: params[:action]
)
end
end
This is really a rudimentary Role-based access control system (RBAC).
I am implementing blog app in ruby on rails where I want to restrict normal user( only admin can create) from creating new articles. For this purpose, I have put befor_filter in articles_controller.rb file which is following. I have hided create button from user in UI but still normal user can create new article by typing in address bar of browser.By using below code, normal user can not go on new article page but it gives me "undefined method `is_admin? when i type in address bar. For more info, I have implemented devise for user authentication.
class ArticlesController < ApplicationController
before_filter :is_user_admin, only: [:new, :create]
def is_user_admin
unless current_user.is_admin?
:root
return false
end
end
end
class ArticlesController < ApplicationController
before_filter :is_user_admin, only: [:new, :create]
def is_user_admin
unless current_user.is_admin?
:root
return false
end
end
def index
#articles = Article.all(:order => "created_at DESC")
end
def show
#article = Article.find(params[:id])
end
def new
#article = Article.new
end
def create
#article = Article.new(params[:article])
#article.user_id = current_user.id
#article.save
redirect_to article_path(#article)
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to action: 'index'
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
#article.update_attributes(params[:article])
flash.notice = "Article '#{#article.title}' Updated!"
redirect_to article_path(#article)
end
end
applicaiton_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(user)
if current_user.is_admin?
dashboard_index_path
else
:root
end
end
end
Basically, I want to restrict normal user (other than admin) to create , update or delete articles either from UI(this is done) or typing address in address bar.
I have no idea why i am getting this and what can i do to avoid this. Should i write above method in application_controller.rb file.
You propably want to redirect users to login so they can't access the action in your controller, if they're not admins. Hence, you could do something like this:
def is_user_admin
redirect_to(action: :index) unless current_user.try(:is_admin?)
end
Your current_user is nil apparently.
You should put before_filter :authenticate_user!, :except => [:show, :index] at the top of your controller in order to authenticate user.
Make sure that at least there is an user before checking for the permission. You can do that adding this code to every controller that requires an authentication:
before_filter :authenticate_user!
Doing this, you will always have a current user and hence will be able to check for its permission the way you pointed on your question.
I'm trying to get before_filter to work on the actions that requires the user to be logged in, however something must be wrong because it's not.
I use a helper file called 'session_helper.rb' for login/logout as well as for checking if the user is logged in (signed_in?). That works fine if used inside an action or in the view, however while using it with the before_filer it's not working. If I log out the user and try to access '/projects/new' it's possible to do that, while it shouldn't be.
What am I doing wrong?
project controller:
class ProjectsController < ApplicationController
before_filter :signed_in?, :except => [:index] // <-- doesn't prevent e.g. the action "new" to be executed
def new
#project = Project.new
#users = (current_user.blank? ? User.all : User.find(:all, :conditions => ["id != ?", current_user.id]))
end
def index
#projects = Project.all
if signed_in? // <-- works as it should
#users_projects = Project.where(:user_id => current_user.id)
end
end
... other actions ...
end
sessions_helper.rb
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
end
So, before_filter is a slightly misleading name. It is not really a filter. It isn't that it'll filter out the other actions and prevent them occurring if you return a falsey value, and allow them if you return a truthy one. It's really a way of calling a method before anything else. Think of it as 'before calling the action that the route has triggered, call the following method'.
Indeed, in Rails 4 they are renaming before_filter to before_action and that should alleviate the confusion moving forward.
You're just returning T/F from signed_in? So it's checking that and moving on, as you haven't told it to do anything special based on the results of that check.
So rather than calling signed_in? Something like this would work:
before_filter :authorize, :except => [:index]
def authorize
redirect_to login_url, alert: "Not authorized" if !signed_in?
end
Hop that helps.
I've always seen the before_filter raise an exception or redirect to another page when there is no current login. I am not sure that returning false will prevent the page from rendering.
Hey guys I created some custom authentication thanks to railscasts.com but I'm somewhat stuck as I need to restrict my users from editing other users' profiles.
Here's my authenticate_user and current_user methods:
private
def current_user
#current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
def authenticate_user!
if current_user.nil?
redirect_to login_url, :alert => "You must first log in to access this page"
end
end
Here's the before_filter in my UsersController:
before_filter :authenticate_user!, :only => [:edit, :update, :destroy]`
EDIT: Fixed it thanks to alock27.
I had to edit my users_controller and modify the edit action as follows:
#user = User.find(params[:id]
redirect_to root_url unless current_user == #user
I think you want this:
Adding security on routes in Rails
you need to find the User by :id and check if current_user = #user
You don't have to provide an id for edit, update and destroy: you already have current_user.
Instead of editing #user = User.find(id), edit current_user. Thus, your authentication functions ensure the user will only edit its own profile.
Just as a disclaimer I am new to rails and programming in general so apologize for misunderstanding something obvious.
I have Authlogic with activation up and running. So for my site I would like my users who are logged in to be able to register other users. The new user would pick their login and password through the activation email, but the existing user needs to put them in by email, position and a couple other attributes. I want that to be done by the existing user.
The problem I am running into, if I am logged in and then try and create a new user it just tries to update the existing user and doesn't create a new one. I am not sure if there is some way to fix this by having another session start??? If that is even right/possible I wouldn't know how to go about implementing it.
I realize without knowing fully about my application it may be difficult to answer this, but does this even sound like the right way to go about this? Am I missing something here?
Users Controller:
class UsersController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => [:show, :edit, :update]
def new
#user = User.new
end
def create
#user = User.new
if #user.signup!(params)
#user.deliver_activation_instructions!
flash[:notice] = "Your account has been created. Please check your e-mail for your account activation instructions!"
redirect_to profile_url
else
render :action => :new
end
end
def show
#user = #current_user
end
def edit
#user = #current_user
end
def update
#user = #current_user # makes our views "cleaner" and more consistent
if #user.update_attributes(params[:user])
flash[:notice] = "Account updated!"
redirect_to profile_url
else
render :action => :edit
end
end
end
My User_Session Controller:
class UserSessionsController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
flash[:notice] = "Login successful!"
if #user_session.user.position == 'Battalion Commander' : redirect_to battalion_path(#user_session.user.battalion_id)
else
end
else
render :action => :new
end
end
def destroy
current_user_session.destroy
flash[:notice] = "Logout successful!"
redirect_back_or_default new_user_session_url
end
end
Could you paste your users and users_session controller code?
I suggest using Ryan Bates' nifty_authentication gem. You can use authologic instead of default restful_authentication with
script/generate nifty_authentication --authlogic
Works like a charm.
I've done this with no probs, but know how hard it can be to port yourself to a new language and many new libraries! Hang in there! :)
I think that it might be the before_filter :require_no_user on new and create that blocks you.
What do you mean with this? Does it render the edit view? Or is this a result of a post/put?
it just tries to update the existing
user and doesn't create a new one.