Add api_key on every request with Rack middleware - ruby

I work with Devise token_authentication service and ActiveResource client. I wish set automatically :auth_token params in every requests !
I tried this, but this doesn't work...
class AuthApp
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
request = Rack::Request.new(env)
request.params[:auth_token] = 'jCxKPj8wJJdOnQJB8ERy'
[status, headers, response]
end
end
Any idea ?

If you have a recent copy of rack that includes this pull request, you can use Rack::Request#update_param:
request = Rack::Request.new(env)
request.update_param :auth_token, 'jCxKPj8wJJdOnQJB8ERy'
That will persist in the env that is passed among middlewares (and to Rails).

Related

Ruby base_auth with net/http - undefined method `user' for String

I've got pure Ruby app where I want to create request to external API. To do so I'm using standard Ruby Net::HTTP like below:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
def initialize(payload:)
#payload = payload
end
def post_quiz
handle_response(Net::HTTP.post_form("#{BASE_URI}#{QUIZ_PATH}", options))
end
attr_reader :payload
private
def options
{
basic_auth: basic_auth,
body: payload.to_json,
headers: headers
}
end
def basic_auth
{
username: Settings.ln_username,
password: Settings.ln_password
}
end
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
def handle_response(response)
return response.body if response.success?
end
end
But instead of response I'm getting an error:
NoMethodError: undefined method `user' for #String:0x00007f80eef9e6f8
Did you mean? super
/Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527:in `post_form'
I don't have any user there, what is it?
Net::HTTP.post_form is used to send FormData pairs - its not what you want to send JSON and it doesn't even allow you to send headers (You're actually putting them in the request body!).
If you want to send a POST request with HTTP Basic auth and custom headers and JSON body you need to create the request object manually:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
attr_reader :payload
def initialize(payload:)
#payload = payload
end
def post_quiz
url = URI.join(BASE_URI, QUIZ_PATH)
request = Net::HTTP::Post.new(url, headers)
request.basic_auth = Settings.ln_username, Settings.ln_password
request.body = #payload.to_json
# open a connection to the server
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
http.request(request)
end
handle_response(response)
end
private
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
# How to respond from an API client is a whole topic in itself but a tuple or hash might
# be a better choice as it lets consumers decide what to do with the response and handle stuff like logging
# errors
def handle_response(response)
# Net::HTTP doesn't have a success? method - you're confusing it with HTTParty
case response
when Net::HTTPSuccess, Net::HTTPCreated
response.body
else
false
end
end
end
Here is the source code that raises the error:
def HTTP.post_form(url, params)
req = Post.new(url)
req.form_data = params
>> req.basic_auth url.user, url.password if url.user
start(url.hostname, url.port,
:use_ssl => url.scheme == 'https' ) {|http|
http.request(req)
}
end
From the docs:
post_form(url, params)
Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.
That means Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) fixes it. You are currently sending a string as url instead of a URI.

Mocking a post request with binary data in Sinatra

I have an endpoint in my Sinatra application that will be receiving binary data as part of the body. The other application sending it data will have a Faraday request that looks like this:
connection = Faraday.new(url: "https://example.com/post_data") do |conn|
conn.request :multipart
conn.adapter :net_http
conn.headers['Content-Type'] = 'octet/stream'
end
#response ||= connection.post do |req|
req.params = { :host => host,
:verification => "false"}
req.body = my_base64_encoded_binary
end
Then, in Sinatra, I will have an endpoint that receives those request parameters and binary data and passes it along to a model, like so:
post '/post_data' do
request.body.rewind
payload = request.body.read
raise Sinatra::NotFound unless payload and params[:host]
output = MyOutput.new(params, payload)
content_type 'application/json'
body output.data
end
When I try to test this endpoint using Rack::Test helpers, I end up in a weird situation. I can't seem to create the proper mock in order to test this endpoint properly. Based on some manual testing with PostMan, I'm pretty certain my endpoint works properly. It's just the test that's holding me up. Here is the spec:
it "should return a json response" do
post(url, :host => host, :verification => "false") do |req|
req.body = [my_binary]
req.content_type = "application/octet-stream"
end
expect(last_response.status).to eq(200)
expect(last_response.content_type).to eq("application/json")
end
And when I inspect what the incoming request looks like in the test, it does not contain a proper body. params is properly set to the host and verification settings I set, but the body is also being set to the params (inspected through payload = request.body.read) instead of the binary.
Is there a different way to set up the mock so that the binary properly is set to the body of the request, and the parameters are still set to the request parameters properly?
The answer is that the only way to post the body is where I was adding the params in the rack test helper. I needed to take the params and move them into the query string of the URL in the request, and then only add the binary as the body of the post request in the test helper.
let(:url) { "http://example.com/post_data?host=>#{host}&verification=#{verification}" }
it "should return a json response" do
post(url, my_binary)
expect(last_response.status).to eq(200)
expect(last_response.content_type).to eq("application/json")
end

How can I wrap every request with RestClient to use :verify_ssl => false?

I'm trying
RestClient.add_before_execution_proc do |req, params|
req(:verify_ssl => false)
end
It isn't working.
How can I wrap every request with RestClient to use :verify_ssl => false?
Use the params instead of req. Those params will be passed to RestClient.execute, which is the entry point for all requests.
RestClient.add_before_execution_proc do |req, params|
params[:verify_ssl] = false
end
You can also consider creating a simple class to encapsulate your needs and execute a get/post/... rest without verify_ssl
(check https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb#L160)
I'd write my own helper wrapper for the RestClient calls rather than modifying global state, since global changes make it hard for someone reading your code to understand what's happening.
For example:
def insecure_restclient_get(url, headers={}, &block)
RestClient::Request.execute(verify_ssl: false, method: :get, url: url, headers: headers, &block)
end
def insecure_restclient_post(url, payload, headers={}, &block)
RestClient::Request.execute(verify_ssl: false, method: :post, url: url, payload: payload, headers: headers, &block)
end
Also note that if you're setting verify_ssl: false you might as well not be using https at all.
In response to #alb-i986's comment for #AlexandreAngelim's answer, I've written the follow monkey patch that wraps the constructor of RestClient::Request class instead so it would work with the latest RestClient:
module RestClient
class Request
orig_initialize = instance_method(:initialize)
define_method(:initialize) do |args|
args[:verify_ssl] = false
orig_initialize.bind(self).(args)
end
end
end

HTTP Basic Authentication as default in Ruby HTTP

Using Ruby 2.2.3 I'm looking for a way to use HTTP Basic Authentication for every request. I know it is possible to define basic_auth per request:
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
request.basic_auth("username", "password")
response = http.request(request)
How can I "globally" use basic_auth?
basic_auth is an instance method of an Net::HTTP object. For convenience you could define a class with the desired basic_auth settings baked-in or given as arguments.
Simple example:
require 'uri'
require 'net/http'
class SimpleHTTP
def initialize uri, user = "default_user", pass = "default_pass"
#uri = URI.parse(uri)
#username = user
#password = pass
end
def request path=nil
#uri.path = path if path # use path if provided
http = Net::HTTP.new(#uri.host, #uri.port)
req = Net::HTTP::Get.new(#uri.request_uri)
req.basic_auth(#username, #password)
http.request(req)
end
end
# Simple Example
http = SimpleHTTP.new('http://www.yoursite.com/defaultpath')
http.request
# Override default user/pass
http2 = SimpleHTTP.new('http://www.yoursite.com/defaultpath', "myusername", "mypass")
http2.request
# Provide path in request method
http = SimpleHTTP.new('http://www.yoursite.com/')
http.request('/some_path')

Request headers in Goliath middleware

I am writing own middleware for Goliath server.
How can I get request headers in "call" method?
Thanks!
"call" method always return [status_code, headers, body] tuple, see example below:
class AwesomeMiddleware
include Goliath::Rack::AsyncMiddleware
def call(env)
status, headers, response = super(env)
[status, headers, response]
end
end
Also checkout AsyncMiddleware and SimpleAroundwareFactory in Goliath repository.

Resources