Sort of a follow-on from this question.
In my application, I have a Task model. Multiple pages can link to a single Task's "edit" url. For example, /tasks/1/edit is linked to from /tasks/1 and user/1/ (User being another model).
In my controller, I want to be able to redirect back to any one of the referring pages after the "edit" is submitted via an "update" action. For example, if I go to /tasks/1/edit from user/1/, after the "update" action I want to redirect back to user/1/. Same deal if I go to /tasks/1/edit from /tasks/1.
In my GET "edit" action I am doing:
#task = Task.find(params[:id])
if request.referer and (request.referer == task_url(#task) or request.referer == user_url(#task.user))
session[:return_to] = request.referer
else
session.delete(:return_to)
end
In the corresponding PUT "update" action I do:
#task = Task.find(params[:id])
respond_to do |format|
if #task.update_attributes(params[:task])
format.html { redirect_to session.has_key?(:return_to) ? session[:return_to] : #task #return to task if no return_to specified
else
...
end
end
This works, but I am concerned that the client could spoof/fake their session[:return_to] in the "update", allowing them to redirect to whatever page they want.
Does that matter? Is this a valid concern? Do I need to validate session[:return_to] in the "update"?
In short, yes...
request.referrer stores the URL that brought a user to your site. If you did the following:
Create a static HTML page
Embed a link to the /task/1/edit
At the end of the update, it would redirect back to the static web page.
To solve this problem, what you should do is store a session variable that denotes the location (e.g. in your UsersController, do a before_filter and set session[:return_task_update_to] = :users)
And then, at the end of the update, you redirect back, and clear the :return_task_update_to variable in the session. You would obviously also handle the case if there is no session variable (in which case you would redirect to whatever made sense as a default).
This way, you don't need to validate the URL or do something crazy. Simply set up the state in the session, and redirect if it exists.
Related
Question:
How I want to redirect to different path after create action, given if previous_page is ../reader/new goes to ../reader/blog/:id, whereas if previous_page is ../editor/new goes to ../editor/blog/:id.
Explaination:
I want to modify the controller actions so that it can redirect to different path depending on which page it comes from. For example, I have a reader, editor and blog model. Both reader and editor can create a blog.
Here is the original blogs_controller:
class BlogsController < ApplicationsController
def create
#blog = Blog.new(blog_params)
respond_to do |format|
if #blog.save
format.html { redirect_to #blog }
else
format.html { render :new }
end
end
end
private
def blog_params
params.require(:service).permit(:title, :content)
end
end
You have a few options:
use redirect_to :back in the controller if all you need is redirecting back to the previous page
add some logic processing the HTTP referer, i.e. decide where to redirect based on request.referer in the controller
pass the redirection info in a parameter to the create action, e.g. pass params[:redirect_to] = "reader" when coming from the "reader" page and decide where to redirect based on this parameter. You can even place the whole URI to redirect into the param and just redirect to it (but this approach is unsafe as params can be mangled by users).
Generally, I'd choose the third option (parameters) as you have the best control over the redirection process (the first two options rely on HTTP referer which can also be mangled by any visitor, thus are potentially unsafe).
I am using Devise with Rails for my user login. It all works great, however, i am unable to get a specific redirection working.
I am new to rails so the method in which i am doing things may not be correct.
Essentially, with an example, i am allowing users to like a post. However, they need to be signed in to do so. I have created an action in my controller called 'like' and the controller has the devise filter
before_filter :authenticate_user!, :except => [:index, :show]
entered thereby the sign in page is being shown. Once the user signs in i want to redirect them back to the post which they have liked with the 'like' action having been called.
my controller looks like
def like
#post = Post.find(params[:id])
#like = Like.new;
#like.user_id = current_user.id;
#like.post_id = params[:id];
respond_to do |format|
if #like.save
format.html { redirect_to #post, notice: 'You have like this post' }
format.json { head :no_content }
else
format.html { redirect_to #post, notice: 'Sorry you like was not saved }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
Naturally i cannot hard code the path using after_sign_in_path_for
def after_sign_in_path_for(resource)
#path to somewhere
end
But i have read that i can use a session variable and perhaps call that in the above code. In which part of Rails could i write the session variable, as it would be too late in the controller action (as devise takes over before it hits the like action) and i cannot see how to set it in the view.
Also my link looks like
<%= link_to "Like", {:controller => :posts, :action => :like, :id => #post.id}, {:method => :post } %> |
PS the redirection when using creating a new 'post' works ok i.e. the user signs in and are redirected to the new post view.
Any help and tips would be greatly appreciated.
Thanks
You are experiencing this because like action is specifically designed for POST. Therefore, you should make sure that user is signed in before you POST to that URL, and doing it with session is tricky:
You'd have to unprotect like method by excluding it from before_filter
Then check manually if user_signed_in? (mind you this is a helper method),
Then (if user is not signed in), stash what you are liking in session and redirect to sign in page with return URL
Upon user visiting this return URL (it will be a GET and not a POST), you would have to look up the session info and do what original Like was supposed to do (but by then user will be signed in).
Seeing that all of this dance will end with a GET request, why not make Like action work on GET requests as well as pass parameters in the Query String in the first place? It will require 0 code changes and it will not expose you to a security threat since Like is protected by before_filter. You would just have to make sure that your Like links aren't followed by search engines by using rel="nofollow" on your <a> tags.
For a related discussion, see redirect_to using POST in rails. There, one of the suggestions is to build and submit a form on the client via JavaScript. That would have to happen on that return URL view once user has authenticated. This might be the best compromise if you object to having your like action exposed as GET (which violates REST principles)
this is easy to fix:
go in application controller:
# after login redirect
after_filter :store_location
def store_location
# previous url save when its not a admin or user url
session[:previous_url] = request.fullpath unless request.fullpath =~ /\/users/ || request.fullpath =~ /\/admin/ || request.fullpath =~ /\/admin\/login/ ||
request.fullpath =~ /\/login/
end
Suppose I have something like this:
def new
#user = User.new
end
def create
#user = User.create(params[:user])
if #user.save
flash[:notice] = 'User created'
redirect_to :action => 'list'
else
flash[:error] = 'Some error here!'
render 'new'
end
end
I think the code is clear.
The problem here is, when the #user object is not saved successfully, should I render new (as above) or should redirect to new?
I know if redirect to new the data input by the user is lost, but if I render new, the URL will be /users/create instead of /users/new (which is ugly!).
You are correct in not using redirect. Redirect is loading an entirely new resource.
render however will keep your session data fresh, and depending on how your form is set up, should repopulate whatever data was inputted.
You mention:
I know if redirect to new the data input by the user is lost, but if I render new, the URL will be /users/create instead of /users/new (which is ugly!).
No, this is not true. If you say render 'new', it will go to the url users/new not create. Create as an action only handles POST requests to your controller, and generally never has a view associated with it. It will instead refer to the new action to handle any errors and displaying of forms.
The create action has this in common with the update action which does the same thing by handling only PUT requests, but refers to the edit action to handle the displaying of views.
def edit
#title ="Edit account"
#page_name = "edit"
end
def update
if #wsp.update_attributes(params[:wsp])
# it worked
flash[:success] = "Profile updated."
if (#title == "Location")
redirect_to wsp_location_path
else
redirect_to edit_wsp_path
end
else
#title = "Edit account"
render 'edit'
end
end
The variable #title is empty when in the update method. How can I make the #title persistent so I can read it?
Each controller action is executed in a separate request, so you are losing the values in between.
You probably need to use session, or better yet flash to store the title across requests.
def edit
flash[:title] = #title = "Edit account"
...
end
def update
...
if (flash[:title] == "Location")
redirect_to wsp_location_path
else
redirect_to edit_wsp_path
end
end
In the edit action, you should write the result to a form field, perhaps a hidden field. Then read that data back in from the submitted form in your update action. You really should not preserve any state server side between these 2 requests.
And while you could use the session, I would advise against this. Overuse of the session for passing tiny bits of short lived data between requests is generally bad form.
If the entire purpose of this is to display it on the Edit page, why not put it there (app/views/the_controller/edit.html.erb)? That way it'd be shown on all requests to that page and you're not putting view / presentation code into the controller where it doesn't belong.
During a post for a new model, I am checking for authentication via Authlogic. There is a before_filter on the create request. It is calling require_user. After the user session has been successfully created, the redirect_back_or_default(default) method is called. The problem is, The request needs to be posted to the stored uri. I have tried to store the method and input it into the redirect_to however it isn't working. Any ideas?
# called before successful authentication with before_filter
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to new_user_session_url
return false
end
end
def store_location
session[:return_to] = request.request_uri
session[:return_to_method] = request.request_method
end
# called after successful authentication
def redirect_back_or_default(default)
redirect_to((session[:return_to] ? session[:return_to] : default), :method => session[:return_to_method])
session[:return_to] = nil
session[:return_to_method] = nil
end
You can't redirect to a post action, only get actions.
You could store the post object for later processing after authentication, but you really don't want to do that.
Why not simply ask for authentication on the #new method, rather than (or in addition to) the #create? That way the user is authenticated before they fill in the form.