Rails image upload security - ruby-on-rails-4

Currently, I adopt Carrierwave for users to images.
However, I hardly find a solution for image security, i.e. how to set image authorisation for the uploaded images to only let certain user in the same group to view?
After digging Facebook's implementation, I observe that they inject these params (oh,oe, __gda_) to the image url
?oh=924eb34394&oe=55E07&__gda__=1436393492fc8bf91e1aec5
Is there any similar implementation for carrierwave or paperclip?
Thanks

I worked quite a bit with this (only with Paperclip).
There is one solution that is okay, but it takes a lot of processing.
If you only want to hide your files from being looped through you can hash your Paperclip attachment, see this: https://github.com/thoughtbot/paperclip/wiki/Hashing
If you want to authorize user on every image load you can do like this:
Move your files out of your Public-folder
has_attached_file :image,
styles: { large: '1500x1500>', small: '250x250>'},
path: ':rails_root/storage/gallery/image/:style/:filename'
Use Sendfile to view your file
def show
send_file(object.image.path(:small), filename: object.image_file_name, type: "image/png",disposition: 'inline',x_sendfile: true)
end
I'm however a bit reluctant to implement this for example an image gallery, since it takes a GET-action + authorization for each image. Using the x-sendfile works with Apache to deliver the images faster.
Ref:
http://apidock.com/rails/ActionController/Streaming/send_file

