I'm trying to figure out before_filters and I was hoping for a little help.
I have a simple blog app, with entries that can be (draft or published) and (public or private). I was wondering how I can do my authentication?
I currently have:
before_filter :authenticate, :except => [ :show ]
So that blocks all the CRUD actions. For show, I need to check that:
If its a draft, that the logged in user owns the entry.
If its private, a user is logged in (in this, all logged in users can see private entries)
I can do it within the action itself, but it seems that the rails way of doing it, is in a before_filter.
Thanks!
Do it in the before filter. If you are using devise then the current_user method is available if a user is logged in. Otherwise replace current_user with the value returned from your authenticate method.
def find_post
#post = Post.find(params[:id])
redirect_to root_path if #post.draft && #post.user != current_user
redirect_to root_path if #post.private && !current_user
end
Related
I'm relatively new to ruby on rails, and so I am now very confused how to setup a user management system for admins.
Besides, users should be able to register themselfs (Devise Registerable).
I have a User controller, using devise_for :users and resources :users .
I can sign_up users, since I used the :registerable, flag in my Users model.
What I want to do now is to add the ability for admins to create users.
If I used the described system, I always get the message 'You are already signed in' when creating a new user through /users/new as admin. This is a message from devise.
So I followed the tutorial www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/ to use cancan to restrict some actions and created a own devise registrations controller like described there.
My cancan ability model looks like this:
if user.has_role?(:admin)
#admin
can :manage, :all
elsif !(user.new_record?)
#logged in but no admin
...
else
# Guest
can :create, User
end
and my registrations controller like in the tutorial
class RegistrationsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def check_permissions
authorize! :create, resource
end
end
I also added the controller to the routes.rb
With this I can create new users with the admin, but if I want to sign_up as not logged in user ("#Guest") I get always the message cancan exception "Access denied". And if I call exception.subject in the CanCan exception handling it is empty.
Can it be, that 'resource' from my controller is not initialized? How can I get the expected behaviour?
Thanks a lot for your help ;-)
Mhm, I figured out, that resource seems to be a method of the devise-controller.
No idea, why it is not called, or is not returning an object
My solution was now (since I can only register users) to change
def check_permissions
authorize! :create, resource
end
to
def check_permissions
authorize! :create, User #could also be User.new
end
And with this it works. But I'm not sure, if it is the best solution ;-)
I'm using Devise to authenticate user via the standard implementation. Once a user logs in, he/she can call an REST API that uses the same controller (and thus also Devise). The controller looks like this:
class FriendController < ApplicationController
before_filter :authenticate_user!
def create
...
end
def destroy
...
end
def index
...
end
end
I got the index action to work with authenticated_user!. In this case, if a user is authenticated, index will return data with 200 Status Code. If a user isn't authenticated, index returns Unauthorized with 40x Status Code.
However, when I'm calling create or destroy via POST and DELETE, it automatically logs me out of the application and returns 40x Unauthorized error. Anyone seen this before? Any ideas?
Here's the route.rb
resources :users do
resources :friends, only: [:index, :friended_me, :create, :destroy]
end
For example, does this needs to be within devise_scope :users block?
I figured out why this is happening. In ApplicationController, if protect_from_forgery is enabled. The API calls for POST/PUT/DELETE will check for auth_token. If it's not provided, it'll fail. However, for GET, if it's not provided, the action will still proceed if user is logged in.
The fix is to use the auth_token generated by resource.reset_authentication_token! after overriding SessionsController#create
I am using "check authorization" in the application controller so every action will require a permission. I'm starting with giving me, the superadmin :=], permissions to manage all. I thought manage all would give me access to the whole app without naming a resource.
user model:
def role?(role)
roles.include? role.to_s
end
application controller:
check_authorization
cancan's ability model:
def initialize(user)
if user.role? :superadmin
can :manage, :all
end
end
error message:
This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check.
Thank you.
As far as I am aware, you're going to need to call authorize_resource in your controller as a before filter so that this works.
Is it possible to define cancan for anonymous users in devise? Where an anonymous user "can" log in, a logged in user "cannot".
Can I define cancan abilities for anonymous users, or should I avoid cancan for anonymous users?
I'm not sure i understand the question completely. You can certainly allow anonymous users to do stuff with cancan
#anyone can see this stuff
can :read, [Contact, Question, UserAction, Provider, Organisation]
#only users that are managers can do this
if user.can_manage_data?
can :manage, [Contact, Organisation, UserAction]
end
But I think you are actually asking 'can you allow anonymous users to do something that signed in users cannot? That's not something I've done, but i presume that you can use
#noone can see this stuff
cannot :read, [Contact, Question, UserAction, Provider, Organisation]
If the question is specifically about logging in though - i think that is a subtly different problem - a user can read an article (or not), but a user is logged in (or not) and in this specific case I don't think cancan is the right answer
I also wondered about this, e.g. how do I only allow account creation when no user is signed in?
Of course one can do this with Devise, by adding before_filters to the controller:
before_filter :authenticate_user!, :except => [:new, :create]
before_filter :user_signed_out, :only => [:new, :create]
then add this at the end of the controller:
private
def user_signed_out
if user_signed_in?
flash[:alert] = "You are already signed in as #{current_user.email}."
redirect_to(root_path)
end
end
However it seems I can accomplish the same thing in the CanCan Ability.rb:
user ||= User.new # guest user (not logged in)
can :create, Account if user.id.nil?
Still pretty new to this so I'd welcome confirmation or correction.
Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.