I'm trying to figure out how to limit Cross-Domain access to a JSON API for 3 sites. It seems you have a few different things to deal with in Rails 4, such as:
protect_from_forgery with: :exception which prevents CSRF attacks.
Changing this to protect_from_forgery with: :null_session seems to allow the world to access it.
Trying this in application.rb:
config.action_dispatch.default_headers.merge!({
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => 'GET, POST, PUT'
})
This, again, allows the world to access your endpoints, however, it only allows a max of one site if you replace '*' with a URL. I'd like at least 3.
There are also gems you could use such as rack-cors:
In application.rb:
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins 'http://myFirstSite.com', 'http://mySecondSite.com', 'http://myThirdSite.com'
resource '/article_pages',
:headers => :any,
:methods => [:post],
:max_age => 0
resource '/article_pages',
:headers => :any,
:methods => [:put],
:max_age => 0
resource '*',
:headers => :any,
:methods => [:get],
:max_age => 0
end
end
This latest example is supposed to only allow the sites specified in origins, but seems to allow any site (as I've tested). I'm not sure if this is a configuration issue but it seems to be configured correctly via their docs.
There are also examples as such: https://gist.github.com/dhoelzgen/cd7126b8652229d32eb4
This one appears to allow ANY site to access the one endpoint inside the controller for which headers['Access-Control-Allow-Origin'] = '*' is set in a filter. Again, I'd like to limit the access to 3 sites.
What is a standard way of allowing only a few sites to access one public endpoint while blocking all other sites?
Related
I place the Swagger formatting directly in my Ruby API source and use Source2Swagger to create the documentation:
# ~ a = my_api.apis.add
# ~ a.set :path => "/my_api/{application_key}"
# ~ op = a.operations.add
# ~ op.set :httpMethod => "GET", :nickname => "getmyapi"
# ~ op.summary = "Get stuff from My API"
# ~ op.parameters.add :name => "application_key", :description => "User authentication token", :dataType => "string", :allowMultiple => false, :required => false, :paramType => "path", :defaultValue => "1234567890"
# ~ op.responseMessages.add :message => "No stuff found", :code => 204
# ~ op.responseMessages.add :message => "API down", :code => 500
#
get '/my_api/?:application_key?', :provides => :json do
Do stuff...
end
The "application_key" is not required, but users need to know the option when the API goes live. As you can see above, I was asked to supply a default value for internal use (please don't ask why :-)) but I would still like to hide the default value of the parameter when displaying the documentation. Is this possible?
You won't be able to do it without hacking the UI. You'd need to find that specific operation and that specific parameter and override its behavior.
If you think about it, what you're asking doesn't make a lot of sense. You're saying that you're being asked to document something one way, but when displaying the documentation, you want to display it differently.
There are of course other possible solutions like proxying your documentation and changing it at the proxy, but that may be more work than it is worth.
I've been having a lot of trouble getting cookies working with my web application running Sinatra.
I am currently setting the cookies with:
response.set_cookie(:id, :value => id, :domain => "XX.XXX.XXX.XXX", :expires => Time.now + 86400000)
where the domain is the IP address of the web app (no proper domain for now). This correctly sets the cookie because I can find the cookie in my web browser's cookies and the values are correct.
However, I can't read the cookie. If I write:
id = request.cookies[:id]
then id just becomes a null value.
Is there something I'm missing (for instance are there any settings I should be aware of)? How can I get this to work?
All help would be appreciated. Thanks in advance.
OK, I managed to figure it out. I wasn't setting the path so it wouldn't work across different URLs.
I found this fixed my problem:
response.set_cookie(:id, :value => id, :domain => "XX.XXX.XXX.XXX", :path => "/", :expires => Time.now + 86400000)
While I don't think it is very restful to have to include a payload in a DELETE request. I ran into an instance where I am testing a service that requires a payload for DELETE. Might there be a way using Ruby's Rest Client to accomplish this? Unfortunately, I am having a hard time with this one.
#json_request = '{"user_id": 5, "meta_data": "foo"}'
resource = RestClient::Resource.new "http://www.foo.com/some/process"
#response_update = resource.delete(#json_request, :content_type => :json, :accept => :json)
Output:
ArgumentError:
wrong number of arguments (2 for 0..1)
Try this
RestClient::Request.execute(:method => 'delete', :url => "http://www.foo.com", :payload => json_data)
Currently it's not possible with that gem. You can see a PL addressing that. Maybe you could fork it and pull those changes to your own fork of the rest-client gem.
The pull request https://github.com/rest-client/rest-client/pull/98
As a very modern update, from the ReadMe
RestClient::Request.execute(method: :delete, url: 'http://example.com/resource',
payload: 'foo', headers: {myheader: 'bar'})
I'm using Rest-client gem in ruby.My code is as follows..,
require 'rest_client'
puts RestClient.get 'http://localhost:3000/articles'
puts RestClient.put 'http://localhost:3000/', {:params => {:Bat => 'ball'}}
RestClient.post 'http://localhost:3000/articles', {:params => {:Name => 'list1', 'Content' => 'Article1'}}
I refer the URL which runs in rails application, the user can can create, delete, edit,list the articles using the above url.For put,delete,post,get methods it produces the html code of the URL in my prompt.But it cannnot able to insert the post/delete an item from the list via ruby code.
It is possible in RestClient?
I think the problem here is that you need to be authenticated. You can do that with RestClient but you will need to chain your calls. See the rest_client Readme how to do that.
I am trying to use OmniAuth to handle the OAuth flow for a small-ish Sinatra app. I can get 37signals Oauth to work perfectly, however I'm trying to create a strategy for Freshbooks Oauth as well.
Unfortunately Freshbooks require OAuth requests to go to a user specific subdomain. I'm acquiring the subdomain as an input and I then need to persistently use the customer specific site URL for all requests.
Here's what I've tried up to now. The problem is that the new site value doesn't persist past the first request.
There's to to be a simple way to achieve this but I'm stumped.
#Here's the setup -
def initialize(app, consumer_key, consumer_secret, subdomain='api')
super(app, :freshbooks, consumer_key, consumer_secret,
:site => "https://"+subdomain+".freshbooks.com",
:signature_method => 'PLAINTEXT',
:request_token_path => "/oauth/oauth_request.php",
:access_token_path => "/oauth/oauth_access.php",
:authorize_path => "/oauth/oauth_authorize.php"
)
end
def request_phase
#Here's the overwrite -
consumer.options[:site] = "https://"+request.env["rack.request.form_hash"]["subdomain"]+".freshbooks.com"
request_token = consumer.get_request_token(:oauth_callback => callback_url)
(session[:oauth]||={})[name.to_sym] = {:callback_confirmed => request_token.callback_confirmed?,
:request_token => request_token.token,
:request_secret => request_token.secret}
r = Rack::Response.new
r.redirect request_token.authorize_url
r.finish
end
Ok, here's a summary of what I did for anyone who comes across this via Google.
I didn't solve the problem in the way I asked it, instead I pushed the subdomain into the session and then I overwrite it whenever the site value needs to be used.
Here's the code:
#Monkeypatching to inject user subdomain
def request_phase
#Subdomain is expected to be submitted as <input name="subdomain">
session[:subdomain] = request.env["rack.request.form_hash"]["subdomain"]
consumer.options[:site] = "https://"+session[:subdomain]+".freshbooks.com"
super
end
#Monkeypatching to inject subdomain again
def callback_phase
consumer.options[:site] = "https://"+session[:subdomain]+".freshbooks.com"
super
end
Note that you still have to set something as the site when it's initialised, otherwise you will get errors due to OAuth not using SSL to make the requests.
If you want to see the actual code I'm using it's at: https://github.com/joeharris76/omniauth I'll push the fork up to the main project once I've battle tested this solution a bit more.