I'm getting cancan with devise wired into my first Ruby on Rails app. I have some of it working, but I hit a problem my newbie brain cannot seem to understand.
I'm trying to delete a project model. The cancan ability.initialize call is not working because the user is passed as nil even though I am logged in and other calls successfully authorize
In my show view, I have this link:
<%= link_to "Purge this project", #project, method: :delete, data: {confirm: "There is no way to get it back. Are you sure you want to permanently remove this project?"} %>
I think my controller is wired up correctly, other actions are properly authorized
class ProjectsController < ApplicationController
load_and_authorize_resource
...
def destroy
puts "***********removing: " + params.inspect
#project.destroy
...
end
My cancan initialize does this:
def initialize(user)
user ||= User.new # guest user
puts "******** Evaluating cancan permissions for: " + user.inspect
...
When I click the delete link above, this gets puts'ed to the console (notice how user.id is nil, this is caused because i setup the guest if user is nil)
******** Evaluating cancan permissions for: #<User id: nil, email: ""...
Started DELETE "/projects/16" for 127.0.0.1 at 2013-04-04 10:48:23 -0400
Processing by ProjectsController#destroy as HTML
Parameters: {"id"=>"16"}
WARNING: Can't verify CSRF token authenticity
PROBLEM: Why is the user nil??? Is it related to the CSRF token issue? Is there something special about http method=delete that I'm missing? How can I stop the dumb, it hurts?
The preceding "show" action yielded this expected puts (concluding stuff should be wired up sufficiently):
******** Evaluating cancan permissions for: #<User id: 2, email: "doug...
Thanks!
self answer warning! So, I guess delete actions need to happen within a form post in order for the identity and CSRF stuff to be handled correctly?!?
Replacing my link with a form made things work again:
<%= form_for #project, :method => :delete do %>
<%= submit_tag "Purge this project", confirm: "Are you sure you want to permanently remove this project?" %>
<% end %>
Related
I'm using Ruby on Rails 5 Api app with modification to enable Active Admin.
Everything was fine until now. I don't remember doing any changes in the app, but now, if I delete cookies and etc on the browser, I can't login to the active admin app and this error is what I get:
I tried to add in application controller both
protect_from_forgery :with => :exception
and
protect_from_forgery :with => :null_session
but none have worked.
This is my application controller:
class ApplicationController < ActionController::Base
# protect_from_forgery :with => :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
attributes = [:name]
devise_parameter_sanitizer.permit(:sign_up, keys: attributes)
end
end
I don't know what causing it and how to solve it.
Thanks beforehand.
Now it's working. After I restart my computer and added the line:
protect_from_forgery prepend: true, with: :exception
instead in the application controller, it worked.
It happens to me a lot when I switch from a branch to another without restarting my server.
Restarting the rails server works for me each time :)
In my context, I have a custom admin page and to solve I put a authenticity_token hidden field. Like below:
form method: :post, action: admin_templates_save_template_path do |f|
f.label "label", for: :base_proposal_url
f.input id: :base_proposal_url, name: :base_proposal_url
### field to handle authenticity token
f.input type: :hidden, name: :authenticity_token
f.button "Save", type: :submit
end
I am trying to make it so that people who dont have account/are not signed in can see the list of users and all the users posts..
in my users_controller.rb I have
class UsersController < ApplicationController
before_filter :signed_in_user,
only: [:index, :edit, :update, :destroy]
before_filter :correct_user, only: [:edit, :update]
before_filter :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
end
I am guessing that the before_filter :signed_in_user is what makes it so only signed in users can access the user list/see user profiles. How can I change this without getting an error?
currently, when I remove the :index portion on line 3, I get the following error.
undefined method `admin?' for nil:NilClass
This is in line 4 of /app/views/users/_user.html.erb
<li>
2: <%= gravatar_for user, size: 52 %>
3: <%= link_to user.name, user %>
4: <% if current_user.admin? && !current_user?(user) %>
5: | <%= link_to "delete", user, method: :delete, confirm: "You sure?" %>
6: <% end %>
7: </li>
I'm assuming your have something like root :to => 'users#index' in your routes.rb file.
Common solutions for 'signed in / not signed in' behavior involves conditionally redirecting based on authentication states, or rendering various partials depending on state.
Under index, your #users instance variable will always contain paginated User models, regardless of state. However, we have no way of knowing what signed_in_user does. If you search for def signed_in_user in your code, you can see what it does. That may reveal something about how the app handles the state of authentication--or also how to proceed if that is what you are building out.
Update
So it looks like you are not populating the current_user object. This is typically a method that will return an object.
If you are using Devise, you may have to do some additional configuration for it in order to make that current_user helper method available to your view.
The nil error is resulting from the fact that current_user returns nil, or simply doesn't exist. So it's essentially trying nil.admin? and causing the Error to be raised.
Maybe the logic you are looking for is,
<li>
<%= gravatar_for user, size: 52 %>
<%= link_to user.name, user %>
<% if current_user.present? && current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete, confirm: "You sure?" %>
<% end %>
You can also investigate the value of current_user from the Rails debugger console. You can add a debugger statement in your controller, and then play around. Also, you can use the Rails console (run rails console from the command line) and play around there too.
Update 2
so why is it that there are no problems populating the current_user
object when I include :index. Why would removing that (see question
above) cause current_user to become depopulated
We can't know what is in the signed_in_user method to investigate further.
However, I have a hunch that current_user == nil is true because you haven't logged in at all.
I am a newbie in Rails. I try to build a simple authenticate system, to application_controller I put following lines:
def check_session
if session[:user]
if session[:expiry_time] < 10.minutes.ago
reset_session
flash[:warning] = 'You was logout.'
redirect_to root_url
else
session[:expiry_time] = Time.now
end
else
#... authenticate
session[:expiry_time] = Time.now
flash[:warning] = 'You was logout.'
redirect_to root_url
end
end
My problem is in one action - in this action I check, if the user is log in or not. And if the user is log in, so I will render one template, and if not, so I will render the second one. It looks like:
<% unless session[:user].nil? %>
<%= render :template => 'template_for_login_user' %>
<% else %>
<%= render :template => 'template_for_not_login_user' %>
<% end %>
And here is the problem - this doesn't works me. At least... well - if I am not log in, so will be render the template template_for_not_login_user and if I am, so template_for_login_user. This is right.
But if I am log in and I am on the template_for_login_user, but I am 15min idle => the session will be expired => I should be redirect to login form. But here is the problem - I am 15 minutes idle and I refresh this page, so I am still on the action template_for_login_user - and this is the problem...
I would like to ask you - can you help me please, where could be a problem? What I'm doing wrong?
In your ApplicationController, did you add a line like this :
before_filter :check_session
if some controller action don't need the user to be authenticated, you can add this:
skip_before_filter :check_session, :only=> [:index, :search, etc..]
in this example, this would skip your before_filter :check_session on action : index and search. This way you have a global behavior that always check the session for a user logged on. But you can skip this in particular controller where some actions don't need the user to be authenticated
Scenario
I have a controller with two actions only - :create and :delete. Where the create action is defined thus:
def create
# some code...
if #thing.save
redirect_to :back, :notice => "Successfully created."
else
redirect_to :back, :notice => "Successfully deleted."
end
end
I link to the action using...
<%= link_to "Become a friend", things_path(...), :method => :post %>
...in the view.
Problem
This works fine as I interact with the app in my browser. However, I wish to test this functionality using RSpec integration testing using Webrat's helper method - click_link "Become a friend" - which I think is correct. But I get this error
Failure/Error: click_link "I like Person-1's taste"
AbstractController::ActionNotFound:
The action 'index' could not be found for ThingsController
I can create an empty index action in the Things controller but this would violate the KISS Principle.
Questions
How can I workaround/fix this? And are there any best practices for cases like this?
The problem is that :method => :post makes rails create a form which is then submitted when you click the link. This only works with javascript enabled which webrat does not support out of the box. Have a look at https://github.com/brynary/webrat/wiki and try to get the test running with selenium. As selenium actually uses a real browser, your specs should run.
I am using devise 1.4.2 with rails 3.0.9, cucumber-rails 1.0.2, capybara 1.0.0. I got No route matches "/users/sign_out" error when I clicked logout. I added :method => :delete to link_to tag after going through this so question ( no-route-matches-users-sign-out-devise-rails-3 ).
Since I replaced prototype with jquery, I also had to change
config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
to
config.action_view.javascript_expansions[:defaults] = %w(jquery jquery_ujs)
to get around rails.js not found error.
Although with above changes I am able to successfully sign out and redirected to root, when I look at response of localhost:3000/users/sign_out request in FireBug it shows the same routing error message click here to see the screenshot with notes
After successfully implementing authentication to rails 3 app through devise, When I added feature and specs using Cucumber + Capybara + RSpec following this tutorial (github.com/RailsApps/rails3-devise-rspec-cucumber/wiki/Tutorial), I got following error
When I sign in as "user#test.com/please" # features/step_definitions/user_steps.rb:41
Then I should be signed in # features/step_definitions/user_steps.rb:49
And I sign out # features/step_definitions/user_steps.rb:53
No route matches "/users/sign_out" (ActionController::RoutingError)
<internal:prelude>:10:in `synchronize'
./features/step_definitions/user_steps.rb:55:in `/^I sign out$/'
features/users/sign_out.feature:10:in `And I sign out'
And I should see "Signed out" # features/step_definitions/web_steps.rb:105
When I return next time # features/step_definitions/user_steps.rb:60
Then I should be signed out
with the following step_definition for 'I sign out'
Then /^I sign out$/ do
visit('/users/sign_out')
end
I searched a lot and found that this is because of unobrusive javascript in Rails 3 being used for 'data-method' attributes, but I also read somewhere that Capybara does check for data-method attributes and behaves accordingly. But it did not work for me, so following this post Capybara attack: rack-test, lost sessions and http request methods I changed my step definition to following:
Then /^I sign out$/ do
rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.process :delete, '/users/sign_out'
end
but I got undefined method process for Capybara::RackTest::Driver (NoMethodError).
Following this lead I changed the above step definition as following:
Then /^I sign out$/ do
rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.delete '/users/sign_out'
end
This at least passed the 'I sign out' step, but it did not redirected to the home page after signing out and the next step failed:
And I should see "Signed out" # features/step_definitions/web_steps.rb:105
expected there to be content "Signed out" in "YasPiktochart\n\n \n Signed in as user#test.com. Not you?\n Logout\n \n\n Signed in successfully.\n\n Home\n User: user#test.com\n\n\n\n" (RSpec::Expectations::ExpectationNotMetError)
./features/step_definitions/web_steps.rb:107:in `/^(?:|I )should see "([^"]*)"$/'
features/users/sign_out.feature:11:in `And I should see "Signed out"'
After all this I had to resort to adding 'GET' method for logout in the routes file:
devise_for :users do get 'logout' => 'devise/sessions#destroy' end
modified my view from
<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
to
<%= link_to "Logout", logout_path %>
and changed my step definition to following:
Then /^I sign out$/ do
visit('/logout')
end
This obviously solved all the problems, all the tests passed and firebug did not show any error on sign_out. But I know that using 'get' request for destroying sessions is not a good practice, because it's a state-changing behavior.
Could this be due to particular version or Rails, Devise, Cucumber-Rails, or Capybara I am using? I want to use Devise's default sign_out route instead of overriding it with get method and be able to do BDD using Cucumber and RSpec. I am new to using Cucumber+Capybara, does there exists another method to send POST request instead of using "visit('/users/sign_out')", which only uses GET method?
So I have found that
<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
rails helper generates following html
<a rel="nofollow" data-method="delete" href="/users/sign_out">Sign out</a>
and jquery_ujs.js has following method to convert the links with data-method="delete" attribute to a form and submit at runtime:
// Handles "data-method" on links such as:
// Delete
handleMethod: function(link) {
var href = link.attr('href'),
method = link.data('method'),
csrf_token = $('meta[name=csrf-token]').attr('content'),
csrf_param = $('meta[name=csrf-param]').attr('content'),
form = $('<form method="post" action="' + href + '"></form>'),
metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
if (csrf_param !== undefined && csrf_token !== undefined) {
metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
}
form.hide().append(metadata_input).appendTo('body');
form.submit();
}
And Capybara helper visit('/users/sign_out') simply clicks the link and send a GET request to the server which does not have any route for this request.
As opposed to link_to helper the button_to helper adds the required form within the html when page is rendered instead of relying on javascript:
<%= button_to "Logout", destroy_user_session_path, :method => :delete %>
generates following html
<form class="button_to" action="/users/sign_out" method="post">
<div>
<input type="hidden" name="_method" value="delete">
<input type="submit" value="Logout">
<input type="hidden" name="authenticity_token" value="0Il8D+7hRcWYfl7A1MjNPenDixLYZUkMBL4OOoryeJs=">
</div>
</form>
with this I can easily use Capybara helper click_button('Logout') in my 'I sign out' step definition.
"link_to with a method anything other than GET is actually a bad idea, as links can be right clicked and opened in a new tab/window, and because this just copies the url (and not the method) it will break for non-get links..."
As Max Will explained right clicking and opening the link_to link with non-get data-method in new tab results in a broken link.
Some more useful discussion on link_to helper with ':method => :delete' and capybara issue can be found on this link
For now I would stick to simple link_to helper without :method attribute, and would prefer using button_to if I want to switch to non-get method for deleting.
At the same time I think there should be a capybara helper equivalent to Visit to cater for data-method attribute to send post request, so that one could avoid using javascript based driver for integration testing. Or may be there already is one which I am not aware of. Correct me if I am wrong.
The easiest way to correct this problem (albeit probably not the most correct one) is to modify your routes file to match the rest of the application. E.g. make the GET version of destroy_user_session_path work. You can do this by modifying the routes file as follows
Remove:
devise_for :users
Add:
devise_for :users do
get "/users/sign_out" => "devise/sessions#destroy", :as => :destroy_user_session
end
This is a bit dirty. I'm sure that Devise deprecated the GET route for good reason. However, fixing it any other way is beyond my Cucumber knowledge at this point, as every test in that suite ultimately relies on visit('/users/logout') which just isn't possible with the out-of-the-box Devise routes.
UPDATE
You can also fix this by commenting out the following in config/initialers/devise.rb
#config.sign_out_via = :delete
Devise 1.4.1 (27 June 2011) changed the default behavior for sign out requests:
https://github.com/plataformatec/devise/commit/adb127bb3e3b334cba903db2c21710e8c41c2b40
Jose Valim explained why: "GET requests should not change the state of the server. When sign out is a GET request, CSRF can be used to sign you out automatically and things that preload links can eventually sign you out by mistake as well."
Cucumber wants to test GET requests not DELETE requests for destroy_user_session_path. If you intend to use Cucumber with Devise, change the Devise default from DELETE to GET for the Rails test environment only with this change to config/initializers/devise.rb:
config.sign_out_via = Rails.env.test? ? :get : :delete
Don't try to tweak the routes.rb file to make the fix. It isn't necessary. If you're not going to use Cucumber, leave Devise's new default (DELETE) in place.
The example source code here:
https://github.com/RailsApps/rails3-devise-rspec-cucumber
now includes the change to the Devise initializer for Cucumber.
The application template here:
https://github.com/RailsApps/rails3-application-templates
now detects the collision between Devise and Cucumber and alters the Devise initializer as needed.
These changes were tested with with Rails 3.1.0.rc4 but the behavior should be the same with Rails 3.0.9. Please add comments here if the issue is unresolved or if you have more information.
The right way to solve this problem is explained in the devise's wiki page: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
Basically, once you have included on your user_step.rb file:
include Warden::Test::Helpers
Warden.test_mode!
You may replace visit '/users/sign_out' with logout(:user)
I'm actually having the same exact problem but with a Rails/Sinatra app. I've got Devise set up for Rails and the logout works. I've got a GuestApp Sinatra app running in lib, which works great except for the logout link. I'm trying to force data-method="delete" on the sinatra logout link, but nothing i do will make the request a delete request.
I think this might me a sinatra problem for me, but I thought what ever requests come in are processed by rails routes first until they reach my sinatra route. I'm about to manually add the GET route for logout, but I'd rather not have to do that.
Here's my devise routes:
devise_for :member, :path => '', :path_names => {
:sign_in => "login",
:sign_out => "logout",
:sign_up => "register" }
Here's my link:
%a{:href => '/logout', :"data-method" => 'delete', :rel => 'nofollow'}Log Out
Log Out
#- realized it should be method instead, but still not reaching routes.rb as delete
Log Out
When i need use something like this in test.env:
visit destroy_user_session_path
it's work for me, but maybe this is not right)
config/init/devise.rb
# The default HTTP method used to sign out a resource. Default is :delete.
if Rails.env.test?
config.sign_out_via = :get
else
config.sign_out_via = :delete
end