InvalidAuthenticityToken with destroy in RSpec - ruby-on-rails

On RSpec, the destroy action fails in ActionController::InvalidAuthenticityToken.
I don't think this is a problem as skip_before_filter :verify_authenticity_token, :only => [:index, :show] in my Controller is enough to fix the problem, but it still bothers me that this has to happen and it seems like it could be a bad security practice.
-
Here's the would-be-failing RSpec test:
require 'spec_helper'
describe "Products" do
subject { page }
...
describe "Destroy" do
before {
FactoryGirl.create(:product)
visit products_path
}
it "should have a destroy link" do
expect { should have_link('Destroy') }
end
it "link should destroy" do
expect do
click_link('Destroy', match: :first)
end.to change(Product, :count).by(-1)
end
end
end
-
And here's the controller:
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
skip_before_filter :verify_authenticity_token, :only => [:destroy] #destroy wasn't working without this
def index
#products = Product.all
end
def show
end
def new
#product = Product.new
end
def edit
end
def create
...
end
def update
...
end
def destroy
#product.destroy
flash[:notice] = "Product was successfully destroyed."
redirect_to products_path
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :description, :image_url, :price)
end
end
-
Ideas?

You are correct, skipping your authenticity token check is a bad idea. At the very least, ensure you are only doing that for your test environment:
skip_before_filter :verify_authenticity_token, :only => [:destroy] if Rails.env.test?
The issue with invalid tokens when you run tests may be the result of running your tests in your development environment.
Ensure your test_helper.rb file is correctly setting the environment:
Rails.env = 'test'
This works better than the ENV['RAILS_ENV'] = 'test' style approach.
Best of luck! Did you happen to find any other resolution?

Related

Rails 6: localhost redirected you too many times

