Is there a better way to test :admin security - ruby-on-rails

I am going through Hartl's Rails Tutorial. I'm up to the first exercise of 9.6, where he asks me to test that the User admin attribute isn't accessible. The justification is earlier in the book:
After Listing 9.42, Hartl's Rails Tutorial says
If we omitted the attr_accessible list in the User model (or foolishly added :admin to the list), a malicious user could send a PUT request as follows:
put /users/17?admin=1
The corresponding exercise (exercise 9.6.1) in the tutorial says
add a test to verify that the User admin attribute isn’t accessible
I have completed that test with this code in user_spec.rb:
expect do
#user.update_attributes(:admin => true)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
But I used stackoverflow to get that test. This was my original idea (in user_pages_spec.rb):
expect do
put user_path(user) + "?admin=1"
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) # or some other error
But I couldn't get it to work.
So my questions are:
Is my idea possible? Isn't it better to test directly for what a potential hacker might do from the command line? Isn't that the idea of Capybara, testing user actions?
If it is possible, is there a difference between testing mass assignment and testing the PUT action?
If it isn't possible, why? Is it just not necessary or am I missing something here?

I think I would argue with you that your test is actually better. Some would argue that the given answer is testing Rails functionality which really isn't your job. However, I do think it's frequently good to test things in several different directions.
I was under the impression from back in my school days that it was impossible to send data via the URI except when doing a GET. A quick search of stackoverflow didn't result in any confirmation. However, the wikipedia article seems to imply it:
http://en.wikipedia.org/wiki/POST_%28HTTP%2
I think the correct line of code would be
put user_path(user), {user: {admin: 1}, id: user.id}
I hope that helps.

Related

Rails: Cucumber and Application Controller Methods

I am doing integration testing using Cucumber. In my ApplicationController, I have a method called current_user that provides the current user object. I use this object to add items to a redis database:
$redis.sadd("cart#current_user.id}", [1,5,2])
In my Cucumber steps I test this functionality:
Then /^the redis database should have "(.+)" item ids/ do |count|
expect($redis.smembers("cart#{current_user.id}").count).to eq count.to_i
end
However, it is my understanding that Cucumber does not have access to controller methods, even if they are under ApplicationController, and therefore I cannot user the current_user method the way I would in my controllers.
What I am doing now is since I am testing features, there is only one user in the database so the current_user.id will always be 1, but if I start adding more users this may not work nicely.
Is there a workaround for this?
Your not really using Cucumber as intended here. What you are doing is testing how your application currently works, but really Cukes is best used to specify what your application does and why its important.
Applying more appropriate usage to your current problem leads to the following questions
What is the reason for storing the ids in Redis?
What benefit does the customer get by having these id's stored?
Taking a wild guess you might be saving a basket so that if the user logs out, their basket would still be populated when they come back. Then your scenario would be something like
Scenario: Remember products in basket
Given I am registered
And I am logged in
When I put some products in my basket
And I log out
And I log in again
Then my basket should still have some products in it
Notice how the scenario is all about WHAT you are doing and WHY its important but reveals nothing about HOW this is going to be done. This is a really good way to critique scenarios. Scenarios that contain HOW stuff are going to be harder to write and much harder to maintain. Anyhow enough of that :)
Now you can use standard cucumber stuff like assigned the user to a variable in one step e.g. #i = create_registered_user and then using that user in the other steps e.g. login as: #i
Note that we don't look at the database, only at what the user sees, and we don't reveal anything about HOW this functionality works in the scenario.
If you want to write tests (rather than scenarios) that do reveal how functionality works and do look at databases for results then I'd suggest that rspec would be better suited for this.
do you have a step to login? if so, you can change it a little so you can control which user logs in:
Given "john_doe" logs in to the app
Then you can search by username and do the login in your step. You can do the same on this step:
Then /^the redis database should have "(.+)" item ids/ do |count|
something like
Then /^the redis database should have "(.+)" item ids for user "(.*)"/ do |count, user_name|
user = User.find_by(username: user_name)
expect($redis.smembers("cart#{user.id}").count).to eq count.to_i
end

Rspec: How can I have many controllers in one test? How can I change controllers in a test?

I'm running into an interesting issue when I try to write a full walk through test.
First, I'd like to acknowledge that tests should be discrete and specific things. This fact I know.. but :)
But as with a play and learning lines, I think it's a good idea to have a full walk through test before you open the doors. To this end I want to write a massive integration test, that hits a pile of controllers. It will be super slow so I've already isolated it to run only when asked for. But now I'm stuck
I can't seem to figure out how to "hit" the controllers.
I've tried setting my test type to 'integration' and stipulating. I followed the steps outlined in this questions answer post to a different controller in an rspec test
And I threw in a 1/0 in the controller, and it never hit.
Okay.. I figured this out, thanks to other answers.. I'm just writing it out simpler, for my own notes.. and for future hunters.
describe 'Payments Integration', :type => :request do
let(:attributes) { p 'blabla' }
it 'should create user and billing details' do
expect{
post '/api/account', account: attributes
}.to change(User,:count).by(1)
user = User.find(json['account']['id'])
auth = user.authentication_token
expect{
post '/api/billing_details', auth_token: auth, credit_card: valid_card
}.to change(BillingDetail,:count).by(1)
end
end
I had many posts and puts, but this code is enough to get anyone started.

rspec expect condition OR condition

