Unable to edit database entry - ruby-on-rails

So I have this application, where you create a user and then you can add movies and shows into a database. Like a bad version on IMDB?
Now.. I have this controller: https://github.com/Veske/form/blob/ryhm/app/controllers/movies_controller.rb
I have set up routes for movies and also it has all the necessary view files.. but when I attempt to go on a page to edit one of the movies: http://whatever.com/shows/1/edit for example, it gives me a error:
Couldn't find User with id=1
def correct_user
#user = User.find(params[:id])
redirect_to root_url unless current_user?(#user)
end
end
params
{"id"=>"1"}
Now.. why is it thinking that the param I throw at it, is a #user param when I have a update and edit controller made specially for Movies?

You don't seem to understand routes. The context in which you are using params[:id] is the movies controller, hence, the id would be the movie id. At the same time, you're authenticating (?) with the same param, giving you the error.
For basic authentication you could use the session hash, and for a more advanced one there are lots of gems, being devise the most popular.
PS: use rake routes to check your available routes and its URL params.

Your shows_controller.rb file calls correct_user before running the edit action you are calling, and it is specifically looking for a user on line 70. So it would make sense that you are getting this error if there is no user with an ID of 1.

Why is it thinking that the param I throw at it, is a #user param when I have a update and edit controller made specially for Movies?
Because you have a before_action filter at the top of your controller that is being called on the edit action.
You get into the correct_user method, which is using finding a user based on params[:id] . To test that this is your actual problem, you might want to try to change line 68 in your controller to:
#user = User.last #quick fix
The above could be used as a quick fix -you shouldn't get that error you posted about any more, as long as your user is signed in. If this allows you to avoid the error, you then need to concern yourself with properly assigning this User#id value when this correct_user method is called by your controller.

This is a MoviesController, so the params[:id] is actually the movie_id, i.e., the number "1" in your url "http://whatever.com/shows/1/edit". Not the user_id. So it throws the exception at line #user = User.find(params[:id]).
I went through your code but can't find where the correct user_id should come from. The Movie model doesn't belongs_to user. You should check out where the user come from.

Related

How can I check the authorization in Cancancan in Ruby on Rails each time the user calls that action?

I'm using Ruby on Rails 5 and I want that depending of the role of a User, the user can post 5 Posts or 10 or 15, and that is just a part of the several possible situations that I have to check for Authorization (for things that are not Posts for instance I have other situations that are a bit more complex), so I started using Cancancan (v 1.15.0) few days ago to don't make the User model too huge, so this way I have one class for each Role and in ability.rb I just merge the classes depending on the Role.
The problem is that using Cancancan apparently it checkes the Authorization only once. For example in Creating a Post, In Post#create the first line of code is:
authorize! :create, Post
In the Role class of the user, I have this code:
if user.posts.size < 10
Rails.logger.debug "If-size: #{user.posts.size}"
can :create, Post
else
Rails.logger.debug "Else-size: #{user.posts.size}"
cannot :create, Post
end
I have some tests with RSpec and I see the first time the current_user (the one with that specific role) creates a Post using the controller (I mean, not by FactoryGirl or any other way that is not using the controller), it appears in log/test.log:
If-size: 0
But neither the Else nor the If appears ever again in the log, it doesn't matter how many Posts I create by the Controller, it only evaluates this condition the first time and the User get authorized to creates as many Posts as he wants because the first time the condition in the If is true and it is not evaluated each time the method create of the controller Post is called.
EDIT: Solved by the method suggested by MarsAtomic. Thanks! :)
CanCanCan isn't meant to dynamically evaluate roles based on conditions within your application. That type of functionality is best handled by your application's own logic, rather than an authorization tool.
You should consider adding a column to your roles table that indicates how many posts a particular role is allowed. This column would allow you to check on the number of posts each user (via role) is allowed to create.
Then, in your posts_controller.rb, you can wrap your post creation logic in a block that runs only if the user has not exceeded the maximum number of posts allowed:
def create
if user.posts.size < user.role.max_posts
#post = Post.new(post_params)
#post.save
redirect_to #post
else
# flash an error on the page or redirect to an error page
end
end

Possible to Change Rails Routing Convention?

