In Ruby on Rails Devise: How to Plug Some Code to Handle the Current User When S/he Logs Out - ruby-on-rails

Basically, I want to log something about the current user when s/he logs out.
I am trying to override after_sign_out_path_for:
def after_sign_out_path_for(user)
# Notice that differently from +after_sign_in_path_for+ this method
# receives a symbol with the scope, and not the resource.
# puts current_user.id
new_user_session_path
end
But the method current_user sometimes it returns nil, and from this ticket (https://github.com/plataformatec/devise/pull/2022), it seems current_user is not available in after_sign_out_path_for.
What should I do? Do I have to override other methods? Like: sign_out_and_redirect? Are the any cleaner way to do?

You could try something like:
before_filter :log_user_logout, only: [:destroy]
and access current_user within that method

Related

How can I use Devise methods in my ConnectionAdapter callback?

I have a rails 5.1 app that's using Devise to handle authentication with my User model. This app has an Oracle database backend that requires setting a system context variable with the logged-in user prior to executing any queries, so I was hoping to do that in the :checkout callback for the ConnectionAdapter.
class ApplicationController < ActionController::Base
before_action :log_user
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.set_callback :checkout, :after do
# Would like to get the logged-in user's username here so I can apply
# it to the oracle sys_context.
# The below throws "undefined method 'user_signed_in?'"
username = current_user.username if user_signed_in?
end
def log_user
# When in this method, user_signed_in? and current_user work fine.
puts "User is #{current_user.username}" if user_signed_in?
end
end
The user_signed_in? method isn't found when run in the :checkout callback block, though it's generally available in the controller. Why?
Also, current_user within the block seems to evaluate to the current_user method defined within the ConnectionAdapter rather than the one defined by Devise. How can I get access to Devise's current_user?
How can I use these Devise-provided methods from within this callback?
You can't use the checkout callback, at the point that it's executed, it has no connection to the controller context. The fact that you've defined it here in your ApplicationController is irrelevant to the context it's actually executed in.
You will need to set the connection option in the before_action so you're running in the controller context. Something like:
before_action :set_user_context
def set_user_context
if current_user
ApplicationRecord.connection.execute "DBMS_SESSION.SET_CONTEXT('whatever', 'goes', 'here', '#{current_user.username}')"
end
end
...or something like that. Note that you might want to add a checkin callback to clear the value when the connection is finished with.
Btw, I answered a nearly identical question a few days ago: https://stackoverflow.com/a/54837596/152786 Different commands though, but might help.

Defining a method on current_user without it throwing a no method error on non logged in users

I made a moderator method thats in the user model
def mod_of_game?(guide_id)
game_mods_relationships.exists?(game_category_id: guide_id)
end
Problem is that whenever the user isn't logged in it just throws a no method error on the page.
I'll be making more user methods in the future and i can only assume i'll come across this problem every time.
I haven't tried it but i guess i could put an if else statement in the method
def mod_of_game?(guide_id)
if current_user.nil?
#empty method
else
game_mods_relationships.exists?(game_category_id: guide_id)
end
But I feel there is a more efficient way that i'm not aware of. I'm Building an app to learn rails better so i guess this is one of the things I just dont know.
The problem is that if no user is logged in, current_user will be nil, not an instance of the User class. So, there is no way to fix this inside the User model, as current_user is not a User if it is nil. Also, current_user is generally not available in the model, just in the controller and view.
What I would recommend is to add a filter in the controller, to make sure that if no user is logged in, the visitor will be redirected to the log in page. This can be done with a before_action filter in the controller, something like:
class YourController < ApplicationController
before_filter :authenticate_user!
...
end
Otherwise you can always check if current_user is nil before calling .mod_of_game?, like so:
current_user.mod_of_game?(#guide) unless current_user.nil?
Try following:
# It will return `nil` if user is not logged in
def mod_of_game?(guide_id)
game_mods_relationships.exists?(game_category_id: guide_id) if current_user
end
Your pattern is wrong.
Calling mod_of_game? is an instance method, which means it's got to be called on an instance of User.
By the nature of current_user, you wouldn't be able to call this method unless the user was logged in, or at least invoked.
You'll have to use all the conditions on the front-end to determine firstly whether current_user exists, and then to call mod_of_game? on it...
<% if user_signed_in? && current_user.mod_of_game?(#guide) %>
--
A much better way would be to either create your own helper method, or to use the .try method:
#app/helpers/application_helper.rb
class ApplicationHelper
def mod? guide
return false unless current_user
current_user.mod_of_game? guide
end
end
This would allow you to call:
<% if mod? #guide %>
... which will return false if the user is not signed in, or the user is not a mod.
The reason the pattern is bad is because you're having to base logic on two conditions: user signed in? AND are they a mod?
What you want is a single point of logic, which will return true or false:
<% if current_user.try(:mod_of_game?, #guide) %>

undefined method `is_admin?' for nil:NilClass :(

I want to restrict the access to my pannel admin (gem active_admin) for admin only.
That's my code
class ApplicationController < ActionController::Base
def authenticate_admin!
unless current_user.is_admin?
flash[:error] = "Access denied"
redirect_to root_path
end
end
and the problem is : undefined method `is_admin?' for nil:NilClass
there is a boolean admin (0 false, true 1) in my DB
I've to define my is_admin?, but i try and he is never found. So where do i have to do that ?
Thx for your help
The issue is in your error message. nil doesn't have the method 'is_admin?'. This means that your current user variable isn't being set. You need to redirect users who are not logged-in to a screen where they can do so. Then direct them either through this authenticate_admin! function either first to redirect all user who are admin to the /admin path or simply push all users to your home page allowing them to click an admin link.
It could be that current_user is being set correctly, but this method is being called when there is no logged in user.
You should use this method in conjunction with another filter which requires the user to be logged in, ie which requires current_user to be defined. This is typically called require_user
Eg, in your application controller (so it gets inherited by all controllers)
before_filter :require_user
protected
def require_user
unless current_user
redirect_to "/" and return
end
end
You then make exceptions for the non-logged-in actions, with skip_before_filter.
Now, you can add authenticate_admin! as a before filter in your admin controller: it will only ever by called when require_user has already been passed, so it should be safe.
Add try: <% if current_user.try(:is_admin?) %>
Try simply add before_action :authenticate_user! (if your user called 'user') before :authentificate_admin! method. After this change your app will redirect non-logged users to login form first and only after that will ask your user 'is he admin?'.

How to validate that the owner of this object is modifying it? (and write a test)

I was attempting to start a test to confirm that a user can only modify an object if current_user.id and model.user_id match.
I feel like this is a validation from the model. So I might write something like:
class UserLocked < ActiveModel::Validator
def validate(record)
unless record.user_id == current_user.id
record.errors[:name] << "Sorry you cannot modify something that is not your's"
end
end
end
Which might be ok... (is there a centralized place I can put this? do I need to do anything special to reference it then?)
Writing a test for that isn't too bad either; however, I also need to prevent the controller from displaying the form to edit form. Should I be creating a separate view or just make it part of the edit page? How can I write a test for this for this in rspec...
I might be over thinking this, but I am trying to figure out what everyone else is doing. An example would be great! I've done this before in other languages/frameworks, but I am trying to "do things the right way."
Thanks!
Authorization belongs in the controller and not in the model. So you could implement a before_filter like this:
class UsersController < ApplicationController
before_filter :correct_user, only: [:edit, :update]
...
private
def correct_user
#user = User.find(params[:id])
redirect_to root_path unless current_user? #user
end
end
Of course you would need some sort a method to detect who the current user is.
You could test this with a request spec, using RSpec & Capybara. The logic is simple: you login with a user and expect that when trying to edit the info of another user you get an error message displayed. Otherwise the relevant form fields should be displayed.
For an example see http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#code:edit_update_wrong_user_tests

Rails 3, how to secure and protect controllers and urls

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

Resources