Ruby on rails query issue - ruby-on-rails

I am developing web application on rails4.
I have user, block and request tables.
Block has blocker_id and blocked_id.
Some users have requests and some users can block other users.
I want to get some request entity that is created by some user and not blocked another user.
Assume there is r1 that is created by u1.
u2 wants to get r1 if u2 not blocks u1.
like Request.where("requests.user_id not blocked by u2")

Without knowing the relations you have set up for the 3 models, it's a little hard to give a complete response. The following is one scenario.
You're trying to determine whether or not to display a request from a user.
This depends on whether or not the logged in User (lets call them current_user) has blocked the requesting user (lets call them requestor). A blocked user can be found in the Blocked table.
You could first query the Block table to see if the user has been blocked:
Block.find_by(blocked_id: requestor.id, blocker_id: current_user.id)
That will return a Block object if one is found, otherwise it will return nil. Setting that to a variable will now give a user's blocked status. Probably best set in the controller action.
class RequestsController << ApplicationController
def show
#blocked_status = Block.find_by(blocked_id: requestor.id, blocker_id: current_user.id)
#request = Request.find_by(request_from_id: requestor.id, request_to_id: current_user.id)
end
end
Then you could use that information in your view to either display the request or not
in requests.html.erb
<% unless #blocked_status %>
# html to display request from requestor to current_user
<% end %>
Or
use a scope to find the request with something like:
scope :not_blocked_by, -> (user_id) {
joins("JOIN `blocks` b ON b.`blocked_id` = `requests`.`request_from_id` AND b.`blocker_id` = #{current_user.id}")
.where.not('b.blocked_id = :user_id', user_id: user_id)
}
then in the controller.
class RequestsController << ApplicationController
def show
#request = Request.not_blocked_by(#requestor.id)
end
end

Related

How can i count view_number from a model filtered by session?