I'm wondering if it's possible to edit the default Rails routing convention to fetch a specific record based on a field that is not the ID?
For instance, instead of retrieving a specific record based on ID, with the verb/url combination:
GET /users/:id
Retrieve a specific record based on username, with the verb/url combination:
GET /users/:username
I don't see why this would be a problem theoretically, as long as usernames were required to be unique, but I'm having trouble understanding how to implement it based on the Rails Routing Guide.
I have gathered that I will need to add a line to my routes.rb file, to define a singular resource, just prior to:
resources :users
However, I'm having trouble understanding the syntax to accomplish this. Any help in understanding this would be greatly appreciated.
Yes it is possible and they are called Non Restful Routes in the rails documentation
A trivial example is doing the below in your routes.rb
get ':users/:show/:username', controller: "users", action: "show"
and in your UsersController you have a show action that looks like this:
def show
if params[:id].present?
#user = User.find(params[:id])
elsif params[:username].present?
#user = User.find_by(username: params[:username])
end
end
This way you support showing by id and username, if you want do disable support for either of them, modify the if clause as you wish
I think you are looking to change the to_param method like so:
class User < ActiveRecord::Base
def to_param
"#{id} #{name}".parameterize
end
end
This would give the url as: /user/id-name. If you want to get rid of the id before the name it gets a little more complicated. If you were just to remove it, it will more than likely break since ActiveRecord needs the id first for finds.
To get around this I would suggest using FriendlyId gem: https://github.com/norman/friendly_id
There is also a RailsCast showing how to use Friendly_id but its pretty straight forward.
The routes does not care if it is an ID or username.
It is really how you find it in the controller.
Just in the user show controller:
def show
#user = User.find_by_username params[:id]
end

How to use "current_user" with devise in Rails?

The code in question is in the create action of the Article controller.
I want each new article created to belong to the currently signed in User.
I checked that there is a user_id column in the Articles table and the correct association statements are in both models.
When I try and submit the form for a new Article, I get this error:
undefined method 'article' for nil:NilClass
in reference to this line of code:
#article = #current_user.article.new(article_params)
Am I using current_user incorrectly? How would one go about making the new item belong to the currently signed in user?
You should use current_user method:
#article = current_user.articles.new(article_params)
#current_user instance variable isn't set (thus it evaluates to nil) until current_user is called.
current_user is basically a convenience method/variable which takes the user id from the session hash and grabs that user (which is the current user that is active on that specific request)
so, when you want to have something only on the current user, just called current_user
however, there are some things you should watch out
make sure that the current user is logged in.
so make sure that when calling current_user.article.new(params) that current_user isn't nil, or it will throw an exception
when you want something to be only on that specific logged in user make sure to do as you did in the question
such as:
current_user.articles instead of Article.where(user_id: ...)
this will help you avoid problems in the future, and it may be faster in some situations
and 3. I think it should be current_user.articles.new(...)
It should be current_user, not #current_user.
current_user is a method, not an instance-variable.

Ruby on Rails security vulnerability with user enumeration via id

