I know this sounds like a really, really simple use case and I'm hoping that it is, but I swear I've looked all over the place and haven't found any mention of any way - not even the best way - of doing this.
I'm brand-spanking new to Ruby, Rails and everything surrounding either (which may explain a lot). The dummy app that I'm using as my learning tool requires authentication in order to do almost anything meaningful, so I chose to start by solving that problem. I've installed the AuthLogic gem and have it working nicely to the extent that is covered by the intro documentation and Railscast, but now that I can register, login and logout...I need to do something with it.
As an example, I need to create a page where users can upload images. I'm planning to have an ImagesController with an upload action method, but I want that only accessible to logged in users. I suppose that in every restricted action I could add code to redirect if there's no current_user, but that seems really verbose.
Is there a better way of doing this that allows me to define or identify restricted areas and handle the authentication check in one place?
Make sure you have these methods in your application_controller.rb
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to new_user_session_url
return false
end
end
Then in your controllers you can use a before filter to limit access to pages
class ExamplesController < ActionController::Base
before_filter :require_user, :only => :private
def public
// some public stuff
end
def private
// some protected stuff
end
end
before_filter is your friend here. You define a require_authentication function that returns false if there is no valid session and then set it up as a before_filter in the controllers and actions to your liking.
Take a look at the Authlogic Sample application, which defines some filters in the application_controller.rb and then uses it where needed (for example here, where you need to be logged to destroy your account, and not logged to create a new one.
You will need to use a before_filter on your page so that only logged in users can see it. If you want a running example of how Authlogic should be used (including the before_filter stuff), you can check out the Authlogic Exmaple from Github.
You have the entire code Gist available here at Github. Its roughly 360 lines of code. Inclusive of steps.
http://gist.github.com/96556.txt
Related
In one of my controllers I have this method:
def method_name
if current_user
#model = Model.find(params[:id])
if #model.destroy
flash.alert = 'Model deleted successfully'
redirect_to models_path
end
end
end
I check if there is a current_user assigned by devise before giving the ability for the #model to be deleted. Is this safe and sufficient in terms of security?
What I really do is just checking if current_user exists. So is there a way that somebody can "trick" the system that current_user does exist and as a result be able to trigger the commands included in the method?
You will get a spectrum of answers in this. But if you want the user to be logged in then just do this at the top of your controller:
before_filter :authenticate_user!
That is provided by devise and ensures that there is a logged in user before allowing any controller actions.
If you have simple authorization then yes, most likely though you are going to want to make sure that the user has the authorization to delete the object. You can do that several ways. My favorite one right now is the Pundit gem.
You could also just check that the user owns the object in order to be able to delete it. That code would look something like this
#model = Model.find(params[:id)
if current_user.id == #model.user_id
# Rest of your destroy code
end
I'm sort of new to rails, what I want to to is protect users profile
what I mean is if user 1 login and go to edit his profile he can, but also if he change on the url to user to # 2 they can also change their information
localhost:3000/users/2/edit
I'm a little lost, any help will be greatly appreciated, or suggestions of good books/blogs
As part of authentication add a session variable, session[:user_id] = User.Authenticate(params[:user][:username], params[:user][:password) (this is the general pattern, you need to add another resource for authentication).
Then add a before_filter function to your controller and check if session[:user_id] == params[:id]. Look at it here: before_filter
The Rails Security Guide is probably a good place to start
Just in case this is useful to someone, something that I came across when testing my app was although users that hadn't signed in couldn't access restricted content, once a user was signed in, they could change the url to a another users id, eg.
/users/3 and it would then show that users home page. So any user could look at any other user, which wasn't what I wanted.
To get around this, I changed the user controller to secure against this:
class UsersController < ApplicationController
#first call the correct_user function before allowing the show action to proceed
before_filter :correct_user, only: [:show]
...
def show
#do whatever here
end
...
private
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
I'm trying to set the current user into a variable to display "Logged in as Joe" on every page. Not really sure where to begin...
Any quick tips? Specifically, what file should something like this go in...
My current user can be defined as (I think): User.find_by_id(session[:user_id])
TY :)
You might want to use something like Authlogic or Devise to handle this rather than rolling your own auth system, especially when you aren't very familiar with the design patterns common in Rails applications.
That said, if you want to do what you're asking in the question, you should probably define a method in your ApplicationController like so:
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
You inherit from your ApplicationController on all of your regular controllers, so they all have access to the current_user method. Also, you might want access to the method as a helper in your views. Rails takes care of you with that too (also in your ApplicationController):
helper_method :current_user
def current_user ...
Note: If you use the find_by_x methods they will raise an ActiveRecord::RecordNotFound error if nothing is returned. You probably don't want that, but you might want something to prevent non-users from accessing user only resources, and again, Rails has you covered:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
before_filter :require_user
private
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
def require_user
unless current_user
flash[:notice] = "You must be logged in to access this page"
redirect_to new_session_url
return false
end
end
end
Cheers!
It belongs in your controllers.
All your controllers inheirit from Application Controller for exactly this reason. Create a method in your Application Controller that returns whatever you need and then you can access it in any of your other controllers.
I am fairly new to Ruby On Rails and right now I am doing a simple app. In this app a user can create many items and I use devise for authentication. Ofcourse I want to make sure that you are the owner in order to delete items (Teams, Players etc) and the way I do it now is:
def destroy
#team = Team.find(params[:id])
if current_user.id == #team.user_id
#team.destroy
redirect_to(teams_url, :notice => 'The team was deleted.')
else
redirect_to root_path
end
end
Is this the best way? I was thinking about putting a method in the model but I am not sure I can access current_user from there. I was also thinking about a before_filer, something like:
before_filter :check_ownership, :only => [:destroy, :update]
I that case and if I want to code only one method for all objects (all objects this relates to have a "user_id"-field)
In my application controller I put:
before_filter :authorize
def authorize
false # Or use code here to check if user is admin or not
end
Then I override the authorize method in my actual controller to allow access to various actions.
You're looking for an authorization solution on top of your authentication (devise)
You can't access current user in the model no. I've had a fair amount of success using Makandra's Aegis for authorization. It allows you to create roles specify permissions attributed to each role. The docs are pretty good and I know it works fine with Devise, it's pretty agnostic that way, I've also used it with Clearance. It also passes an implicit "current_user" to your permissions so you can specify and check that your current user can take appropriate actions.
I'm trying to get the permit method to work using the rails-authorization-plugin and authlogic, and I keep running into this error:
When I try:
class ApplicationController < ActionController::Base
...
before_filter permit 'admin'
...
I get this:
Authorization::CannotObtainUserObject in HomeController#index
Couldn't find #current_user or #user, and nothing appropriate found in hash
Now I do have my current_user method setup, and it works, because I used it just about everywhere else in my app:
class ApplicationController < ActionController::Base
...
helper_method :current_user
private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
...
I also know that I have users with the appropriate roles in my database, because this method works:
def require_admin
unless current_user.is_admin? || current_user.is_root?
flash[:warning] = 'You are not an administrator and cannot access this page.'
redirect_to root_path
end
end
I can make everything work if I just check on the user level using this:
before_filter :require_admin, :only => 'index'
... but shouldn't I be able to the same thing effectively with permit and permit??
Any help would be much appreciated. Let me know if you need to see more code and I'll be happy to post it. There really is nothing on Google that I can make heads-or-tails of regarding getting these two systems to work with each other.
Okay, I think I figured it out.
As Jared correctly pointed out, the proper usage is
permit 'admin'
(Not as part of a before_filter).
HOWEVER...
... the default :get_user_method is set to #current_user, which is what the acts_as_authenticated plugin uses. I, as noted earlier, am using AuthLogic, in where I have the method defined as current_user (without the pound sign).
So, I had tried the following:
permit 'admin', :get_user_method => current_user
Only to be greeted by a nice error message explaining that I had no such variable or method. What I was missing, however, is that the hash option takes a string, not a direct call to the method!! (stupid mistake, I know!)
So
permit 'admin', :get_user_method => 'current_user'
... seems to work for me.
I love Ruby and Rails, but sometimes its simplicity can be a curse of its own; I always get owned by the simple things. :)
You are using the plugin incorrectly. It should not be placed in a before filter.
On the global level, you simply declare:
permit 'admin'
That's it.
All of your actions will look for a current_user or #user object and redirect to the login page if not.
On a per-action level, you use it as a block:
def index
permit 'admin' do
#some_models = SomeModel.all
end
end