Selectively allow some urls through Rack::Auth::Basic - ruby

I've set up a blog that I'd like to be minimally secured (i.e., I just want to keep out random people I don't know, I'm not trying to implement NSA-like security measures). I'm using toto with Rack::Auth::Basic to "secure" the site. I'd like to let through index.xml so that blog readers will be able to read the feed without dealing with password (and yes, I know that this is a big hole in my "security").
How do I let through this one url with Rack::Auth::Basic?
This is how I added basic auth to my site:
use Rack::Auth::Basic, "blog" do |username, password|
[username, password] == ['generic', 'stupidanddumbpassword']
end

How about some good ol' fashioned inheritance? Rack::Auth::Basic is a simple rack app (source: https://github.com/rack/rack/blob/master/lib/rack/auth/basic.rb), so it's possible to override the #call method and skip authentication when the request path matches '/index.xml':
class BlogAuth < Rack::Auth::Basic
def call(env)
request = Rack::Request.new(env)
case request.path
when '/index.xml'
#app.call(env) # skip auth
else
super # perform auth
end
end
end
use BlogAuth, "blog" do |username, password|
[username, password] == ['generic', 'stupidanddumbpassword']
end
For more background on rack, check out: http://rack.rubyforge.org/doc/SPEC.html
I haven't tried #Iain's suggestion about Rack::URLMap, but it looks like it could also be a good option.

Thanks for the answer!
I used this solution too, but made a small change.
because the current solution will probably result in a duplication of code if an app will require more then one path to be accessible,
I changed the code to:
class AppBasicAuth < Rack::Auth::Basic
def call(env)
request = Rack::Request.new(env)
allowed_paths = ['/api/v2/get_new.json']
if allowed_paths.include? request.path
#app.call(env) # skip auth
else
super # perform auth
end
end
end

Related

how to refactor IEX exchange data calls for grabbing data from api in ruby program