I found this great solution for paperclip from https://makandracards.com/makandra/734-deliver-paperclip-attachments-to-authorized-users-only
Though a little out of date, this article details everything needed to secure not only the access to attachments, but also how to secure the files themselves. This article describes all of the steps to implement it, including Capistrano deployment!
be sure to use updated routes by changing:
map.resources :notes, :member => { :attachment => :get }
to:
resources :notes, only: [] do
member do
get :attachment
end
end
also I updated the link from:
link_to 'Download attachment', [:attachment, #note]
to:
link_to 'Download Attachment', attachment_note_path( #note.id )
also see Paperclip changing URL/Path for configuring the url.

Carrierwave stores uploads in /public by default, where all content is simply served as static content. If you need to control access to this uploads I'd start by configuring a different storage path
class TestUploader < CarrierWave::Uploader::Base
def store_dir
Rails.root.join('uploads', relative_path).to_s
end
def serving_path # Use this method to get the serving path of the upload
File.join '/uploads', relative_path
end
private
def relative_path
File.join model.class.model_name.plural, model.id.to_s
end
end
Since CarrierWave relies on public asset serving to serve uploads, you'll have to implement your own file serving method. This is silly example of how to do that with Rails
class Test < ApplicationRecord
mount_uploader :file, TestUploader
end
Rails.application.routes.draw do
get '/uploads/:model/:id', to: 'uploads#get'
end
class UploadsController < ApplicationController
def get
# ... autorization logic
model = params.fetch(:model).singularize.camelcase.safe_constantize
return head 400 unless model.present?
send_file model.find(params.fetch(:id)).file.path
end
end

Related

Redirecting a request in a routing constraint

I have Sidekiq mounted in my routes file to the /sidekiq endpoint.
I use a constraints option to have it call an external class for validation as a way of preventing non-privelaged users from accessing that endpoint.
# config/routes.rb
mount Sidekiq::Web => "/sidekiq", constraints: Sidekiq::AdminConstraint.new
# lib/sidekiq/admin_constraint.rb
module Sidekiq
class AdminConstraint
def matches?(request)
return false unless request.session[:user_id]
user = User.find_by_id(request.session[:user_id])
user && Ability.new(user).can?(:manage, :sidekiq)
end
end
end
This setup works great. However, it only lets me return true / false on whether the request should go through or not. It does not let me -
Set a flash message (e.g. "You are not permitted to access that page") and
Redirect to some arbitrary page
In that sense, I'm looking for it to behave more like a controller's before_filter.
Is there a way I can modify the request object that's passed in to implement that redirect?
Thanks!
I don't have idea directly set the flash messages, But we can use in different way.
Use the following solution
In your routes.rb, add the following line in the end of the file
match "*path", :to => "application#error_404"
This basically means, any path that is not defined in your route will end up going to error_404 in application_controller. Its very important to put this at the end of your file
And in your ApplicationController, add
def error_404
redirect_to root_path
end
Thanks

Rails 4 Dynamically Generate an Extra Public-Facing URL on Create

I have a Rails app that stores stock photos in each project. Upon creating a project, I want the app to not only create the url for the project that we will view internally, which is projects#show,i.e. /projects/4 but I also want it to create another URL that we can show to the client that will be a little different. It will allow the to approve the photos, etc. I want the url to be something like /projects/st53d where the end of the url will be a random number generated with random_string = SecureRandom.urlsafe_base64(5)
This way, I can pass this url to the clients and they can view all the photos in the project and approve the ones they want us to use, but cannot change or view the other internal stuff we have on the standard product show page
Currently, I have added a client_url column to the Project model and I was thinking of generating the random number within the Project#create method and then saving it to the project's client_url column. This way I can loop through all client urls and make sure I did not generate a duplicate. But I cannot figure out how to do the route creation part.
i have yet to do the part where I check if it is random but I know how to do that, just not the route creation.
I was thinking I needed to create a new method which I named show_client and somehow create a route for that in my config/routes.rb file
class ProjectsController < ApplicationController
before_action :authenticate_user!, only: [:show,:index]
def create
#project = Project.create(project_params)
#project.creator = current_user.email
require 'securerandom'
random_string = SecureRandom.urlsafe_base64(5)
#project.client_url = random_string
#project.save
redirect_to #project
end
def show_client
#project = Project.find(params[:id])
#photos = #project.photos
end
This seems like more of a roles issue. You could check out something like CanCanCan. If you only need two kinds of users, you could also just add a boolean column for admin to your user table. The url extension doesn't seem to matter in the scope of your problem. If you want to use a different view for the visiting the user show page, something in the show action like:
unless current_user.admin?
redirect_to client_show(current_user.id)
end
I was able to figure it out.
I created the client_url column in the database and upon creating a project, in the create method of the projects_controller, I generated a random base64 number and assigned it to project.client_url
in routes.rb i did:
get 'projects/clients/:client_id' => 'projects#clients', as: 'projects_client'
in my projects_controller.rb:
def clients
#project = Project.where(client_id: params[:client_id])
end
then the link to that route is:
<%= link_to 'Client Version', projects_client_path(#project.client_url) %>

CarrierWave Backgrounder process leaves denied access to s3 image

My current Sidekiq setup with the following is processing the image, but nothing is being done in the background. The page takes 10 seconds to load and then the new image is shown on the redirected page with the following path.
https://d9vwx0ll7rhpx.cloudfront.net/development/photo/image/product_thumb/product_thumb_0046d4ca-ca8d-4c02-b8cd-da0255c5736e.jpg
I would like for this process to be done in the background.
def create
#photo = Photo.new(photo_params)
if #photo.save
UploadsWorker.perform_async(#photo.id, photo_params)
flash[:notice] = "Your new photograph is being processed."
redirect_to photographer_listings_path(#photog)
else
flash[:notice] = "Check the fields marked with an orange flag."
render 'new'
end
end
class UploadsWorker
include Sidekiq::Worker
def perform(photo_id, photo_params)
photo = Photo.find(photo_id)
photo.image = photo_params["image"]
photo.save
end
end
I instead tried to use the CarrierWave Backgrounder gem, but found that the version processing doesnt take place. The code runs but I am left with no image on the page redirect after a new record saves.
When looking at the Sidekiq web interface, I can see that the job is being run and then completes.
However I am left with an image ( within the same directory) that has denied access.
Strange that occurs seeing the url shown at the top when not using the gem was valid.
https://d9vwx0ll7rhpx.cloudfront.net/development/photo/image/product_thumb/product_thumb_a9cf111f-c93a-4ec3-b1be-293321147000.jpg
I had to use store_in_background rather than process_in_background in order for the image to be properly uploaded to s3

Serve a file from public in a controller action

In my project, we have a single-page application, and our build process compiles the application into a single, static file at public/index.html.
The single-page application is responsible for handling all the routing in the app, so no matter whether you visit site.com or site.com/foo/bar/action or site.com/☃, I want the Rails application to serve public/index.html
I have this route:
match '*path', to: 'foo#index', via: :all
And in FooController:
class FooController < ApplicationController
def index
render 'index.html', layout: false
end
end
This does not work as I would hope; I get the error Missing template foo/index.html, application/index.html
Is it possible to serve an assets from public as part of a Rails controller action?
In Rails 5 I got this to work by doing
render file: "public/examplename.html", layout: false
This was way easier than I thought it would be.
I changed:
render 'index.html', layout: false
to
render 'public/index.html', layout: false
and everything worked as expected.
This appears to have changed. I'm trying to temporarily render a static file for the admin portion of my app, which will be a VueJS SPA that talks to rails via an API. I had to do the following:
routes.rb
match '*path', :to => 'console#index', :constraints => { :subdomain=>'console'}, :via=>:all
console_controller.rb
class ConsoleController < ApplicationController
def index
render :file=>'/public/console/index.html', :layout=>false
end
end
So using :file instead of no parameter on the path - because otherwise Rails 5 was trying to treat it like a normal view template.
With that path matching and a subdomain, this seems to work well, as all routes will pass through to the VueJS router.

Rails Routes not conforming to expected controller action

I've spent several hours trying to understand why my Rails 4 app doesn't seem to want to route to the expected controller action.
In summary: every single action that I attempt in the browser URL seems to go to the index view, even though my routes appear to be correct. I've attempted to restart the server, etc hoping that might fix it, but right now I'm completely lost.
For example, if I try to access the URL at localhost:3000/leads#new I get the following error message:
Missing template leads/index, application/index with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in: * "/Users/me/Sites/azimuth/app/views"
If I add a template for index.html.erb to the app/views/leads folder, then I don't get the error message - however every single path goes to that same index view - leads#show, leads#edit, etc - all of them.
Here is the routes.rb:
Azimuth::Application.routes.draw do
# get 'leads', to: 'leads#new'
resources :applicants
resources :contacts
resources :leads
PagesController.action_methods.each do |action|
get "/#{action}", to: "pages##{action}", as: "#{action}_page"
end
root "pages#home"
end
note that the commented line - get 'leads', to: 'leads#new' - seems to be the only way to properly get the routing to work. Using resources :leads (which I understand is best practice?) is giving me fits.
Here's the leads_controller.rb:
class LeadsController < ApplicationController
def new
#lead = Lead.new
end
def create
#lead = Lead.new(lead_params)
if #lead.save
flash[:success] = "Thank you for reaching out! We'll be in touch soon."
redirect_to 'home'
else
render 'new'
end
end
def index
#lead = Lead.all
end
private
def lead_params
params.require(:lead).permit(:first_name, :last_name, :subject, :message)
end
end
Rake routes - appears that things should work fine. (Note this is just showing the routes relevant to the Leads object).
Prefix Verb URI Pattern Controller#Action
leads GET /leads(.:format) leads#index
POST /leads(.:format) leads#create
new_lead GET /leads/new(.:format) leads#new
edit_lead GET /leads/:id/edit(.:format) leads#edit
lead GET /leads/:id(.:format) leads#show
PATCH /leads/:id(.:format) leads#update
PUT /leads/:id(.:format) leads#update
DELETE /leads/:id(.:format) leads#destroy
I'm very confused, can't seem to track down what's going on, and would appreciate any help!
Correct me if you are wrong, but I think you are trying to access the wrong URL. You said you were visiting localhost:3000/leads#new in your browser. The correct URL for that route would be localhost:3000/leads/new
When are you are defining routes in your config/routes.rb file, the #'s are used to let rails know that you are specifying that a method of one of your controllers should respond to this URL. The actual URL's do not contain #'s (typically speaking).