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

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

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.

How use sessions with Whats app cloud api?

Need to build chat bot with whatsapp, so i use Whats app cloud api + Sinatrarb.
When i need to send the session its sends perfectually, but its doesnt work
class WhatsAppSender < Sinatra::Base
configure :development do
register Sinatra::Reloader
enable :sessions
set :session_secret, "secret"
set :session_store, Rack::Session::Pool
end
configure do
enable :sessions
set :session_secret, "secret"
set :session_store, Rack::Session::Pool
end
post '/bot' do
request.body.rewind
body = JSON.parse request.body.read
# puts body
puts session[:answer]
if body['entry'][0]['changes'][0]['value'].include?("messages")
user_text = body['entry'][0]['changes'][0]['value']['messages'][0]['text']['body']
case
when user_text == "hi"
# puts session[:answer]
response = HTTP.auth("Bearer mytoken")
.headers(:accept => "application/json")
.post("https://graph.facebook.com/v14.0/myid/messages",
:json => { messaging_product: "whatsapp",
recipient_type: "individual",
to: "mynumber",
type: "text",
some_text: "some text",
text: { preview_url:
false,
body: "hi man"}
})
session[:answer] = "booking_amount"
puts response
when session[:answer] == "booking_amount"
puts "session works"
when user_text.downcase == "d"
session.clear
puts "cleared"
end
end
end
end
then i inspect request, sinatra session works fine, set the cookie
Set-Cookie rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Npb25JZAY6D0BwdWJsaWNfaWRJIkViNThiYmJjMjdmYjI4MGU0ZTMxMDY4NzE4MDllOWVhYTBlNTVlM2UwMjg4ZWE3OWRiMjVmYTlkNThmZjczNzI3BjsARkkiCWNzcmYGOwBGSSIxbEJydTFnUm5tSTVCOVZUT1pZelNwSFY3a3ZUNGxiVmVHS2FlZVFvYVJsOD0GOwBGSSINdHJhY2tpbmcGOwBGewZJIhRIVFRQX1VTRVJfQUdFTlQGOwBUSSItNDJiNzI3MTFjNTdmZDA5YTk1MjY0NmY0N2Q0YWJjMjk0ODk5OTZhMQY7AEY%3D--f88a49537f00d65faf60da839b05d847d47f288d; path=/; HttpOnly
But its doesn't work... Maybe i should use another language or framework? please help.

Rack Middleware in Rack Middleware?

I am trying to build a rack middleware GEM that uses rack middleware itself (RackWired).
I have an existing application, whose config.ru uses Rack::Builder. In that block (Rack::Builder), I would like to specify my middleware, and when it is called to use a third party middleware (rack-cors) inside my own to do some stuff. Confusing I know.
The problem is that Rack::Builder's context is in config.ru and my middleware (RackWired) is thus unable to access it to "use" the third party middleware (rack-cors).
The intent of my effort is here
Is there a way to use middleware within middleware?
Thank you
Right, I'm not entirely sure what you're trying to do. But you can do this
class CorsWired
def initialize(app)
#app = app
end
def call(env)
cors = Rack::Cors.new(#app, {}) do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options, :delete], :credentials => false
end
end
cors.call(env)
end
end
Your config.ru should have use CorsWired though, not use CorsWired.new
This is I think what you were asking but I think you're missing the point of middleware. You should just change your config.ru to use rack-cors before/after your middleware depending on what you want to do.
require 'rack'
require 'rack/cors'
require './cors_wired'
app = Rack::Builder.new do
use Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options, :delete], :credentials => false
end
end
use CorsWired
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
end
run app

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

Base protocol (https://) for every render/redirect_to call

is there a way i can set up a base protocol to use
render :action => "myaction"
redirect_to :action => "myaction"
instead of calling
render :action => "myaction", :protocol => "https://"
redirect_to :action => "myaction", :protocol => "https://"
every time?
Simply use config.force_ssl = true in your environment configuration.
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = true
end
end
You can also selectively enable https depending on the current Rails environment. For example, you might want to keep HTTPS turned off on development, and enable it on staging/production.
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = false
end
end
# config/environments/production.rb
MyApp::Application.configure do
config.force_ssl = true
end
Behind the scenes, Rails adds the awesome Rack::SSL Rack middleware to your application middleware stack. Rack::SSL automatically filters the request, redirects not-HTTPS requests to the corresponding HTTPS path and applies some additional improvements to make sure your HTTPS request is secure.

Resources