Root page of my app is post's index page
Rails.application.routes.draw do
root 'posts#index'
resources :users
resources :posts
resource :sessions, only: [:new, :create, :destroy]
resources :passwords, only: [:new, :create, :edit, :update]
end
My uses cookies to store user sessions. At the time of login, if the user checks the remember me checkbox, its session is remembered and he is able to access the app after closing the browser without needing to log in again.
Step I follow:
1. Run rails s on the terminal.
Type localhost:3000 on the browsers address bar
The app opens up,I land on posts#index page as it is root path my app follows.
I click on the login link, I fill email, password & check remember me checkbox
I land on user's profile page i.e., users#show page
Terminal till this point
Now I close the browsers window. (I do not log out.)(cookies should rememeber my details)
I again open the browser. type localhost:3000 on browser's addreess bar.
I land 'posts#index' page. But I want to not on the user's profile i.e, users#show page as my details are stored in cookies
But I want to land on the user's profile page.
On the application controller, if do...
before_action :set_cache_buster,:redirect_if_logged_in
def redirect_if_logged_in
redirect_to user_path(current_user) if logged_in?
end
And login if says redirect too many times
Application Controller:
# frozen_string_literal: true
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user,:logged_in?, :logged_in_user,:current_user
before_action :set_cache_buster
def log_in(user)
session[:user_id] = user.id
end
def current_user
#current_user ||= User.where("auth_token =?", cookies[:auth_token]).first if cookies[:auth_token]
end
def logged_in?
!current_user.nil?
end
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to new_sessions_path
end
end
def current_user?(user)
user == current_user
end
private
def set_cache_buster
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
end
User Contoller
# frozen_string_literal: true
require 'resolv-replace'
class UsersController < ApplicationController
before_action :logged_in_user, only: [:show, :edit, :update]
before_action :correct_user, only: [:show, :edit, :update]
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(set_params)
if #user.save
UserNotifierMailer.send_signup_email(#user).deliver
flash[:success] ="Success"
redirect_to new_sessions_path
else
render 'new'
end
end
def show
#user = User.find(params[:id])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(update_params)
redirect_to #user
else
render 'edit'
end
end
private
def set_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def update_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def correct_user
#user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to(#current_user) unless current_user?(#user)
end
end
After I type localhost:3000 redirecting to 'post#index' is fine. but I add a conditional redirect. If cookies are not empty redirect to some other path.
I hope i am able to explain the problem well at least now.
You have redirect_if_logged_in method called each time you are being redirected as your UsersController inherits from ApplicationController. As I understood you just need to move this code to PostsController
Try doing this, add before_action
before_action :check_logged_in
def check_logged_in
unless logged_in?
flash[:danger] = "Please log in."
redirect_to new_sessions_path
else
redirect_to user_path(current_user) if cookies[:auth_token]
end
end
You don't need logged_in_user as helper method after adding this
Hope that helps!

How can I test an expected 404 response on a private resource page out of production?

I have an application where users are authors of objects called "Binders". This is a private resource where one user shouldn't be able to view the binder of another user unless it is being shared. I would like my application to 404 whenever a user tries to do such a thing. Here is what I have tried so far.
class BindersController < ApplicationController
before_action :authenticate_user!
before_action :set_user, only: [:show, :edit, :update, :destroy]
before_action :authenticate_access, only: [:show, :edit, :update, :destroy]
# ...
private
def authenticate_access
if current_user != #binder.user
respond_to do |format|
format.html { head :missing }
format.json { head :missing }
end
end
end
end
My problem is that Rails prefers to 500 in development and test. This makes this impossible to check with a test like the following.
class BindersControllerTest < ActionDispatch::IntegrationTest
include Warden::Test::Helpers
setup do
#alices_binder = binders(:alices_binder)
#alice = users(:alice)
#eve = users(:eve)
end
teardown do
Warden.test_reset!
end
test 'binders#show should be missing if accessed by wrong user'
login_as #eve, scope: :user
get binder_url(#alices_binder)
assert_response :missing
end
end
How can I properly test this behavior out of production?

authenticate_user! failing with guest

I am writing a rails application with devise and testing in rspec. I have an issue where my rspec fails the user_authenticate when the user is not logged in. All of my specs pass except for the last one- the error it gives is
"Failure/Error: get :show, id: course NoMethodError:undefined method `authenticate' for nil:NilClass"
I suspect I am having this issue because I have a before_action :authenticate_user! call and for someone not logged in, it tries to authenticate nil. Is there a way to make it fail gracefully and redirect to user_session? I tried to create an inherited version of authenticate_user to do the redirect, but it does not appear to work. I know this is probably a noob question but I have extensively searched around without any solution. thanks!
This is my controller:
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
def authenticate_user!
if user_signed_in?
super
else
redirect_to user_session
end
end
end
This is my spec:
require 'rails_helper'
RSpec.describe CoursesController, :type => :controller do
describe "user access " do
before(:each) do
#user = create(:user)
#request.env['devise.mapping'] = Devise.mappings[:user]
sign_in :user, #user
end
describe 'GET #index' do
it 'renders the :index view' do
get :index
expect(response).to render_template :index
end
end
describe 'GET #show' do
it 'assigns the requested course to #course' do
course = create(:course)
get :show, id: course
expect(assigns(:course)).to eq course
end
it 'renders the :show template' do
course = create(:course)
get :show, id: course
expect(response).to render_template :show
end
end
end
describe "guest access " do
describe 'GET #index' do
it 'renders the :index view' do
get :index
expect(response).to render_template :index
end
end
describe 'GET #show' do
it 'redirects to the login url' do
course = create(:course)
get :show, id: course
expect(response).to redirect_to 'user_session'
end
end
end
end
It seems that devise does the redirect to "users#sessions" itself when you add :authenticate_user! to the show action for a guest or a user that is not signed in.
Try removing your custom :authenticate_user! method and add "only: [:show]" to your before_action
class CoursesController < ApplicationController
before_action :authenticate_user!, only: [:show], except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
end
Update
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
if user_signed_in?
render :show
else
redirect_to user_session
end
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
end
This isn't a super satisfying result but it appears as if authenticate_user! does not properly work with rspec. When I load the page directly, it correctly redirects to the login page, I am still interested to know what the proper work around is.
Can I have some sort of OR statement where I can first check if a user exists? There must be a standard way to deal with this problem so I can ensure my app is properly redirecting.

Update route in rails doesn't respond well to after_action?

class FrogsController < ApplicationController
before_action :find_frog, only: [:edit, :update, :show, :destroy]
after_action :redirect_home, only: [:update, :create, :destroy]
def index
#frogs = Frog.all
end
def new
#ponds = Pond.all
#frog = Frog.new
end
def create
#frog = Frog.create(frog_params)
end
def edit
#ponds = Pond.all
end
def update
#frog.update_attributes(frog_params)
end
def show
end
def destroy
#frog.destroy
end
private
def find_frog
#frog = Frog.find(params[:id])
end
def frog_params
params.require(:frog).permit(:name, :color, :pond_id)
end
def redirect_home
redirect_to frogs_path
end
end
Hi all. I was wondering if someone could explain to me why the update route in rails can't take my after_action of redirecting (custom made method on the bottom) it home. The error that I get when i include update in the after_action is "Missing template frogs/update".
This is going to cause me to manually add a redirect_to frogs_path inside the update method.
thanks!
The after_action callback is triggered after the action has run its course. You cannot use it to render or redirect. Do that within the action itself by calling the method:
def update
...
redirect_home
end

How to obtain action level protection using Authlogic and STI?

Given that it is well-documented how to use before_filter for a single user classification, I'm having trouble getting action-level protection for multiple user types. Let me explain:
I've got something like this...
class ApplicationController < ActionController::Base
class << self
attr_accessor :standard_actions
end
#standard_actions = [:index, :show, :new, :edit, :create, :update, :destroy]
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
redirect_to home_url
return false
end
end
def require_admin
unless current_user and current_user.is_a?(Administrator)
store_location
redirect_to register_url
return false
end
end
end
And in the GuardiansController I want to only allow the standard actions for Administrator but all other actions should require Guardian. So I tried this...
class GuardiansController < ApplicationController
before_filter :require_admin, :only => ApplicationController::standard_actions
before_filter :require_guardian, :except => ApplicationController::standard_actions
...
end
Which ends up doing a recursive redirection. There must be a better way?
OK, this is another case of not looking carefully and missing something. I inadvertently had setup the route to redirect the user in a recursive way. The above solution works just fine when you set the routes properly:
def require_guardian
unless current_user and current_user.is_a?(Guardian)
store_location
# this route (home_url) sent the user to another controller which ran a before_filter sending them back here again.
# redirect_to home_url
# So I changed it to a neutral route and it works great!
redirect_to register_url
return false
end
end

Resources