Faraday::RackBuilder methods - ruby

in order to use custom middlewares from faraday docs I see that I have to use the use method. In my use case my custom builder just add a jwt auth token in the header:
Faraday.new(url: wsconfig.base_url) do |builder|
builder.use CustomMiddlewares::JwtAuthentication
builder.request :url_encoded
builder.response :json
builder.adapter :net_http
end
jwt_authentication.rb
require 'jwt'
module CustomMiddlewares
class JwtAuthentication < Faraday::Middleware
def call(env)
payload = RequestStore.store[:jwt_claims].to_h.merge({method: env.method, path: env.url.request_uri})
token = jwt(payload)
Rails.logger.debug { " with token: #{token}" }
env[:request_headers]["Authorization"] = "Token: #{token}"
#app.call(env)
rescue StandardError => e
raise "problem in JwtAuthentication Middleware"
end
private
def jwt(payload, expiration = 1.minute.from_now)
payload = payload.dup
payload['exp'] = expiration.to_i
payload['iss'] = 'cgp'
JWT.encode(payload, key, 'RS256')
end
def key
OpenSSL::PKey::RSA.new(Rails.configuration.x.secrets.ws_config.jwt_private_key)
end
end
end
CustomMiddlewares::JwtAuthentication should only be used on request phase like url_encoded middleware which is adde by request method. I wonder why I cannot do the same with mine:
builder.request CustomMiddlewares::JwtAuthentication
I got:
CustomMiddlewares::VerbosingPseudonymizationWs is not registered on Faraday::Request (Faraday::Error)

If you want to use builder.request you first need to register the middleware like this:
Faraday::Request.register_middleware jwt: -> { CustomMiddlewares::JwtAuthentication }
Afterwards you should be able to call builder.request :jwt. This is due to Faraday::RackBuilder#request essentially calling Faraday::RackBuilder#use with Faraday::Request.lookup_middleware(key) as the first parameter.
See https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L92
and https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb#L228
This also means that there is no difference between builder.request :jwt and builder.use CustomMiddlewares::JwtAuthentication.
The difference between a request and a response middleware is that the response middlewares should inherit from Faraday::Response::Middleware which makes sure that they only execute on the response (on_complete). See https://github.com/lostisland/faraday/blob/master/lib/faraday/response.rb#L8
I.e., even when registering a middleware via builder.request it can still act on the response if it implements the on_complete callback. Conversely you do not execute any code for the response if you do not implement the callback.

Related

Authentication problem in Ruby Sinatra web app

I have a simple Sinatra app with two controllers and api helper
# ApplicationController
class ApplicationController < Sinatra::Base
register Sinatra::ActiveRecordExtension
helpers ApiHelper
configure :production, :development do
enable :logging
end
before do
content_type :json
end
get '/hello' do
{ message: 'Hello!' }.to_json
end
end
# ArticlesController
class ArticlesController < ApplicationController
before do
authenticate!
end
get '/articles' do
articles = Article.all
articles.map { |article| serialize(article) }
end
...
end
# ApiHelper
module ApiHelper
def authenticate!
halt 403, { message: 'Unauthorized!' }.to_json unless authorized?
end
private
def authorized?
request.env['HTTP_AUTHORIZATION'] == 'Bearer 123qweasd'
end
end
When I do
curl -X GET -i -H 'Accept: application/json' http://localhost:4567/hello
to do helth check I get 403 Unauthorized. Why? I don't require authentication in /hello endpoint, only in /articles CRUD endpoints so I don't understand why it authenticates in /hello. According to the docs before block is used to perform some action before other action runs but I don't call authenticate! in before block in ApplicationController. What am I missing?
It turned out that not knowingly I was using ArticlesController as a middleware. My config.ru looked like this.
run ApplicationController
use ArticlesController
which made it so that the authenticate! was called before every request.
I changed my config.ru to this:
map '/' do
run ApplicationController
end
map '/articles' do
run ArticlesController
end
And it works.

Post to Sinatra API with hyperresource

