After following the Hartl Tutorial I'm trying to change the authentication to use the devise gem. My sample application site seems to be working again but some of the specs still fail because some of the routes and user controller actions have changed. So I'm in the process of fixing those and stuck on one that checks to make sure the user can't give themselves admin access.
describe "update user with forbidden attributes", type: request do
FactoryGirl.create(:user)
let(:params) do
{ "user[name]" => "new name",
"user[email]" => user.email,
"user[current_password]" => user.password,
"admin" => true }
end
before do
post user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
patch user_registration_path(user), params
user.reload
end
its(:name) { should eql "new name" } # passes, and should.
its(:admin?) { should be false } # can't get to fail.
specify { expect(response).to be_success } # fails, gets response 406.
end
This test passes, but it passes because I can't get it to fail. I'm trying to do the usual Red-Green-Refactor and I can't make it go red, even if I add admin to the list of devise acceptable parameters. I want to make sure that this would change admin if the permissions were screwed up.
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: devise_controller?
after_action :print_permitted_parameters, if: devise_controller?
def configure_permitted_parameters
...
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:name, :email, :password, :password_confirmation, :current_password, :admin)
end
end
def print_configured_parameters
puts "sign_up: " + devise_parameter_sanitizer.for(:sign_up).join(' ')
#prints "sign_up: email password password_confirmation"
puts "sign_in: " + devise_parameter_sanitizer.for(:sign_in).join(' ')
#prints "sign_in: email password remember_me"
puts "account_update: " + devise_parameter_sanitizer.for(:account_update).join(' ')
#prints "account_update: email password password_confirmation current_password"
end
end
The strange thing is that user's name and email do update, so something is working. But the response I get is always 406 for "Not Acceptable". So my question is why can I not get the admin tests to fail? And are the 406 errors related?
printing the permitted parameters suggests the parameters aren't being configured for any actions, it's just the default list. And I can sign_in with an existing user but if I just click "sign_in" with no fields it complains of an umpermitted parameter: "remember_me" despite that being on the list. Similarly if I try to sign_up a new user, which used to work, it complains that password_confirmation is unpermitted.
Thanks for your help, I appreciate it.
Related
I'm working through Michael Hartl's Rails Tutorial. I've come to Chapter 9, Exercise 1. It asks you to add a test to verify that the admin attribute of the User class is not accessible. Here's the User class with irrelevant portions commented out:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
attr_protected :admin
# before_save methods
# validations
# private methods
end
And here's the test I'm using to validate that the admin attribute is not accessible.
describe User do
before do
#user = User.new(
name: "Example User",
email: "user#example.com",
password: "foobar123",
password_confirmation: "foobar123")
end
subject { #user }
describe "accessible attributes" do
it "should not allow access to admin" do
expect do
#user.admin = true
end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
end
The test fails. It says no errors were raised, in spite of the fact that the admin attribute is protected. How can I get the test to pass?
From the Ruby documentation:
Mass assignment security provides an interface for protecting attributes from end-user assignment.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html
Try this code instead
describe "accesible attributes" do
it "should not allow access to admin" do
expect do
User.new(admin: true)
end.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
As Rails docs claim about attr_protected
Attributes named in this macro are protected from mass-assignment, such as new(attributes), update_attributes(attributes), or attributes=(attributes).
So you can change field manually. 'attr_protected' is only about mass-assignment.
This only works for mass assignments like setting the field from a form submit. Try something like this:
#user.update_attrtibutes(:admin => true)
#user.admin.should be_false
#agaved. This answer may come late and you may already have the answer but I wanted to answer your question, it may help somebody else.
The best way to understand how update_attributes differs from direct assignment
#user.admin = true is to try and do it in your console. If you are following Hartl's tutorial, try the following:
#user = User.first
#user.admin?
=> true
#user.admin = false
=> false
Direct assignment manages to change the value of the attribute admin for user from true to false without raising a Mass Assignment Error. This is because Mass Assignment Errors are raised when you call update_attributes or create a new user User.new using an attribute that is not accessible. In other words, Rails raises mass assignment errors when a user tries to update (attribute_update) or create User.new(admin: true) a new user with attributes that are not accessible to her. In the above case, direct assignment is not using the create or update methods of the user controller.
They are very similar pieces of code since you can use direct assignment to force a change in the admin attribute in the above case using #user.save!(validate: false) directly in IRB but as I said above this does not use the create or update method of your user controller and, hence, it will not throw the error.
I hope that helps, this helped me.
[Spoiler alert: If you are trying to solve the exercises in Hartl's book on your own, I'm pretty sure I'm about to give the answer away. Even though the answer that has been accepted is interesting information, I don't believe it was what Hartl had in mind as that would require knowledge the book has not covered and also does not relate it specifically to updates via web action or use the test he provides.]
I think you might be thinking this exercise is a lot harder than it actually is, if I got it right. First of all, you have misunderstood the hint:
Hint: Your first step should be to add admin to the list of permitted parameters in user_params.
It does not say to change its attr declaration in the class. It says to modify the helper function user_params. So I added it to the list in users_controller.rb:
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation, :admin)
end
Next, I copied the code in Listing 9.48 to the indicated place in spec/requests/user_pages_spec.rb:
require 'spec_helper'
describe "User pages" do
.
.
.
describe "edit" do
.
.
.
describe "forbidden attributes" do
let(:params) do
{ user: { admin: true, password: user.password,
password_confirmation: user.password } }
end
before do
sign_in user, no_capybara: true
patch user_path(user), params
end
specify { expect(user.reload).not_to be_admin }
end
end
end
The test then fails, showing that it is possible to pass in an admin parameter and thus change a normal user to an admin, which is not what you would want to allow:
$ rspec spec
.....................[edited out dots].................................F
Failures:
1) User pages edit forbidden attributes
Failure/Error: specify { expect(user.reload).not_to be_admin }
expected admin? to return false, got true
# ./spec/requests/user_pages_spec.rb:180:in `block (4 levels) in <top (required)>'
Finished in 4.15 seconds
91 examples, 1 failure
Failed examples:
rspec ./spec/requests/user_pages_spec.rb:180 # User pages edit forbidden attributes
Then, to make it impossible to pass in an admin value via a web action, I simply removed :admin from the list of acceptable user_params, undoing the first change:
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
Now the attempt to patch the user with a new admin value fails... and the test for it succeeds, verifying "that the admin attribute isn’t editable through the web."
$ rspec spec
...........................................................................................
Finished in 4.2 seconds
91 examples, 0 failures
Following the hint, I first added :admin to attr_accessible in app/models/user.rb to start with a red.
I then added the test:
describe "admin attribute" do
it "should not be accessible" do
expect do
#user.update_attributes(:admin => true)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
to the spec and got a red.
Removing :admin from user.rb I get a green. So far so good.
What puzzles me is why I should use the sintax:
#user.update_attributes(:admin => true)
instead of #user.admin = true (I checked and in this case it doesn't work).
I have a User model and Authentications model, which is a basic omniauth setup. Essentially, users can sign up through oauth without setting a password.
I have a Authentication.is_destroyable? method that returns true if the user has a password or has more than one authentication. Essentially, this prevents users deleting their one and only way of authentication.
def is_destroyable?
if user.encrypted_password.present? || user.authentications.count > 1
true
else
errors.add :base, 'not allowed'
false
end
end
When testing this in development it works as expected under all conditions. However, my unit tests are failing:
describe "Authentication#is_destroyable?" do
before(:each) do
# This creates a user with no password and a single authentication
#user = FactoryGirl.create(:user_with_oauth)
#auth = #user.authentications.first
end
# This spec passes :)
it "should return false when is users only authentication method" do
#auth.is_destroyable?.should be_false
end
# This FAILS - I have no idea why :(
it "should return true when user has multiple authentications" do
#user.authentications.create FactoryGirl.attributes_for(:authentication, :provider => 'twitter')
#auth.is_destroyable?.should be_true
end
# This FAILS - I have no idea why :(
it "should return true when user has a password" do
#user.update_attributes :password => 'password'
#auth.is_destroyable?.should be_true
end
end
I've spent the best part of 3 hours banging my head against the wall. I can't for the life of me understand why this works when I manually test the functionality (and Cucumber stories pass also testing the functionality), but in rspec the unit tests are failing. Is there something obvious that I'm missing?
Edit
As requested, here's some further detail.
Both failing specs fail with:
Failure/Error: #auth.is_destroyable?.should be_true
expected false to be true
The Factories:
FactoryGirl.define do
factory :user do
username { FactoryGirl.generate(:username) }
name 'Test User'
email { FactoryGirl.generate(:email) }
password 'password'
end
factory :user_with_oauth, :parent => :user do
password nil
authentications [ FactoryGirl.build(:authentication) ]
end
factory :authentication do
provider 'facebook'
uid SecureRandom.hex(16)
end
end
Also, maybe relevant, am using DatabaseCleaner with the truncation strategy.
I can answer my own question (after 2 more hours of hitting my head against the wall)...
My :user_with_oath Factory was to blame; I wasn't wrapping the authentications association in a block:
factory :user_with_oauth, :parent => :user do
password nil
authentications { [FactoryGirl.build(:authentication)] }
end
I set up a User AR model which has conditional validation which is pretty much identical to the Railscast episode on conditional validation. So basically my User model looks like this:
class User < ActiveRecord::Base
attr_accessor :password, :updating_password
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 },
:if => :should_validate_password?
def should_validate_password?
updating_password || new_record?
end
end
Now in my action where the User can change their password I have the following two lines:
#user.updating_password = true
if #user.update_attributes(params[:user]) ...
so that I flag the validations to be run on the password. In development mode this works great - if the user tries to put in a password that is too short or too long the model does not pass validation. My problem is that for the life of me I can not get my tests for this to pass. Here is my spec:
require 'spec_helper'
describe PasswordsController do
render_views
before(:each) do
#user = Factory(:user)
end
describe "PUT 'update'" do
describe "validations" do
before(:each) do
test_sign_in(#user)
end
it "should reject short passwords" do
short = "short"
old_password = #user.password
#attr2 = { :password => short, :password_confirmation => short }
put :update, :user_id => #user, :old_password => #user.password, :user => #attr2
#user.password.should == old_password
end
it "should reject long passwords" do
long = "a" * 41
old_password = #user.password
#attr2 = { :password => long, :password_confirmation => long }
put :update, :user_id => #user, :old_password => #user.password, :user => #attr2
#user.password.should == old_password
end
end
end
end
When I run these tests I always get the error:
1) PasswordsController PUT 'update' validations should reject short passwords
Failure/Error: #user.password.should == old_password2
expected: "foobar"
got: "short" (using ==)
and of course the error for the password being too long. But should'nt the password be validated as a result of me setting #user.updating_password = true before any save attempts in the controller?
I think the problem isn't the code but what you expect it to do. When you call update_attributes and pass in a bad value, the value is saved into the model object even though the validation fails; the bad value has not been pushed to the database.
I think this makes sense because when the validation fails normally you would show the form again with the error messages and the inputs populated with the bad values that were passed in. In a Rails app, those values usually come from the model object in question. If the bad values weren't saved to the model they would be lost and your form would indicate that the old 'good' values had failed validation.
Instead of performing this check:
#user.password.should == old_password
Maybe try:
#user.errors[:password].should_not == nil
or some other test that makes sense.
Im trying to test my successfully creates a new user after login (using authlogic). Ive added a couple of new fields to the user so just want to make sure that the user is saved properly.
The problem is despite creating a valid user factory, whenever i try to grab its attributes to post to the create method, password and password confirmation are being ommitted. I presuem this is a security method that authlogic performs in the background. This results in validations failing and the test failing.
Im wondering how do i get round this problem? I could just type the attributes out by hand but that doesnt seem very dry.
context "on POST to :create" do
context "on posting a valid user" do
setup do
#user = Factory.build(:user)
post :create, :user => #user.attributes
end
should "be valid" do
assert #user.valid?
end
should_redirect_to("users sentences index page") { sentences_path() }
should "add user to the db" do
assert User.find_by_username(#user.username)
end
end
##User factory
Factory.define :user do |f|
f.username {Factory.next(:username) }
f.email { Factory.next(:email)}
f.password_confirmation "password"
f.password "password"
f.native_language {|nl| nl.association(:language)}
f.second_language {|nl| nl.association(:language)}
end
You definitely can't read the password and password_confirmation from the User object. You will need to merge in the :password and :password_confirmation to the #user.attributes hash. If you store that hash somewhere common with the rest of your factory definitions, it is not super dry, but it is better than hardcoding it into your test.
--preface: ignore if you want.
I'm new to rails, and working on a project that will require user authentication.
I found this tutorial and have been trying to go through it and understand what's happening. Of course, it's not exactly what I need as-is, so I've been modifying as I go along. The tutorial is also out of date in some areas, so of course I've had to update my code. So part of my problem is that I'm not sure if the bug is in my modifications, or some function that's been deprecated, or what.
--the question
This is the (simplest) test that fails. (" expected to not be nil" on the first assert statement.)
def test_authentication
#check we can log in
post :login, :user => { :username => "bob", :password => "test" }
assert_not_nil session[:user_id]
assert_equal users(:bob).id, session[:user_id]
assert_response :redirect
assert_redirected_to :action => 'welcome'
end
It calls the user_controller action login:
def login
if request.post?
if session[:user_id] = User.authenticate(params[:user][:username], params[:user][:password])
flash[:message] = "Login succeeded!"
redirect_to_stored
else
flash[:warning] = "Login failed."
end
end
end
which calls the User method authenticate. I know that authenticate works properly, however, because I have a single test that does pass:
def test_registration
#check that we can register and are logged in automatically
post :register, :user => { :username => "newuser", :password => "pass", :password_confirmation => "pass", :email => "newuser#web.com" }
assert_response :redirect
assert_not_nil session[:user_id]
assert_redirected_to :action => 'welcome'
end
which calls the user_controller action register
def register
#user = User.new(params[:user])
if request.post?
if #user.save
session[:user_id] = User.authenticate(#user.username, #user.password)
flash[:message] = "Registration succeeded"
redirect_to :action => 'welcome'
end
else
flash[:warning] = "Registration failed"
end
end
which successfully calls authenticate.
the users fixture has one relevant record:
bob:
username: bob
email: bob#mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test
salt: 1000
I've tested the hashed password and salt - "test" is the correct password.
So by my analysis, the bug has got to be in one of 3 places:
how I'm sending my post request,
how I'm accessing the parameters in the login action,
or some aspect of the fixture loading.
(originally I was using the tutorial's code to load the fixture explicitly (self.use_instantiated_fixtures = true; fixtures :users), but I read that all fixtures are automatically loaded before testing, so I took it out. That didn't change a thing.)
Of course, since I can't seem to find the problem in those areas, it could just as well be anywhere else.
Is it possible that there's a filter that is preventing your action getting called? If there's a general :before_filter => 'login_required' then you might not be reaching your login functionality at all. (Though admittedly the register action would have to be excluded for that test to pass)
In cases like this it's useful to stick some logging in (or run through a debugger) to see whether you even get to the part of the method that you think is failing. If it were me I'd stick a logger.debug("...") as the first line of the login method and then another after the check for request.post? and then another after the authentication check.