Storing Params in a Session with Nested Resource - ruby-on-rails

I am using Rails 4.
I have subarticles nested into articles. I am storing all of the form data from subarticles in a session when a user needs to create an account before submission.
Here is what I am using (subarticles_controller):
def create
if current_user.nil?
session[:subarticle] = params
redirect_to new_user_session_path
end
Then after the user signs up, it creates the subarticle with the stored params using
if session[:subarticle].present?
#subarticle = current_user.subarticles.create(session[:subarticle]["subarticle"])
session[:subarticle] = nil
flash[:notice] = "Awesome, you are logged in and your answer is undergoing review."
edit_user_registration_path
end
I am having trouble, however, saving the article_id in which the subarticle is created under. Can someone point me in the right direction to doing this?

A better approach could be to save the (sub)articles created by guest users in the database.
class SubArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
# ...
def create
#subarticle = Article.new(article_params) do |a|
if current_user
a.user = current_user
else
a.token = SecureRandom.hex
end
end
if #subarticle.save
if #subarticle.user
redirect_to #subarticle
else
session[:after_sign_in_path] = edit_article_path(#subarticle, token: #subarticle.token)
redirect_to new_user_session_path, notice: 'Please sign in to finalize your article.'
end
else
render :new
end
end
def edit
if #subarticle.user.nil? && #subarticle.token != params[:token]
redirect_to root_path, alert: 'You are not authorized.'
end
flash[:notice] = 'Please press save again to publish your post.' unless #subarticle.user
render :edit
end
def update
# The #subarticle.token should be included in the edit form
unless #subarticle.user && #subarticle.token == params[:sub_article][:token]
# let the current user claim the article
#subarticle.user = current_user
end
if #subarticle.update(article_params)
redirect_to #subarticle
else
render :edit
end
end
private
def set_article
#subarticle = Article.find(params[:id])
end
def sub_article_params
params.require(:sub_article).permit(...)
end
end
So here we we instead give the user a link to the edit page for the article where he/she can "finish" the article after logging in.
Since a malicious user could potentially "steal" unclaimed articles by guessing the id and entering the edit url we add a random token which we store in the database with the article and add to the url. Not 100% foolproof but at least better.
To make this work you will also need to add a token field to your form:
<%= form_for(#subarticle) do |f| %>
...
<%= f.hidden_field :token %>
...
<% end %>
The reason you might want to consider this is because session storage is often memory based and if you have a large amount of traffic storing the entire params hash in the session will exhaust the server memory. Also you should reset the session before logging a user in or out to avoid session fixation.
We do have a few issues though - first we don't want to accumulate a bunch of "unclaimed" articles if the user (or a bot) never logs in. The easiest way to do this is to setup a cron job to delete articles over a certain age without an associated user.
You would also want to filter any articles without a user from you show/index action.

Related

Rails 4: Change Database Values

I'm fairly new to rails and struggling on changing database values after the user successfully paid via stripe. Additionally after paying, it somehow redirects me everytime to '/subscriberjobs/1' which doesn't exist. Instead it should direct to the root_path of the application.
Here is what I've got:
Routes
resources :subscriberjobs
resources :jobs
Jobs Controller
def new
if current_user
#job = current_user.jobs.build
else
redirect_to new_user_session_path
end
end
def create
#job = current_user.jobs.build(job_params)
if #job.save
redirect_to '/subscriberjobs/new'
else
render 'new'
end
end
Subscriberjobs Controller (Here is what doesn't work!)
class SubscriberjobsController < ApplicationController
before_filter :authenticate_user!
def new
end
def update
token = params[stripeToken]
customer = Stripe::Customer.create(
card: token,
plan: 1004,
email: current_user.email
)
Job.is_active = true # doesn't work
Job.is_featured = false # doesn't work
Job.stripe_id = customer.id # doesn't work
Job.save # doesn't work
redirect_to root_path # doesn't work
end
end
Please tell me if you need additional information. Every answer is very appreciated. Thanks!
Send saved job id to subscriberjobs/new as a param. You can keep hidden field which will have value job_id in subscriberjobs/new html form, which will call your SubscriberjobsController#update method. There access it using params.
In JobController #create
redirect_to "/subscriberjobs/new?job_id=#{#job.id}"
In your SubScribeJob form
hidden_field_tag 'job_id', params[:job_id]
In your SubScribeJobCotroller
#job = Job.find(params[:job_id])

Rails: Can't get test to pass on create action

I'm trying to make sure that people can't submit a create action if they submit an entry with an ID other than their own. For this, I have set up the test as following:
entries_controller_test.rb
def setup
#user = users(:thierry)
#other_user = users(:steve)
end
...
test "should redirect create action on entry with id that doesn't belong to you" do
log_in_as(#user)
assert_no_difference 'Entry.count' do
post :create, entry: { content: "Lorem Ipsum"*10, id: #other_user }
end
end
The outcome of the test is that Entry.count increases by one, therefore #user can create a post with ID #other_user (is the code correct to create an entry with ID of the other user?)
entries_controller.rb: My create action currently looks like this.
def create
#entry = #entries.build(entry_params)
if #entry.save
flash[:success] = "Your entry has been saved."
redirect_to root_path
else
flash.now[:danger] = "Your entry has not been saved."
render 'index'
end
end
The instance variable is being passed in to the action by calling before_action :correct_user on the action. Here's the correct_user method.
def correct_user
#entries = current_user.entries
redirect_to root_url if #entries.nil?
end
By the way, the create action is being called from the index page. I suspect the problem is indeed with authorization since my test can log in the user and create an actual entry.
Can anyone spot an issue?
Your code is only checking whether the current_user has some entries, but there is no validation on the user_id of the entry being submitted to the create action. Moreover, even if the user has no entries, the #entries variable will be [], which is not nil (so correct_user will never redirect to root). The correct check would have been #entries.empty?, but still the object would be created with an incorrect user, as long as the current_user already has some entries belonging to them.
The way I usually go about this is not to permit the user_id parameter (with strong_parameters), and by setting the ownership of new objects to the current_user. If you want to perform the check, your correct_user should look more like this:
def correct_user
unless current_user.id == params[:entry][:user_id]
flash[:alert] = "Some error message"
sign_out # This action looks like a hack attempt, thus it's better to destroy the session logging the user out
redirect_to root_url
end
end
I think this might work.
In your entries controller.
class EntriesController < ApplicationController
before_action :correct_user, only: [:edit, :update]
def correct_user
unless correct_user.id == params[:entry][:user_id]
else
redirect_to root_url
end
end
end

Does directly visiting a url have the same effect as navigating via a link?

I have a voting system which records users' ip addresses, users can only vote once. If the array of votes contains the user's ip address, then the voting link will not be displayed to the user:
show.html.erb
<% if !ip_array.include? request.remote_ip %>
<%= link_to "Vote!", vote_user_path(#user) %>
<% else %>
You've already voted!
<% end %>
users_controller.rb
def vote
#user = User.find(params[:id])
#user.votes = #user.votes + 1
#user.save
end
But couldn't a user just directly go to www.my_website.com/users/:id/vote to bypass this? If so, how can I prevent this?
The way you prevent it is by placing the validation inside the controller action, rather than in (or in addition to) the view.
before_action :ensure_not_voted
def vote
#user = User.find(params[:id])
#user.votes = #user.votes + 1
#user.save
end
protected
def ensure_not_voted
# perform the check and stop on failure
# already_voted is a fake function, replace it with your real check
if already_voted
halt(403)
end
end
For simpler solution, you can add a session token to each user, and check if the ip contains the specific token, like
add a before_filter :auth, :only => [:vote]
def vote
#user = User.find(params[:id])
#user.votes = #user.votes + 1
#user.save
session[:token] = request.remote_ip
end
private
def auth
#ip = session[:token].to_s
if #ip != nil
redirect_to :back
flash[:notice] = "You have already voted"
return false
else
return true
end
end
the user can remove cookies to vote again(few people can), for that case you can save the ip in the db and check against it

How do I restrict access to edit action and through URL entry?

I have a relationship user ("devise") that has many events.
I want to prevent users from editing events that do not belong to them and stop users from accessing the edit action by entering something like 'http://localhost:3000/events/65/edit' into the browser.
I also want to redirect the user back to the page they were on when clicking on the edit event link.
I tried the following two methods without success:
def edit
if current_user == #event.user_id
#event = Event.find(params[:id])
else
redirect_to events_path
end
def edit
#event = Event.find(params[:id])
unless session[:id] == #event.user_id
redirect_to events_path
return
end
end
If you only need this kind of authorization logic in this controller, something like this would be possible:
class User < ActiveRecord::Base
has_many :events
end
class EventsController < ApplicationController
def edit
#event = current_user.events.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to events_path, notice: "You cannot edit this event."
end
end
The rescue-block is really optional. If you remove it, the user will get a 404 Not found error message if she visits the edit URL for an event she didn't create,
If you expect to use authorization other places in your application, I would advise you to look into CanCan. It's a gem that sentralizes rules for authorization and access in an Ability class.
Try adding a before filter (it can be used for other actions as well if needed):
before_filter :check_user, :only => [:edit]
def check_user
#event = Event.find(params[:id])
unless current_user.id == #event.user_id
redirect_to (request.referrer || root_path)
return
end
end
The idea behind your first method is fine, but that comparison will always fail. current_user is a User object; #event.user_id is an integer (or possibly some form of UUID).
You need to either compare a User object to a User object:
if current_user == #event.user
Or an ID to an ID:
if current_user.id == #event.user_id

Rails 3 -- After Failed Sign In, Errors Do No Populate

Edit: Hmm this is interested. I just noticed, my signup route is /signup. But, once I submit the information and the form returns invalid, I'm in the route /users .
I'm building a simple app to learn rails, and I've learned to set up an authentication system.
Today, I added a new plans table, to make different subscriptions for users. The Plan model has_many users, and users belong to plans. After implementing this, I see that if I enter invalid information, error messages do not show up in the view anymore.
I have the following code in the application.html.erb file for it show up -- >
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
It works fine on other notices, but it isn't showing the error messages.
One thing to also note, is that if I enter an email incorrectly for example, Rails shows me that it was the problem by highlighting it in red(edited CSS previously to do that). But, the error messages themeselves are nowhere to be found :P
Here's my Users controller :
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:show]
before_filter :correct_user, only: [:show]
def show
#user = User.find(params[:id])
end
def new
plan = Plan.find(params[:plan_id])
#user = plan.users.build
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def index
if current_user
redirect_to(user_path(current_user))
else
redirect_to(root_path)
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to login_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
end
I did some research, and people have been saying it might because of redirects, but I'm not sure If I'm doing that.
When first visiting the signup though, it's in the form of /signup?plan_id=1, to populate a hidden field with the plan_id in the signup form. When it shows the error screen, the plan_id is no longer there, which I assumed is okay since it already POSTed it. Does that have anything to do with it?
I think the problem is that the error messages you're expecting to see are errors on the User object, not stored in the flash. Try this in your view:
<% #user.errors.full_messages.each do |error_message| %>
<div class="alert"><%= error_message %></div>
<% end %>
See the docs for ActiveModel::Errors for more info.

Resources