Can't render iframe on another domain using rack-cors - ruby-on-rails-5

I'm trying to render an iframe of App A within App B.
App A is a local Rails 5.0 app and is using https.
App B is hosted on Heroku and is using https.
I've tried implementing the rack-cors gem but with no success, and I've tried all the suggestions I can find on StackOverflow.
My cors.rb file, within App A:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'https://app-b.herokuapp.com'
resource '/url/on/app_a/*',
headers: :any,
methods: :any
end
end
My config.ru file (I've tried with and without this):
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Rails.application
require 'rack/cors'
use Rack::Cors do
allow do
origins 'https://app-b.herokuapp.com'
resource '/url/on/app_a/*',
headers: :any,
methods: :any
end
end
The error I get is: Refused to display 'https://app-a.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

I am not sure if this is specifically to rack-cors, but I do know that the header 'X-Frame-Options' is intentionally set to 'sameorigin' for at least Rails 5. Most likely to prevent developers from unintentionally allowing someone to wrap their server in an iframe.
According to the docs, we can see that if the server sets this response as not 'sameorigin', then the browser will allow the HTML code to run. So what we need is to remove that header away. Chris Peters does a great job at this post. To save a click
class SomeController < ApplicationController
after_action :allow_iframe
private
def allow_iframe
response.headers.except! 'X-Frame-Options'
end
end
To apply this to all endpoints simply place the after_action line and the function code in the application controller, but I would suggest limiting this to only specific pages/controllers.

Related

Why Does JSONP Call Return Forbidden 403 Yet URL can be accessed in a browser

I know there are several questions related to this-- but I couldn't find my answer with them. Plus I wanted a bit more clarification.
I am running a rails app locally which makes a jsonp call to a sinatra application which is being used as an API.
When I put this URL in my browser I end up getting the correct response, yet when I make this call through jQuery using $.getJSON I get a forbidden 403 Error. I understand that the $.getJSON is making a jsonp request based on the url having callback=? parameter.
I'm trying to figure out what is causing the 403 Error. Is there some default configuration on the api application that is refusing the request because the script is being requested from an included script tag?
Right now the api request return json data. I assume it's my responsibility to look at the callback parameter and construct a response that actually calls the callback...
so if url was http://myapi.com?callback=blah, then I should be returning something like:
blah({foo: 'bar'})
But I don't know exactly what the 403 is all about. If it's the api server that is returning, then what is it trying to protect against?
here is an example of what the jsonp call looks like:
$.getJSON( 'http://myapi.com?callback=?', {biz: 'buzz'})
I see posts about setting headers for cross origin concerns-- but not sure why this is needed for jsonp request.
You need to add jsonp support to your sinatra api application.
Here is one way of adding jsonp support to your sinatra app
Add rack-contrip gem to your Gemfile
gem 'rack-contrib'
Add following to your config.ru
require 'rack/contrib'
use Rack::JSONP
Restart your sinatra app and start testing jsop from javascript

Backbone.js + Rails + IE routing

In my rails app, I have two layouts/controllers for different actions.
Eseentially, I match the root / to gateway#index, along with a few other pages such as /login and /register
The actual app once logged has its own sets of URLs, such as /dashboard /dashboard/action /explore etc.
Because of the pushstate with IE, the url changes to /#dashboard and loads the layout/JS for the gateway pages.
My rails controller for root has the following code, which is resulting in an endless loop in all versions of IE
if #current_user
redirect_to '/dashboard/lists'
end
The following is the Backbone history initializer (coffeescript):
Backbone.history.start
pushState: true
root: '/dashboard/'
Even with this setting, the application renders the gateway layout/JS not the application, and keeps the faulty URL the same (not setting the root to /dashboard).
How can I have IE load the application layout/JS/CSS while still having a different layout for the root?
I was setting the root to an invalid route. I ended up doing the following:
Backbone.history.start
pushState: true
root: '/app/'
And creating a route to a controller that used the application template.
From the Backbone website:
For RESTful persistence, history support via Backbone.Router and DOM manipulation with Backbone.View, include json2.js, and either jQuery.
So have you included json2.js?

Rails send_file not sending data when called through a web service

I am in a strange situation where send_file is unable to send file correctly. Here is situation:
Rail version: 3.0.10 and 3.1.0 [two different branches for testing]
Ruby: 1.9.2 on RVM
Webserver: Apache with Passenger
My client has a document management system; I worked on upgrading it to Rails 3 (and now rails 3.1) from Rails 2. We mostly redeveloped the system as the earlier one was quite old. All the features are working except one. The application allows users to download documents assigned to them. When users login they can see which documents are assigned to them and they can download. It works perfectly fine. Here is the code which works:
send_file(document.file[:path],
:type => document.file[:content_type],:x_sendfile=>true, :filename=>document.name)
There is one client for which they (my client) have made their earlier solution to send the document when requested through a .NET based web service (or whatever it may be called). The web service authenticates as a user and then is forwarded to document download path. I am able to make the web service authenticate and then redirect to the controller action which downloads the files but it does not work. The Server log says everything is ok:
Started GET "/download/12234" for 12.123.12.123 at 2011-09-20 23:21:24 -0400
Processing by DocumentController#download as HTML
Parameters: {"id"=>"12234"}
Sent file /yyy/zzz/abc/12234 (0.1ms)
Completed 200 OK in 138ms
I have changed the specific names and IPs. Note that the IP (12.123.12.123) is for the server which hosts web service.
I was on call with the developer who developed the .NET web service and he says he getting all the headers correct except that the content length is -1 and he is receiving no content. He said all other headers are correct.
To solve the problem; I tried multiple variations of send_file by trying to set all possible options (x_sendfile, stream, disposition etc).. I also tried setting the header:
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
But nothing works when I use the web service to download the file. However, same method works directly in the browser [I tested by bypassing authentication in the code].
I tried using send_date, but it does not work:
File.open(document.file[:path], 'r') do |f|
send_data f.read, :type => document.file[:content_type], :filename => document.name, :disposition => 'inline'
end
As a workaround I tried redirect_to instead of send file and used a test file in the public folder and it works. Although insecure but this seems to work fine. The only problem is that the browser is now opening the document instead of downloading it.
Please help me.
Update: The problem was related to the fact that Rails now sends chunked content and the web service was expecting content length.
It was not a problem with send_file. It was how the .NET web service was programmed. It was expecting content length.
There is a change (between Rails 2 & Rails3) in the default behaviour when sending content. Now it is chunked content - so there can not be a content length.
The .NET guy changed the code and everything is now working fine! Hope this would help somebody.

rails 3 sessions across subdomains not working in Internet Explorer

I am working on a rails 3 application which use subdomains. I used railscasts #221 "Subdomains in rails 3" (http://railscasts.com/episodes/221-subdomains-in-rails-3) as a guide and everything goes well, except in Explorer.
To keep my session across all the subdomains I put the next line in session_store.rb as the tutorial says:
MyApp.application.config.session_store :cookie_store, :key => '_myapp_session', :domain => "example.com"
I have tested my app on Firefox and Chrome and it works well, but for some reason is not working at all in Internet Explorer. The behavior is strange because sometimes it seems the session is share across all my subdomains, but some others there are some subdomains where I am logged in and other sudomains where I am not logged in.
I can't find any reason for this and I would appreciate any idea...
I am using Devise for authentication with rails 3.0.5
I believe you'll need to change your domain value to .example.com (the leading dot indicates that the cookie can be used across subdomains):
MyApp.application.config.session_store :cookie_store, :key => '_myapp_session', :domain => ".example.com"
For some reason this did not work (rails 3.2.11) for any session data that was set on a subdomain. It took a piece of custom Middleware to fix it. A summary of that solution is below.
tl;dr: You need to write a custom Rack Middleware. You need add it into your conifg/environments/[production|development].rb. This is on Rails 3.2.11
Cookie sessions are usually stored only for your top level domain.
If you look in Chrome -> Settings -> Show advanced settings… -> Privacy/Content settings… -> All cookies and site data… -> Search {yourdomain.com} You can see that there will be separate entries for sub1.yourdomain.com and othersub.yourdomain.com and yourdomain.com
The challenge is to use the same session store file across all subdomains.
Step 1: Add Custom Middleware Class
This is where Rack Middleware comes in. Some relevant rack & rails resources:
Railscasts about Rack
Railsguide for Rack
Rack documentation for sesssions abstractly and for cookie sessions
Here is a custom class that you should add in the lib
This was written by #Nader and you all should thank him
# Custom Domain Cookie
#
# Set the cookie domain to the custom domain if it's present
class CustomDomainCookie
def initialize(app, default_domain)
#app = app
#default_domain = default_domain
end
def call(env)
host = env["HTTP_HOST"].split(':').first
env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{#default_domain}"
#app.call(env)
end
def custom_domain?(host)
host !~ /#{#default_domain.sub(/^\./, '')}/i
end
end
Basically what this does is that it will map all of your cookie session data back onto the exact same cookie file that is equal to your root domain.
Step 2: Add To Rails Config
Now that you have a custom class in lib, make sure are autoloading it. If that meant nothing to you, look here: Rails 3 autoload
The first thing is to make sure that you are system-wide using a cookie store. In config/application.rb we tell Rails to use a cookie store.
# We use a cookie_store for session data
config.session_store :cookie_store,
:key => '_yourappsession',
:domain => :all
The reason this is here is mentioned here is because of the :domain => :all line. There are other people that have suggested to specify :domain => ".yourdomain.com" instead of :domain => :all. For some reason this did not work for me and I needed the custom Middleware class as described above.
Then in your config/environments/production.rb add:
config.middleware.use "CustomDomainCookie", ".yourdomain.com"
Note that the preceding dot is necessary. See "sub-domain cookies, sent in a parent domain request?" for why.
Then in your config/environments/development.rb add:
config.middleware.use "CustomDomainCookie", ".lvh.me"
The lvh.me trick maps onto localhost. It's awesome. See this Railscast about subdomains and this note for more info.
Hopefully that should do it. I honestly am not entirely sure why the process is this convoluted, as I feel cross subdomain sites are common. If anyone has any further insights into the reasons behind each of these steps, please enlighten us in the comments.

Celerity cannot follow a Devise redirect, because Celerity doesn't send an accept header and Devise responds with plain text

We've got a Rails application just upgraded to Rails3 using Devise's Rails3 gem for authentication. We've been using Capybara with Celerity backend to test some of the pages.
When accessing the application in a browser, Devise responds with a redirect to the login page when a user is trying to access a protected page/controller.
In the Rails 2.x version of Devise this used to work even if the incoming request had a blank accept header */*.
In the Rails 3 version, Devise responds with a plain text string when the accept header is blank.
The reason the blank accept header thing matters is because we're using the Celerity backend of Capybara to test some of the pages, and apparently Celerity sends a blank accept header, and thus doesn't get redirected by Devise. This behavior has changed from Devise for Rails 2.x to Devise for Rails 3.
Celerity fails with an UnexpectedPageException and the server log reports that the request was made with */* as accept header.
When using the Selenium/Webdriver backend on the exact same test suite, the problem goes away.
There are two ways to tackle this:
Tell Devise to somehow always assume text/html as accept header and respond accordingly. How could that be done? Do we have to override the controllers?
"Fix" Celerity to sent text/html as accept header. How can this be done?
Is this an HTMLUnit problem/bug?
To me #2 looks like the "right" way to fix this, but I'm not sure if Celerity/HTMLUnit's lack of accept header is a bug or a feature. Thoughts?
It turns out to be an issue with HTMLUnit. I've made a patch to the Celerity gem which you can find here: https://github.com/jarib/celerity/pull/49
It will set the default accept header to "text/html" but also adds an optional parameter to override it.