One thing I noticed when working with nested resource routes in Rails is that it is technically possible for a user to visit a route where the child resource exists (and is therefore displayed correctly), but the id for the parent resource represents an object that is not actually related to the child resource.
For example, in the route users/:user_id/post/:id, the user could type in a route where :user_id represents a user who did not make the post corresponding to :id.
What would be the best way to fix this so that if the user visits an invalid URL, the server redirects the user to the correct URL?
I have already put some code in my controllers to handle this, but it's kind of awkward having to check the path in every controller action and then redirect the user to the appropriate url, especially since the URL helpers are different for every action.
(edit_user_post_path(#user, #post), new_user_post_path(#user, #post))
There has to be a better way, right?
You should have a before_filter running on all requests that makes sure the user is valid. If not, it will throw ActiveRecord::RecordNotFound and show the friendly 404 page.
Then grab the post based on the user however you need, whether in another before_filter or directly in the action. Base your post search on the user. My example below demonstrates doing this with another before_filter.
before_filter :find_user_by_user_id
before_filter :find_post
def show
# Use #post variable here however you need
end
private
def find_user_by_user_id
#user = User.find(params[:user_id])
end
def find_post
# This assumes you have an association set up as needed
#post = #user.posts.where(id: params[:id]).first
if #post.nil?
# Do whatever you need here
end
end
First of all you should know that the error wich is raised by ROR will display the message 'Sorry but the page you are looking for does not exist' on a production environment.
Therefor I would not be concerned about that. if you want to 'capture' the failure and quickly redirect to a safe area you might be interested in using the rescue method.
http://www.simonecarletti.com/blog/2009/12/inside-ruby-on-rails-rescuable-and-rescue_from/
have fun
Related
I'm wondering what would be the best way to handle the following:
I have an authentication method (used as a before_action) as follows that checks if a user_id is in the session when the login page is requested. If the user_id is detected, then it redirects the user to dashboard path.
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
This is leading to a too many redirect errors which I understand. I can see in pry that it's just evaluating that before_action filter every-time the page loads. That's what leads to too many redirects.
My question is what is the best way to handle this type of setup. Is there a way in rails to only evaluate on the first redirect? I thought of using a temp flag to tell if the redirect happened before. That doesn't seem very elegant though. I'm sure there is an easier/better way to manage it.
Thanks for any advice you can provide.
There has to be an exception on your before_action: you don't want to call it on the dash_path. If a user enters there and is validated, it should stay there (as what the redirect would do) and if it is not validated it should just stay there (as with any other url that fails this validation process).
There is no point on checking if it is validated as the result will always be to stay on the same page.
Then in your controller you have to specify that you want an exception on the before_action:
class SomeController < ApplicationController
before_action: :already_validated, except: [:dash_action]
def is_validated_action # the method that causes the redirect
end
def dash_action # action of dash_path url
end
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
end
If you want some validation before the hypothetical dash_action then create a new method for it. Be sure that you don't have circular references or it will be pretty difficult to debug on the long run.
You can just tell Rails to skip the before filter in the controller that handles the dash_path:
# in the controller
skip_before_action :already_validated
Read about Filters in the Rails Guides.
I am re-reading railstutorial the second time for clarity. I was trying to understand RESTful resources provided by rails where it listed various HTTP request with corresponding controller actions: index, show, new, create, edit, update, destroy.
I never really fully understood how rails controller works and I wanted to clarify it. If I make a users_controller.rb and want to create new user, and that's it. I don't care about redirecting, flash messages, and other flairs. I just want to create new user and save it on my database.
I want to do User.create(name: "Iggy1", email: "letsmail#iggy.com"). Assume I did migration and I have name (string) and email (string) on DB schema.
What is the least code line on users_controller.rb needed to create a user?
The reason why I ask this question is up til now, I am still not 100% sure which code line actually performs the desired action. Is rails actually smart enough to recognize these specific keywords index, new, create, destroy, show, edit, and update, or are they arbitrary? If rails is smart enough to detect those seven keywords, by merely listing the method (def destroy; #<no code>; end), is DELETE user method automatically accessible, or I have to actually say def destroy; User.find(params[:id]).destroy; end to use DELETE user method? On users_controller.rb, I have, from railstutorial, in the end, this elaborate code.
...
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
...
Is merely having def create on Users_controller sufficient for rails to create a user?
def create
end
Or I need at least to have User.new, or more codes to create new user?
The first thing you'll need to create a user is a 'new' action in your UsersController, like the following:
class UsersController < ApplicationController
def new
#user = User.new
end
end
When you declare the above method definition, Rails will expect a file named 'new.html.erb', unless you tell it to render another template.
NOTE: I'll continue under the assumption that you don't want to do this, but if you did, you would add render 'other' to the end of your 'new' method (where 'other' is the file 'other.html.erb'):
Since your goal is to create a new User instance, this view template will need a form to add the name and email fields. While not required for all Rails forms, one common way of creating a User is to create a 'new' (i.e. unsaved) User instance and pass it to the form, so that the form knows which attributes a User has. Rails passes this instance variable to the view, and it also knows which view file to render (because the 'new' method you defined should be named the same thing as the 'new.html.erb' view template that contains the form). This is also how Rails knows that the 'submit' button of the form should read 'Create user'.
Assuming you have the above, the next step is adding a place for the form data to be sent once you click the form's "Submit" button. This is what the 'create' action does. In other words, the 'new' action is in charge of displaying the form in your browser, and the 'create' action is in charge of handling the data submitted by that form. The bare minimum code you'll need to add at this point is the following:
def create
#user = User.create(user_params)
end
The way Rails does this is through a special method it implements, called 'params'. Behind the scenes, Rails takes the HTTP request that occurs when you submit the form, and stores certain data in an ActionController::Parameters object. This data is then processed by the 'user_params' method (which you'll need to declare; see below), and only the paramaters which you whitelist in that method definition (i.e. 'name' and 'email') are returned.
This implies that you'll need to implement that 'user_params' method definition. This is typically done as a private method at the bottom of your UsersController, since you don't want this method available outside of the controller. So you'd add the following:
private
def user_params
params.require(:user).permit(:name, :email)
end
This is important from a security standpoint because a malicious user could potentially add new form elements in their browser (for instance, an element named 'admin') and click 'submit'. If your User model does indeed include an 'admin' attribute, that malicious user has just created an Admin user (with corresponding Admin privileges).
Finally, since the request that the form sends is a POST request (not a get request), Rails expects you to provide a view to send the user to after they submit the form. This view is typically the 'show.html.erb' template for the user you've just created, since the 'create' doesn't have a template of its own (remember, the form we've discussed is the view for the 'edit' action, not the 'create' action). The 'show' view is rendered by the following method:
def show
end
In addition, you'll need to tell Rails to redirect to the 'show' page after the 'create' action is finished. Add this to the end of your 'create' method:
redirect_to #user
Since you passed a single instance of the User class, Rails infers that you want to redirect to the 'show' action (as opposed to 'index' or some other action) of the User controller. Depending on what you pass to the "redirect_to" method, you can send your user to any number of destinations. See these docs for more info.
So in summary, besides the 'edit.html.erb' and 'show.html.erb' view templates and the route declaration in your 'config/routes.rb' file (which I haven't discussed in detail, since the question scope is limited to the UsersController), the bare minimum amount of code your UsersController should have is the following:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.create(user_params)
redirect_to #user
end
def show
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
You have to understand rails is built on MVC and REST. MVC is an architectural pattern to distribute responsibility of your project.
M - Model, which interacts with the database and the ideal place to implement business logic.
V - View, where the HTML rendering happens
C - Controller, basically bridges the communication between M and V.
So, basically when the end user accesses your domain the request comes to the webserver and then, to the rails API. The rails API would know the default controller to transfer the request and the default controller action would return the html output.
Default router in config/routes.rb
root to: 'controller#action'
Likewise rails understands the 5 HTML methods which are GET, POST, PUT, DELETE and PATCH. So, once you create a controller and set the reference in routes.rb as resources [controller_name] then, the rails routes would create 8 urls for each of the 8 default actions in your controller. For an example let's say your controller is users_controller.rb then, you set it in routes.rb as resources :users this would allow the following methods,
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
So, when you build your view and set the form_for url as users_path and set the http method as POST the submission of the form would trigger the create action. So, in the create action you should write all the code that is needed to trigger the model to save your record.
Also, go through this document as well,
http://guides.rubyonrails.org/routing.html
I have typical RESTful routes for a user:
/user/:id
/user/:id/edit
/user/:id/newsfeed
However the /user/:id/edit route can only be accessed when the id equals the current_user's id. As I only want the current_user to have access to edit its profile. I don't want other users able to edit profiles that don't belong to them.
What is typically the best practice to handle this situation?
Should I leave the route as is, and thrw an error if the current_user.id != param[:id], forcing the front end client calling the api to track the logged in user's id?
Should I make a special route /user/self/edit and in the controller check to see if param[:id] == 'self'?
I would've added special routes for current user profile actions, in this case you don't have to check anything. Just load and display the data of current user. For example:
/my-profile/edit
/my-profile/newsfeed
It's not that RESTful but you don't have to put extra checks keeping your code clean.
If you still have to have (or want to have) a strict RESTful routes then I would use a before_filter and check if the id = current_user.id. If not then return 401 or 403.
I only want the current_user to have access to edit its profile. I
don't want other users able to edit profiles that don't belong to
them.
What I suggest is to use some authorization gems like pundit
Sample code:
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
def edit?
#current_user == #user
end
end
Also with an authentication gem like Devise, only the current_user(the users who logged in) can only access and edit their profiles
I would say that you are doing it correctly, just keep your current route as it is right now. And what you should do is to add a restriction in your controller instead. I would assume that you are using Rails, and working on users_controller.
class UsersController < ApplicationController::Base
def edit
if current_user.id == params[:id]
# do your work
else
render :404
end
end
end
Or you could clean up your controller by moving the restriction into a callback instead:
class UsersController < ApplicationController::Base
before_filter :restrict_user, only: [:edit]
def edit
# do your work
end
private
def restrict_user
render :404 unless current_user.id == params[:id]
end
end
You can add the gem "cancancan" and after the initialize....
class Ability
include CanCan::Ability
def initialize(user)
can :update, User do |user|
user.id == params[:id]
end
end
end
Then add this authorize! :edit, #user to your update action
You're going to need to add authorization code in all the user_controller methods as another comment suggested. Usually what I do in apps where a user is only supposed to edit their own profile I add a /profile route for a user to edit their own profile and then on the main /users/:id/* routes I add logic to prevent non-admin users from accessing those routes.
User is able to view his profile /users/1 or edit his profile /users/1/edit. From users perspective this URLs are absolutely fine.
There is no links which may lead user to edit the another user. You are trying to cover the different situation: when someone manually trying to craft the URL and get access to another account. I would not call them hackers, but technically they are – users who are trying to exploit your website to pass the restrictions.
You don't have to worry about "hackers" convenience. I'm always use current_user in edit action so nobody can edit wrong profile whatever his profile is.
def edit
#user = current_user
end
Also, I need to mention that you should also cover update action with such checks. With edit you may only get data (and probably only wide-public open data, unless you put billing information or plain-text-passwords inside your edit template). But with update you can actually change the data, which may be more destructive.
Because it seems that the only available user resource should be the authenticated user, I think the best way to solve this is
GET /user
PUT /user
GET /user/newsfeed
If you like to extend the api usage in future so that one user could have access to other user resources, than you need a solution that includes the user ids. Here it makes sense to introduce the routes for "self", too. But then you also have to implement an access check on server side.
GET /user/id/:id
PUT /user/id/:id
GET /user/id/:id/newsfeed
GET /user/self
PUT /user/self
GET /user/self/newsfeed
But I think you should keep it as simple as possible
For further investigations I would propose books like http://apigee.com/about/resources/ebooks/web-api-design which give a good introduction into API design
Since you only care to provide RESTful endpoints only for the currently authenticated user, which is available in your controllers as current_user, i say you don't need the id identifier parameter. I suggest using the following routes:
GET /user => users#show
PUT/PATCH /user => users#update
GET /user/edit => users#edit
You should keep the url as it is. Authentication and Authorization are separate concerns. 'current_user' refers to the user who is authenticated to access the apis. The id in the url identifies the resource on which 'current_user' is working, so does he have access to that resource or not is the concern of authorization. So you should add current_user.id != param[:id] (as you mentioned) in your api permissions and throw 403 status code in response.
You should use this route:
PUT /user/me
Note that there is no need for "edit": you should use the PUT method instead.
Also, you should explicitly define the route I've written above, instead of checking if id == 'self'.
I am running Ruby on Rails 4.1 and I would like to implement a feature that redirects users to a custom web page when they perform an action by providing a redirect_uri parameter. In other words, I would like to "trigger" the redirection each time the redirect_uri value is present in params and the redirection should happen in any case (e.g., when GET, POST, PUT and DELETE HTTP requests are executed with HTML or JS formats) after the process flow has been fully accomplished (e.g., when the HTTP verb is POST then the redirection should happen after submission).
Bonus - If possible, I would like to validate the URL (e.g., link correctness) in cases when the referrer URL comes from outside the application domain.
How should I make that the proper way?
From this discussion, once your action encounters redirect_to/render statements, the request flow will be complete and after_action redirect may not be trigerred.
How about creating a controller action like custom_redirect_to as:
class ApplicationController < ActionController::Base
def custom_redirect_to(url)
redirect_to (params[:redirect_uri].present? ? params[:redirect_uri] : url)
end
end
and then use this method in your controller actions instead of redirect_to as:
def create
#post = Post.new(params)
if #post.save
custom_redirect_to #post
else
render 'new'
end
end
I am fairly new to Ruby On Rails and right now I am doing a simple app. In this app a user can create many items and I use devise for authentication. Ofcourse I want to make sure that you are the owner in order to delete items (Teams, Players etc) and the way I do it now is:
def destroy
#team = Team.find(params[:id])
if current_user.id == #team.user_id
#team.destroy
redirect_to(teams_url, :notice => 'The team was deleted.')
else
redirect_to root_path
end
end
Is this the best way? I was thinking about putting a method in the model but I am not sure I can access current_user from there. I was also thinking about a before_filer, something like:
before_filter :check_ownership, :only => [:destroy, :update]
I that case and if I want to code only one method for all objects (all objects this relates to have a "user_id"-field)
In my application controller I put:
before_filter :authorize
def authorize
false # Or use code here to check if user is admin or not
end
Then I override the authorize method in my actual controller to allow access to various actions.
You're looking for an authorization solution on top of your authentication (devise)
You can't access current user in the model no. I've had a fair amount of success using Makandra's Aegis for authorization. It allows you to create roles specify permissions attributed to each role. The docs are pretty good and I know it works fine with Devise, it's pretty agnostic that way, I've also used it with Clearance. It also passes an implicit "current_user" to your permissions so you can specify and check that your current user can take appropriate actions.