i was coding a feature about view_number(view count) of Blog.
It's worked when i reload page, however i want count view_number based on session. Maybe it like Usage #8 in this gem
Any solution for this? Thanks you for reading
class Home::UpdateBlogViewNumberWorker
include Sidekiq::Worker
sidekiq_options queue: 'blogs'
def perform(*args)
blog_id = args.first
viewer = Blog.find_by_id(blog_id)
if viewer.present?
view_number = viewer.view_number.to_i + 1
viewer.update_attribute(:view_number, view_number)
end
end
end
and my controller
class Home::BlogsController < Home::BaseController
#...
def show
update_blog_view_number
end
private
def update_blog_view_number
Home::UpdateBlogViewNumberWorker.perform_async(#blog.id)
end
end
IDK what means your Blog model, if returns Users, or returns Posts, or return a Blog, like multi tenant blog app maybe. I will assume that returns Posts.
If you want control who view something, like the gem that you sent the link, you will need a table to match between session_hash, ip, or user_id and blog_id. Then you should ask to this table if the match exist for this post, if exist don't do anything but if doesn't exist will increase the counter.
Consider for better scalability add an index in the matches table by your user identifier (session, ip, id) and another in the blog_id.

Calculate a user_liked column for posts if a user is logged in

I'm using Rails API 5.0 in a blog application where users can log in and 'like' posts and comments similar to Facebook. When a user is logged in the client passes a token in the header of every request. This way my controller can see which user is making the request to the API.
# app/controllers/posts_controller.rb
if request.headers['Authorization']
token_string = request.headers['Authorization']
current_user = User.where(token: token_string).take
end
My models are set up so that users can have many posts, comments and likes. Likes of post and comments are kept in separate comment_likes and post_likes tables.
When a user is logged in I want them to be able to see all the posts and comments they have already 'liked' when viewing a list of posts or comments. I store this in an optional user_liked column. Initially I did this using a raw SQL query in my controller index method.
# app/controllers/posts_controller.rb
#posts = Post.joins("LEFT OUTER JOIN
(SELECT * FROM post_likes
WHERE post_likes.user_id = #{current_user.id}) AS pl
ON posts.id = pl.post_id")
.select("posts.*,CASE WHEN pl.user_id IS NULL THEN 0 ELSE 1 END AS user_liked")
However as I continue to work on the application I find that I want the user_liked column in every controller method when a user is logged in. It seems like it would be better to put it in the model.
Since the model doesn't have access to controller data, how can I create a model attribute that is dependent on current_user?
Create a method on the model and call it from the controller and pass through the current user.
Ex:
#likes = Post.user_liked(current_user)
render json: #likes

Rails getting access to current user

I'm trying to save in Note which Employee was the last editor of a `Note'
In a View, I'm able to access the current employee like this:
<h4><%= current_user.employee.id%></h4>
But, in a Model, I can't use current_user.employee.id.
So, I'm trying this in the User model:
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
And this in the Note model:
before_create :record_update
before_update :record_update
protected
def record_update
self.lasteditor_id = User.current.employee.id unless User.current.employee.id.nil?
end
What I'm getting is the last User in the Users table.
Thanks for the help!
current_user gets the logged in user information from the session. You cannot access session variables from model. If you want to update the Note model with the Last employee who viewed it, do it in your controller(most likely show action of your note or any other action you think would be right)
def show
#note = Note.find(params[:id])
#note.update_atribute(:last_viewed_by, current_user.id)
end
You code might look different from above. But this is the idea

How can I call a controller action from ActiveAdmin?

I have this method in my reports_controller.rb, which allows an user to send a status.
def send_status
date = Date.today
reports = current_user.reports.for_date(date)
ReportMailer.status_email(current_user, reports, date).deliver
head :ok
rescue => e
head :bad_request
end
How can I call this action from ActiveAdmin, in order to check if a User sent this report or not? I want it like a status_tag on a column or something.
Should I do a member action?
Thanks!
I'll address the issue of checking if a report has been sent later, but first I'll cover the question of how to call the controller action from ActiveAdmin.
While you can call ReportsController#send_status by creating an ActionController::Base::ReportsController and then calling the desired method, e.g.
ActionController::Base::ReportsController.new.send_status
this isn't a good idea. You probably should refactor this to address a couple potential issues.
app/controllers/reports_controller.rb:
class ReportsController < ApplicationController
... # rest of controller methods
def send_status
if current_user # or whatever your conditional is
ReportMailer.status_email(current_user).deliver
response = :ok
else
response = :bad_request
end
head response
end
end
app/models/user.rb:
class User < ActiveRecord::Base
... # rest of user model
def reports_for_date(date)
reports.for_date(date)
end
end
app/mailers/reports_mailer.rb
class ReportsMailer < ActionMailer::Base
... # rest of mailer
def status_email(user)
#user = user
#date = Date.today
#reports = #user.reports_for_date(#date)
... # rest of method
end
end
This could obviously be refactored further, but provides a decent starting point.
An important thing to consider is that this controller action is not sending the email asynchronously, so in the interest of concurrency and user experience, you should strongly consider using a queuing system. DelayedJob would be an easy implementation with the example I've provided (look into the DelayedJob RailsCast).
As far as checking if the report has been sent, you could implement an ActionMailer Observer and register that observer:
This requires that the User model have a BOOLEAN column status_sent and that users have unique email address.
lib/status_sent_mail_observer.rb:
class StatusSentMailObserver
self.delivered_email(message)
user = User.find_by_email(message.to)
user.update_attribute(:status_sent, true)
end
end
config/intializer/setup_mail.rb:
... # rest of initializer
Mail.register_observer(StatusSentMailObserver)
If you are using DelayedJob (or almost any other queuing system) you could implement a callback method to be called on job completion (i.e. sending the status email) that updates a column on the user.
If you want to track the status message for every day, you should consider creating a Status model that belongs to the User. The status model could be created every time the user sends the email, allowing you to check if the email has been sent simply by checking if a status record exists. This strategy is one I would seriously consider adopting over just a simple status_sent column.
tl;dr ActionController::Base::ReportsController.new.send_status & implement an observer that updates a column on the user that tracks the status. But you really don't want to do that. Look into refactoring like I've mentioned above.

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.

Resources