I am using the iex exchange api for grabbing info about stocks. It's working great but my code is very ugly was wondering how to refactor the code.
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
This is what is needed to grab the info. The problem is that I have to put the code above in every method that utilizes the api. For instance,
def self.stock_price(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
company = stock.company(ticker)
quote = stock.quote(ticker.upcase)
puts "#{company.company_name}: #{quote.latest_price}"
end
def self.week_52_high(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
key_stats = stock.key_stats(ticker)
puts "52-week high: #{key_stats.week_52_high}"
end
def self.week_52_low(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
key_stats = stock.key_stats(ticker)
puts "52-week low: #{key_stats.week_52_low}"
end
Is there any way to factor that call out to a different file and call the method that way? The code is very repetitive as is. The "stock" variable is what i need to actually work with, should I made that a global variable? I've heard that's a no-no but is this case an exception? Also, where i have
publishable_token: token,
that token variable is actually my actual, hard-coded token, not the "token" variable you see, I simply changed it for security issues. What should I do instead of hard-coding it? The documentation says to turn it into an environment variable but i dont know what that means. Thanks in advance!
What should I do instead of hard-coding it? The documentation says to
turn it into an environment variable but i dont know what that means.
An environment variable is a variable whose value is set outside the the application, typically through functionality built into the operating system or shell. You need to check the documentation for your setup to see how to set env vars.
You can get env vars in Ruby through the ENV hash.
ENV['FOO']
ENV.fetch('FOO') # will raise a KeyError if it is not set instead of just returning nil
Rails 5.2 and up have secure credentials that can be used instead. It stores your credentials in an encrypted YAML file that can be checked into source control.
How do I refactor this?
One way to refactor this would be to use delegation instead of bunch of largely static methods:
require 'forwardable'
class MyClient
extend Forwardable
TOKEN = ENV.fetch('IEX_API_TOKEN')
ENDPOINT = ENV.fetch('IEX_API_ENDPOINT', 'https://sandbox.iexapis.com/v1')
def_delegators :#client, :company, :quote, :key_stats
def initialize(publishable_token: TOKEN, endpoint: ENDPOINT, client: nil)
# This is know as constructor injection and makes it easy to mock out
# the dependency in tests
#client = client || IEX::Api::Client.new(publishable_token: TOKEN, endpoint: ENDPOINT)
end
def stock_price(ticker)
company_name = company(ticker).company_name
price = quote(ticker.upcase).latest_price
puts "#{company_name}: #{price}"
end
def week_52_high(ticker)
puts "52-week high: #{key_stats(ticker).week_52_high}"
end
def week_52_low(ticker)
puts "52-week low: #{key_stats(ticker).week_52_low}"
end
end
#client = MyClient.new
#client.week_52_low(ticker)

Facebook Graph API for websites using Ruby Koala gem in Sinatra

I want to implement Facebook login for web apps. All I need is the basic public information of a user for the account creation steps.
This is what I have done:
Created a basic Facebook app with nearly no custom permissions.
Used the APP_ID and APP_SECRET in Koala to get access_token.
Everything worked perfectly, I am able to login/logout.
Just that the only information I am able to get back when I do: graph.get_object('me') is the logged in user's name and an id (It doesn't look like the default Facebook id).
Surprised whether something changed in the new API, I tested the gem in the console using the access_token from graph explorer (where all permissions are enabled by default). And I get all data using the same method call.
When I review what all the app gets while signing up; I see that the user's basic information, profile pic and other public data will be accessible to the app.
Any idea why this is so? It seems I am missing something obvious. The code is available in Github. But this is pretty much everything to it:
require 'bundler'
Bundler.require :default
Dotenv.load '.env'
require_relative './app/constants.rb'
module Banana
class App < Sinatra::Base
use Rack::Session::Cookie, secret: COOKIE_SECRET
set :public_folder, File.dirname(__FILE__) + '/bower_components'
get '/' do
if logged_in?
haml :welcome_in, layout: :layout
else
haml :log_in, layout: :layout
end
end
get '/log_out' do
session['oauth'] = nil
session['access_token'] = nil
redirect '/'
end
get '/log_in' do
session['oauth'] = Koala::Facebook::OAuth.new(APP_ID, APP_SECRET, "#{request.base_url}/call_back")
redirect session['oauth'].url_for_oauth_code()
end
get '/call_back' do
begin
session['access_token'] = session['oauth'].get_access_token(params[:code])
rescue
redirect '/?error=user_denied'
end
redirect '/'
end
get '/test' do
if logged_in?
p graph.get_object("rakeshbs")
"e"
else
redirect '/'
end
end
def logged_in?
!session['access_token'].nil?
end
def toggle_access
logged_in? ? '/log_out' : '/log_in'
end
def graph
#graph ||= Koala::Facebook::API.new(session['access_token'])
end
def errored?
!params["error"].nil?
end
def user
p graph.get_connections(:me, :photos) # This is just nil
#user ||= OpenStruct.new(
name: graph.get_object("me")["name"], # All I get here is just a hash with the name and an id!
photo: 'http://semantic-ui.com/images/avatar/small/elliot.jpg'
)
end
end
end
You should add fields parameter.
Something like this:
graph.get_object('me', { fields: 'id,first_name,last_name,gender,birthday,photos,email' })

Using specific VCR cassette based on request

Situation: testing a rails application using Rspec, FactoryGirl and VCR.
Every time a User is created, an associated Stripe customer is created through Stripe's API. While testing, it doesn't really makes sense to add a VCR.use_cassette or describe "...", vcr: {cassette_name: 'stripe-customer'} do ... to every spec where User creation is involved. My actual solution is the following:
RSpec.configure do |config|
config.around do |example|
VCR.use_cassette('stripe-customer') do |cassette|
example.run
end
end
end
But this isn't sustainable because the same cassette will be used for every http request, which of course is very bad.
Question: How can I use specific fixtures (cassettes) based on individual request, without specifying the cassette for every spec?
I have something like this in mind, pseudo-code:
stub_request(:post, "api.stripe.com/customers").with(File.read("cassettes/stripe-customer"))
Relevant pieces of code (as a gist):
# user_observer.rb
class UserObserver < ActiveRecord::Observer
def after_create(user)
user.create_profile!
begin
customer = Stripe::Customer.create(
email: user.email,
plan: 'default'
)
user.stripe_customer_id = customer.id
user.save!
rescue Stripe::InvalidRequestError => e
raise e
end
end
end
# vcr.rb
require 'vcr'
VCR.configure do |config|
config.default_cassette_options = { record: :once, re_record_interval: 1.day }
config.cassette_library_dir = 'spec/fixtures/cassettes'
config.hook_into :webmock
config.configure_rspec_metadata!
end
# user_spec.rb
describe :InstanceMethods do
let(:user) { FactoryGirl.create(:user) }
describe "#flexible_name" do
it "returns the name when name is specified" do
user.profile.first_name = "Foo"
user.profile.last_name = "Bar"
user.flexible_name.should eq("Foo Bar")
end
end
end
Edit
I ended doing something like this:
VCR.configure do |vcr|
vcr.around_http_request do |request|
if request.uri =~ /api.stripe.com/
uri = URI(request.uri)
name = "#{[uri.host, uri.path, request.method].join('/')}"
VCR.use_cassette(name, &request)
elsif request.uri =~ /twitter.com/
VCR.use_cassette('twitter', &request)
else
end
end
end
VCR 2.x includes a feature specifically to support use cases like these:
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/before-http-request-hook!
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/after-http-request-hook!
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/around-http-request-hook!
VCR.configure do |vcr|
vcr.around_http_request(lambda { |req| req.uri =~ /api.stripe.com/ }) do |request|
VCR.use_cassette(request.uri, &request)
end
end
IMO, libraries like this should provided you with a mock class, but w/e.
You can do your pseudocode example already with Webmock, which is the default internet mocking library that VCR uses.
body = YAML.load(File.read 'cassettes/stripe-customer.yml')['http_interactions'][0]['response']['body']['string']
stub_request(:post, "api.stripe.com/customers").to_return(:body => body)
You could put that in a before block that only runs on a certain tag, then tag the requests that make API calls.
In their tests, they override the methods that delegate to RestClient (link). You could do this as well, take a look at their test suite to see how they use it, in particular their use of test_response. I think this is a terribly hacky way of doing things, and would feel really uncomfortable with it (note that I'm in the minority with this discomfort) but it should work for now (it has the potential to break without you knowing until runtime). If I were to do this, I'd want to build out real objects for the two mocks (the one mocking rest-client, and the other mocking the rest-client response).
The whole point (mostly anyway) of VCR to just to replay the response of a previous request. If you are in there picking and choosing what response goes back to what request, you are quote/unquote doing-it-wrong.
Like Joshua already said, you should use Webmock for something like this. That's what VCR is uing behind the scenes anyway.

How do I do a really simple Sinatra LDAP authentication?

I looked at the Sinatra docs and they only seem to reference HTTP authentication. I'm looking for a really simple way to control access to routes based on a user being authorised/authenticated via an LDAP server.
I've already built a class that does the LDAP bit and returns an LDAP object if the user has successfully authenticated and nil if they haven't:
>>DirectoryUser.authenticate('user', 'password')
#<DirectoryUser:0x007ffb589a2328>
I can use this to determine if they've successfully authenticated or not.
As a next step I want to splice this into a simple Sinatra app that provides a form to collect the LDAP user and password:
require 'directoryUser'
require 'sinatra'
enable :sessions
get '/form' do
username = params[:username]
password = params[:password]
haml :form
end
Then I want to only allow routes if the 'DirectoryUser' object exists:
get '/protected' do # Only if DirectoryUser object exists
"This route is protected"
end
get '/unprotected' do
"This route is unprotected"
end
I've spent hours trying to find an answer to this but so far and can't seem to find anything that works for me.
I'd probably go with something like this:
require 'directoryUser'
require 'sinatra'
enable :sessions
helpers do
def authorize!
redirect(to('/login')) unless session[:user_id]
end
end
get '/login' do
haml :login # with the login form
end
post '/login' do
user = DirectoryUser.authenticate(params[:username], params[:password])
if user
session[:user_id] = user.id
# Or: session[:logged_in] = true, depending on your needs.
redirect to('/protected')
else
redirect to('/login')
end
end
get '/protected' do
authorize!
'This route is protected'
end
get '/unprotected' do
'This route is unprotected'
end

Calling Sinatra from within Sinatra

I have a Sinatra based REST service app and I would like to call one of the resources from within one of the routes, effectively composing one resource from another. E.g.
get '/someresource' do
otherresource = get '/otherresource'
# do something with otherresource, return a new resource
end
get '/otherresource' do
# etc.
end
A redirect will not work since I need to do some processing on the second resource and create a new one from it. Obviously I could a) use RestClient or some other client framework or b) structure my code so all of the logic for otherresource is in a method and just call that, however, it feels like it would be much cleaner if I could just re-use my resources from within Sinatra using their DSL.
Another option (I know this isn't answering your actual question) is to put your common code (even the template render) within a helper method, for example:
helpers do
def common_code( layout = true )
#title = 'common'
erb :common, :layout => layout
end
end
get '/foo' do
#subtitle = 'foo'
common_code
end
get '/bar' do
#subtitle = 'bar'
common_code
end
get '/baz' do
#subtitle = 'baz'
#common_snippet = common_code( false )
erb :large_page_with_common_snippet_injected
end
Sinatra's documentation covers this - essentially you use the underlying rack interface's call method:
http://www.sinatrarb.com/intro.html#Triggering%20Another%20Route
Triggering Another Route
Sometimes pass is not what you want, instead
you would like to get the result of calling another route. Simply use
call to achieve this:
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end
I was able to hack something up by making a quick and dirty rack request and calling the Sinatra (a rack app) application directly. It's not pretty, but it works. Note that it would probably be better to extract the code that generates this resource into a helper method instead of doing something like this. But it is possible, and there might be better, cleaner ways of doing it than this.
#!/usr/bin/env ruby
require 'rubygems'
require 'stringio'
require 'sinatra'
get '/someresource' do
resource = self.call(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/otherresource',
'rack.input' => StringIO.new
)[2].join('')
resource.upcase
end
get '/otherresource' do
"test"
end
If you want to know more about what's going on behind the scenes, I've written a few articles on the basics of Rack you can read. There is What is Rack? and Using Rack.
This may or may not apply in your case, but when I’ve needed to create routes like this, I usually try something along these lines:
%w(main other).each do |uri|
get "/#{uri}" do
#res = "hello"
#res.upcase! if uri == "other"
#res
end
end
Building on AboutRuby's answer, I needed to support fetching static files in lib/public as well as query paramters and cookies (for maintaining authenticated sessions.) I also chose to raise exceptions on non-200 responses (and handle them in the calling functions).
If you trace Sinatra's self.call method in sinatra/base.rb, it takes an env parameter and builds a Rack::Request with it, so you can dig in there to see what parameters are supported.
I don't recall all the conditions of the return statements (I think there were some Ruby 2 changes), so feel free to tune to your requirements.
Here's the function I'm using:
def get_route url
fn = File.join(File.dirname(__FILE__), 'public'+url)
return File.read(fn) if (File.exist?fn)
base_url, query = url.split('?')
begin
result = self.call('REQUEST_METHOD' => 'GET',
'PATH_INFO' => base_url,
'QUERY_STRING' => query,
'rack.input' => StringIO.new,
'HTTP_COOKIE' => #env['HTTP_COOKIE'] # Pass auth credentials
)
rescue Exception=>e
puts "Exception when fetching self route: #{url}"
raise e
end
raise "Error when fetching self route: #{url}" unless result[0]==200 # status
return File.read(result[2].path) if result[2].is_a? Rack::File
return result[2].join('') rescue result[2].to_json
end

Resources