I built an app in Sinatra with Roar (hal/JSON) and now I'm trying to post a new item from my client to this API.
In my Sinatra app I have routes like these:
get '/todos/new' do
#pagetitle = 'New Todo'
#todo = Todo.new
#todo.extend(TodoRepresenter)
#todo.to_json
erb :'todos/new'
end
post "/todos" do
#todo = Todo.new(params[:todo])
if #todo.save
redirect "todos/#{#todo.id}"
else
erb :"todos/new"
end
end
And my client.rb looks like this:
require 'hyperresource'
class ApiRequest < HyperResource
api = ApiRequest.new(root: 'http://127.0.0.1:9393',
headers: {'Accept' => 'application/vnd.http://127.0.0.1:9393.v1+json'})
api.post '/todos/new', { :title => "a"}
This doesn't work. The only way of get a working client is the get function:
require 'hyperresource'
class ApiRequest < HyperResource
api = ApiRequest.new(root: 'http://127.0.0.1:9393/todos/13',
headers: {'Accept' => 'application/vnd.http://127.0.0.1:9393.v1+json'})
todo = api.get
output = todo.body
puts output
I don't know how to solve this, and the Github page doesn't tell me either.
I changed the API slightly:
get '/todos' do
#pagetitle = 'New Todo'
#todo = Todo.new
erb :'todos/new'
end
post "/todos/new" do
#todo = Todo.new(params[:todo])
#todo.extend(TodoRepresenter)
#todo.to_json
if #todo.save
redirect "todos/#{#todo.id}"
else
erb :"todos/new"
end
end
and also the way of posting in my client:
todo = api.get.new_todo.post(title: "Test")
In my API console I now get:
D, [2014-11-04T17:11:41.875218 #11111] DEBUG -- : Todo Load (0.1ms) SELECT "todos".* FROM "todos" WHERE "todos"."id" = ? LIMIT 1 [["id", 0]]
ActiveRecord::RecordNotFound - Couldn't find Todo with 'id'=new:
and a lot of other code.
In my client console I get a lot of code, with a hyper resource server error and a lot of other code.

How to test the request (not response) of a Rack middleware?

I can see how to test the response of a rack middleware, but how to test the request?
That is, how to test when the middleware changes the request going to the app?
Working in RSpec and Sinatra.
I presume you're meaning testing whether it's changing env...
A middleware goes something like:
class Foo
def initialize(app)
#app = app
end
def call(env)
# do stuff with env ...
status, headers, response = #app.call(env)
# do stuff with status, headers and response
[status, headers, response]
end
end
You could initialize it with a bogus app (or a lambda, for that matter) that returns a dummy response after doing some tests:
class FooTester
attr_accessor :env
def call(env)
# check that env == #env and whatever else you need here
[200, {}, '']
end
end
#Denis's answer would work, but I'd personally prefer an alternative, which is to put the middleware in a bare Rack app (be it Sinatra or whatever) and just pass the request on as the response and test that. It's how most Rack middleware is specced. That, and unit testing the internals of the middleware.
For example, it's what I've done here with a fork of Rack Clicky
Edit: testing middleware separately from the main app.
require 'lib/rack/mymiddelware.rb'
require 'sinatra/base'
describe "My Middleware" do
let(:app) {
Sinatra.new do
use MyMiddleware
get('/') { request.env.inspect }
end
}
let(:expected) { "Something you expect" }
before do
get "/"
end
subject { last_response.body }
it { should == expected }
end

Handle the PUT method in WEBrick

How do I handle PUT requests in WEBrick?
I have tried defining a do_PUT() method in an AbstractServlet class but the method is never invoked.
I had the same problem and got it working by creating my own custom WEBrick::HTTPProxyServer and adding the put method in that.
require "webrick"
require "webrick/httpproxy"
require 'cgi'
class CustomWEBrickProxyServer < WEBrick::HTTPProxyServer
def do_PUT(req, res)
perform_proxy_request(req, res) do |http, path, header|
http.put(path, req.body || "", header)
end
end
# This method is not needed for PUT but I added for completeness
def do_OPTIONS(req, res)
res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT,PUT"
end
end
Then you need to start your proxy server using your own Custom class.
my_proxy_server = CustomWEBrickProxyServer.new :Port=> proxy_port,
:ProxyVia => forward_proxy,
:ProxyURI => forward_proxy,
:RequestCallback => method(:request_callback),
:ProxyContentHandler => method(:response_callback),
:AccessLog => method(:access_log)

Thin, Sinatra, and intercepting static file request to do CAS authentication

I'm using the casrack-the-authenticator gem for CAS authentication. My server is running Thin on top of Sinatra. I've gotten the CAS authentication bit working, but I'm not sure how to tell Rack to intercept "/index.html" requests to confirm the CAS login, and if the user is not allowed to view the page, return a HTTP 403 response instead of serving the actual page. Does anyone have experience with this? Thanks.
My app:
class Foo < Sinatra::Base
enable :sessions
set :public, "public"
use CasrackTheAuthenticator::Simple, :cas_server => "https://my.cas_server.com"
use CasrackTheAuthenticator::RequireCAS
get '/' do
puts "Hello World"
end
end
My rackup file:
require 'foo'
use Rack::CommonLogger
use Rack::Lint
run Foo
Initial attempt at getting Rack to understand authentication in its file service (comments and thoughts welcome):
builder = Rack::Builder.new do
map '/foo/index.html' do
run Proc.new { |env|
user = Rack::Request.new(env).session[CasrackTheAuthenticator::USERNAME_PARAM]
[401, { "Content-Type" => "text/html" }, "CAS Authentication Required"] unless user
# Serve index.html because we detected user
}
end
map '/foo' do
run Foo
end
end
run builder
Casrack-the-Authenticator will put the CAS information into the Rack session. You can pull that out in another piece of Rack middleware or in your Sinatra app.
The following is for a Rails application, but the concept is similar for Sinatra or a Rack middleware:
# in app/controllers/application_controller.rb:
protected
def require_sign_in!
render :nothing => true, :status => 403 unless signed_in?
end
def signed_in?
current_user.present?
end
def current_user
#current_user ||= Person.find_by_username(session[CasrackTheAuthenticator::USERNAME_PARAM])
end

Resources