With Ruby on Rails, my models are being created with increasing unique ids. For example, the first user has a user id of 1, the second 2, the third 3.
This is not good from a security perspective because if someone can snoop on the user id of the last created user (perhaps by creating a new user), they can infer your growth rate. They can also easily guess user ids.
Is there a good way to use random ids instead?
What have people done about this? Google search doesn't reveal much of anything.
I do not consider exposing user IDs to public as a security flaw, there should be other mechanisms for security. Maybe it is a "marketing security flaw" when visitors find out you do not have that million users they promise ;-)
Anyway:
To avoid IDs in urls at all you can use the user's login in all places. Make sure the login does not contain some special characters (./\#? etc.), that cause problems in routes (use a whitelist regex). Also login names may not be changed later, that can cause trouble if you have hard links/search engine entries to your pages.
Example calls are /users/Jeff and /users/Jeff/edit instead of /users/522047 and /users/522047/edit.
In your user class you need to override the to_param to use the login for routes instead of the user's id. This way there is no need to replace anything in your routes file nor in helpers like link_to #user.
class User < ActiveRecord::Base
def to_param
self.login
end
end
Then in every controller replace User.find by User.find_by_login:
class UsersController < ApplicationController
def show
#user = User.find_by_login(params[:id])
end
end
Or use a before_filter to replace the params before. For other controllers with nested resources use params[:user_id]:
class UsersController < ApplicationController
before_filter :get_id_from_login
def show
#user = User.find(params[:id])
end
private
# As users are not called by +id+ but by +login+ here is a function
# that converts a params[:id] containing an alphanumeric login to a
# params[:id] with a numeric id
def get_id_from_login
user = User.find_by_login(params[:id])
params[:id] = user.id unless user.nil?
end
end
Even if you would generate random INTEGER id it also can be compromted very easy. You should generate a random token for each user like MD5 or SHA1 ("asd342gdfg4534dfgdf"), then it would help you. And you should link to user profile with this random hash.
Note, this is not actually the hash concept, it just a random string.
Another way is to link to user with their nick, for example.
However, my guess is knowing the users ID or users count or users growth rate is not a vulnerability itself!
Add a field called random_id or whatever you want to your User model. Then when creating a user, place this code in your UsersController:
def create
...
user.random_id = User.generate_random_id
user.save
end
And place this code in your User class:
# random_id will contain capital letters and numbers only
def self.generate_random_id(size = 8)
alphanumerics = ('0'..'9').to_a + ('A'..'Z').to_a
key = (0..size).map {alphanumerics[Kernel.rand(36)]}.join
# if random_id exists in database, regenerate key
key = generate_random_id(size) if User.find_by_random_id(key)
# output the key
return key
end
If you need lowercase letters too, add them to alphanumerics and make sure you get the correct random number from the kernel, i.e. Kernel.rand(62).
Also be sure to modify your routes and other controllers to utilize the random_id instead of the default id.
You need to add a proper authorization layer to prevent un-authorized access.
Let us say you you display the user information in show action of the Users controller and the code is as shown below:
class UsersController < ActionController::Base
before_filter :require_user
def show
#user = User.find(params[:id])
end
end
This implementation is vulnerable to id guessing. You can easily fix it by ensuring that show action always shows the information of the logged in user:
def show
#user = current_user
end
Now regardless of what id is given in the URL you will display the current users profile.
Let us say that we want to allow account admin and account owner to access the show action:
def show
#user = current_user.has_role?(:admin) ? User.find(params[:id]) : current_user
end
OTH authorization logic is better implemented using a gem like CanCan.

How do I respect RESTful methods when using find_or_initialize_by in Rails 3.2?

I have a resource in my project that collects some information from a user. Basically it's a form that they fill out before they can access another area of the site. It then sets a cookie for a week, but if they come back it will look up their previous entry and keep their preferences tied to them (and will update any details as long as the email address matches).
Currently I have a Applicants controller that looks like this:
class ApplicantsController < ApplicationController
...
def create
#applicant = Applicant.find_or_initialize_by_email(params[:applicant])
if #applicant.new_record? ? #applicant.save : #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
def update
if #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
end
The set_cookie_and_redirect is a private method that just sets some cookies and redirects the user to a page. The code works, but it just feels dirty. It's essentially updating a record within the create method under the condition that it's not a new record. I'm also forced to have an update method in case an existing record comes back with a validation error--the form helper will then switch the form over to sending to the update method.
So to my point... is there a more appropriate way to push the update_attributes call in the create method to the update method? Or better put, is there a better way to respect the RESTful methods in isolating the create and update functionality?
UPDATE: I wanted to be a little more specific too. If the user has filled this form out before it will set a cookie so they don't have to fill it out again for seven days. However after seven days the cookie is expired and they see the form again. The controller doesn't know if the user is new or existing until they add user input into the form which is then compared based on the email address.
Thanks in advance! I definitely look forward to anyone's thoughts on this.
The create method should only create, and the update method should only update. Let Rails decide which is going to happen based on what is inside of #applicant when the form is rendered - It essentially does what you're doing: Checks if the record is new or not, and sends it to update/create accordingly. Example:
def applicant
#applicant = Applicant.find_or_initialize_by_email(cookies[:email])
# renders applicant.html.erb form
end
<%= form_for #applicant do |f| %>
# ... fields ...
<% end %>
def create
#applicant = Applicant.new(params[:applicant])
#applicant.save
# .. etc.
end
def update
#applicant = Applicant.find_by_email(cookies[:email])
#applicant.update_attributes(params[:applicant])
# ... etc.
end
Rails will send the request to the correct action based on the new_record? status of the Applicant object.

Resources