How do I write rspec tests defensively, so that in a scenario at least one expectation must be met yet the failure of others is accepted? (without the input changing). AND is easy enough by listing multiple expectations, but how is OR expressed?
As an example, a user has many posts, and user Bob hacks a form so that when he submits his create post form it sends the id of user Dunc. Currently the application ignores the passed Dunc id, and uses Bob's id as Bob is creating the post. So we could test that the newly created Post has Bob's user_id. However, if in future the code is refactored so that it returns an error message instead of assuming Bob's id, that test would wrongly fail. I want to test the intent, not the implementation.
So i need to test that either no post is created, or that if one is created, its for Bob.
This example is so simple it can be solved by testing
expect { run }.not_to change( Post.where(user_id: #other_user.id), :count )
However I'm looking for the general solution, in more complex cases there can be many conditions. How is "OR" achieved in Rspec? (or is it not possible?)
I don't think it is possible.
I do think you are mistaken when you say that you would be testing implementation, instead of intent in your example.
When you write a test, you test whether what comes out matches your expectation.
Creating a user is something completely different than returning error messages.
In my opinion it would be strange to say: when I do this, I expect this, or that, or that, or that to happen.
In my opinion you should write one test, that tests whether a user is created when you send the correct parameters, and another test that deals with what happens when a user tries to send illegal parameters.

Creating a path with an object id to map with cucumber scenario

I'm trying to create a cucumber scenario that checks to see if elements are loaded for an 'edit posting' page. My trouble, however, is that I don't know how to create a path that will direct it to the page.
The general path is something like: /posting/id/edit
i.e. /posting/11/edit
Here is my posting.feature scenario
# Editing existing post
Scenario: Saving the edits to an existing post
Given I am logged in
Given there is a posting
Given I am on the edit posting page
When I fill in "posting_title" with "blah"
And I fill in "posting_location" with "blegh"
When I press "Update posting"
Then I should see "Posting was successfully updated."
I dabbled around with some Factory Girl stuff, but I don't have the knowledge to use it appropriately (if it offers a solution), and wasn't able to find a relevant example.
I've also seen a lot of suggestions with regards to 'Pickle', but if possible I'd like to avoid that route to keep things simple seeing as I have very limited experience. Thanks!
Is there a link on your website that would take someone to the edit page? Then you could do something like:
Given I am on the homepage
And I follow "Posts"
And I follow "Edit"
This assumes that there is a link on your homepage whose text is Posts, and then another one in the resulting page called Edit. This is the best way to accomplish this, because there should be a direct route to whatever page you are testing. Those steps are also provided in web_steps.rb
You could also make a custom step like you did there with Given I am on the edit posting page and the code would be something like:
Given /^I am on the edit posting page$/ do
visit("/posting/11/edit")
end
Which you of course could also generalize like I am on the edit posting page for posting 11. But in general, cucumber tests are acceptance tests, which means not bypassing things like this. You should have a link to the edit page that can be clicked.
I came up with a solution, but I am not sure of its validity in terms of how clean it is. I ended up using Factory Girl (installed the gem).
I kept my scenario the same.
Under features/step_definitions I created posting_steps.rb
Given /^there is a posting$/ do
Factory(:posting)
end
Under features/support I created a file factories.rb with the following inside:
Factory.define :posting do |f|
f.association :user
f.title 'blah'
f.location 'Some place'
end
In my paths.rb I used
when /the edit posting page/
edit_posting_path(Posting.first)
How it works (or at least how I think it works) is that as
Given there is a posting
is executed, the posting_step.rb is invoked (Factory(:posting) is basically Factory.create(:posting)), which in turn uses the factory definition I created in factories.rb. This leads to an instance of a posting being created.
Then in my paths.rb
when /the edit posting page/
edit_posting_path(Posting.first)
gets passed the id from the instance, to ultimately get a path that could resemble /posting/1/edit , and the test continues on its way!
If there are any corrections to be made, please let me know as I am just learning the ropes.
Hopefully this will help other newbies out there!

RoR testing controllers

I use RoR 3 and i guess something changed in controller's tests.
There is no
def test_should_create_post
but
test "should create user" do
...
end
Is there any decription how is that mapping etc? Because i dont get it.
And second thing. How to program (what assertion) use to test login?
so the test "something here" style is rails way of helping us out. It is fundamentally the same as def test_as_you_want but they helped us out by taking away those nasty '_(underscores)' and wrapping the actual test wording in a string. This change came back, phew... maybe 2.3.x. that fact has to be checked but at least a year and a half ago.
Your second thing is a little more harder to answer man. What plugin are you using, or are you one of those guys who are writing their own auth system?
Either way, check out how the 'famous' auth plugins do it. from Restful Auth to Devise, basically you want test that you can:
Signup for the User account
all of your confirmation emails are sent etc..
Most of these 'cheat' or take the easy way out by passing a helper called signed_in users(:one) for instance. Assuming you are cool and using fixtures.
Basically here is what a helper method looks like if your Auth plugin/gem doesn't have one, like Clearance which didn't have it when i was first writing my tests... not sure if it has it now but it sheds light on how it should look. Notice I've commented out Restful Auth and how he/they did it:
#login user
def login_user(user = users(:one))
#Restful Auth Example
# #request.session[:user_id] = user ? users(user).id : nil
# Clearance
#controller.class_eval { attr_accessor :current_user }
#controller.current_user = user
return user
end
Actually i think i stole this from their shoulda login helper... that's probably what i did. Either way it shows you how to fake login a user.
Now when you are testing, just pass this login_user method to your test when you need a user logged in and start testing the rest of the method without worrying about them actually signing in. That is what the plugin is supposed to do and the 1000 people following it on github would scream if it didn't at least LOG that guy in.
cheers

Resources