Secure paperclip urls only for secure pages

I'm trying to find the best way to make paperclip urls secure, but only for secure pages.

For instance, the homepage, which shows images stored in S3, is http://mydomain.com and the image url is http://s3.amazonaws.com/mydomainphotos/89/thisimage.JPG?1284314856.

I have secure pages like https://mydomain.com/users/my_stuff/49 that has images stored in S3, but the S3 protocol is http and not https, so the user gets a warning from the browser saying that some elements on the page are not secure, blah blah blah.

I know that I can specify :s3_protocol in the model, but this makes everything secure even when it isn't necessary. So, I'm looking for the best way to change the protocol to https on the fly, only for secure pages.

One (probably bad) way would be to create a new url method like:

def custom_url(style = default_style, ssl = false)
  ssl ? self.url(style).gsub('http', 'https') : self.url(style)
end

One thing to note is that I'm using the ssl_requirement plugin, so there might be a way to tie it in with that.

I'm sure there is some simple, standard way to do this that I'm overlooking, but I can't seem to find it.

Answers


If anyone stumbles upon this now: There is a solution in Paperclip since April 2012! Simply write:

Paperclip::Attachment.default_options[:s3_protocol] = ""

in an initializer or use the s3_protocol option inside your model.

Thanks to @Thomas Watson for initiating this.


If using Rails 2.3.x or newer, you can use Rails middleware to filter the response before sending it back to the user. This way you can detect if the current request is an HTTPS request and modify the calls to s3.amazonaws.com accordingly.

Create a new file called paperclip_s3_url_rewriter.rb and place it inside a directory that's loaded when the server starts. The lib direcotry will work, but many prefer to create an app/middleware directory and add this to the Rails application load path.

Add the following class to the new file:

class PaperclipS3UrlRewriter
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)
    if response.is_a?(ActionController::Response) && response.request.protocol == 'https://' && headers["Content-Type"].include?("text/html")
      body = response.body.gsub('http://s3.amazonaws.com', 'https://s3.amazonaws.com')
      headers["Content-Length"] = body.length.to_s
      [status, headers, body]
    else
      [status, headers, response]
    end
  end
end

Then just register the new middleware:

Rails 2.3.x: Add the line below to environment.rb in the beginning of the Rails::Initializer.run block. Rails 3.x: Add the line below to application.rb in the beginning of the Application class.

config.middleware.use "PaperclipS3UrlRewriter"

UPDATE: I just edited my answer and added a check for response.is_a?(ActionController::Response) in the if statement. In some cases (maybe caching related) the response object is an empty array(?) and hence fails when request is called upon it.

UPDATE 2: I edited the Rack/Middleware code example above to also update the Content-Length header. Otherwise the HTML body will be truncated by most browsers.


Use the following code in a controller class:

# locals/arguments/methods you must define or have available:
#   attachment - the paperclip attachment object, not the ActiveRecord object
#   request - the Rack/ActionController request
AWS::S3::S3Object.url_for \
  attachment.path,
  attachment.options[:bucket].to_s,
  :expires_in => 10.minutes, # only necessary for private buckets
  :use_ssl => request.ssl?

You can of course wrap this up nicely into a method.


FYI - some of the answers above do not work with Rails 3+, because ActionController::Response has been deprecated. Use the following:

class PaperclipS3UrlRewriter
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)
    if response.is_a?(ActionDispatch::BodyProxy) && headers && headers.has_key?("Content-Type") && headers["Content-Type"].include?("text/html")
    body_string = response.body[0]
    response.body[0] = body_string.gsub('http://s3.amazonaws.com', 'https://s3.amazonaws.com')
    headers["Content-Length"] = body_string.length.to_s
    [status, headers, response]
  else
    [status, headers, response]
  end
end

end

And make sure that you add the middleware in a good place in the stack (I added it after Rack::Runtime)

config.middleware.insert_after Rack::Runtime, "PaperclipS3UrlRewriter" 

Need Your Help

status bar notification in web application

javascript android html5 cordova notifications

I have a responsive web application and want to show notifications in the status bar of the mobile when the app is running in the mobile browser.

efficient usage of vbo for 2d sprites[opengl/android]

java 2d opengl-es-2.0 sprite vbo

I'm working on my own game engine these days, and I'd like to make the rendering process as efficient as I can. With "immidiate mode" I found that it's very easy to implement the features I want to