Building middleware that grabs the IP - ruby-on-rails

I'm trying to write my firsts rack middleware. And need help. I want middleware that find the requestor's IP and if it is in the allowed list of IP continues the request, otherwise it aborts.
The tricky part is I need this to work on heroku where you can't use request.ip: http://developerhemal.tumblr.com/post/3958107290/client-ip-addresses-on-heroku
So I have the following:
class RestrictIP
def initialize(app, message = "Hello")
#app = app
#message = message
end
def call(env)
dup._call(env)
#ip = env['HTTP_X_REAL_IP'] ||= env['REMOTE_ADDR']
end
def each(&block)
block.call("<!-- #{#ip} -->\n")
#response.each(&block)
end
end
This errors with:
NoMethodError (You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]=):
For my first itteration I just want to make sure I can grab and put the requestor's IP on the html doc.
Any rack middleware experts out there? Thanks

As per this post, it appears you could use the following:
ip = env[‘HTTP_X_REAL_IP’] ||= env[‘REMOTE_ADDR’]
I haven't tested this as I'm not on Heroku.

Related

Building Rack Middleware responses with Flash message functionality

I have a Sinatra app that's mounted on a Rails app under /admin. The Sinatra app is an admin dashboard, and therefore should only be available to authorized users.
To enforce that, I built a piece of Rack Middleware that will run before the Sinatra app is called.
The logic is simple -
If user is authenticated, continue as normal
If user is not authenticated, redirect to the root path with a flash alert message (I'm using the rack-flash gem to allow access to the flash messages in Rack)
Code below. I feel like I'm missing something in the redirect method. The Rack::Builder block constructs a mini-Rack application and the block inside further creates another Rack application (the Proc) that builds the Redirect Response with flash message.
When I run it, I get undefined method 'detect' for nil:NilClass, which indicates that neither block is returning a valid non-nil response. Do I need to run call somewhere on one of these blocks?
I'm using a Puma Webserver if that helps.
Thanks!
require "rack"
require "rack-flash"
class AdminAuthorizer
def initialize(app)
#app = app
end
def call(env)
#env = env
id = #env["rack.session"][:user_id]
user = User.where(id: id).first
# Check if user is authorized, otherwise redirect
user.admin? ? ok : redirect
end
private
def ok
#app.call(#env)
end
def redirect
Rack::Builder.new do
use Rack::Flash, sweep: true, accessorize: true
run(
Proc.new do |env|
env["x-rack.flash"].alert = "Insufficient permissions"
res = Rack::Response.new
res.redirect("/")
res.finish
end
)
end
end
end
Ok, figured it out myself for anyone else that's curious.
I had to use the env key 'action_dispatch.request.flash_hash', which is used by the Flash middelware here
I didn't have to use the rack-flash gem, although I'm sure that's still useful when building Sinatra apps and such
NOTE: This is on Rails v4.2.4. I believe there have been several changes to that Flash module since, so I don't know if that key has changed. But you can confirm by searching the latest repo for a similar definition.
require "rack"
class AdminAuthorizer
FLASH = ActionDispatch::Flash
def initialize(app)
#app = app
end
def call(env)
#env = env
id = #env["rack.session"][:user_id]
user = User.where(id: id).first
# Check if user is authorized, otherwise redirect
user.admin? ? ok : redirect
end
private
def ok
#app.call(#env)
end
def redirect
# Calls a Rack application (the defined Proc). If you want to do more steps
# or get fancier, you can wrap this in a Rack::Builder call
#
# Rack::Builder.app(redirect_proc)
# use (blah)
# run (blah)
# end.call(#env)
#
redirect_proc.call(#env)
end
def redirect_proc
Proc.new do |env|
# Use the key 'action_dispatch.request.flash_hash' to set
# an new FlashHash object. This will overrite any existing FlashHash
# object, so use it carefully. You can probably avoid that by checking
# for an existing one and adding to it.
env[FLASH::KEY] = FLASH::FlashHash.new(alert: "Insufficient permissions")
# Construct the redirect response
res = Rack::Response.new
res.redirect("/")
res.finish
end
end
end

How do I modify the request object before routing in Rails in a testable way?

So, I have a situation where I need to determine something about a request before it is dispatched to any of the routes. Currently, this is implemented using several constraints that all hit the database, and I want to reduce the database hit to one. Unfortunately, doing it inline in routes.rb doesn't work, because the local variables within routes.rb don't get refreshed between requests; so if I do:
# Database work occurs here, and is then used to create comparator lambdas.
request_determinator = RequestDeterminator.new(request)
constraint(request_determinator.lambda_for(:ninja_requests)) do
# ...
end
constraint(request_determinator.lambda_for(:pirate_requests)) do
# ...
end
This works great on the first request, but then subsequent requests get routed as whatever the first request was. (D'oh.)
My next thought was to write a Rack middleware to add the "determinator" to the env hash, but there are two problems with this: first, it doesn't seem to be sticking in the hash at all, and specs don't even go through the Rack middleware, so there's no way to really test it.
Is there a simple mechanism I'm overlooking where I can insert, say, a hook for ActionDispatch to add something to the request, or just to say to Rails routing: "Do this before routing?"
I am using Rails 3.2 and Ruby 1.9.
One way to do this would be to store your determinator on the request's env object (which you have since ActionDispatch::Request is a subclass of Rack::Request):
class RequestDeterminator
def initialize(request)
#request = request
end
def self.for_request(request)
request.env[:__determinator] ||= new(request)
end
def ninja?
query_db
# Verify ninjaness with #request
end
def pirate?
query_db
# Verify piratacity with #request
end
def query_db
#result ||= begin
# Some DB lookup here
end
end
end
constraint lambda{|req| RequestDeterminator.for_request(req).ninja? } do
# Routes
end
constraint lambda{|req| RequestDeterminator.for_request(req).pirate? } do
# Routes
end
That way, you just instantiate a single determinator which caches your DB request across constraint checks.
if you really want to intercept the request,try rack as it is the first one to handle request in any Rails app...refer http://railscasts.com/episodes/151-rack-middleware to understand how rack works....
hope it helps.

how can I redefine the ruby and rails request.ip and request.remote_ip methods?

I've a rails app and I'm trying to overload the request.remote_ip and request.ip in order to use the cloudflare header (HTTP_CF_CONNECTING_IP) if it's present...
I've tried these, but none of them work:
module Rack
class Request
class << self
def ip
#ip ||= (#env['HTTP_CF_CONNECTING_IP'] || super)
end
end
end
end
module ActionDispatch
class Request < Rack::Request
class << self
def remote_ip
#remote_ip ||= (#env['HTTP_CF_CONNECTING_IP'] || super)
end
end
end
end
I can't use an extra method like
def connecting_ip
#env['HTTP_CF_CONNECTING_IP'] || request.remote_ip
end
in the application_controller because I've some other gems (like devise) which use request.ip
Thank you!
I believe request is an instance. But you are defining class methods. Remove the class << self nonsense and you instead be redefining instance methods.
Just a note though, this sounds kind of crazy. Be careful in there. Frameworks dont always like having their guts rearranged.
Your error message when using instance methods means something else is going on. super calls the superclass implementation. But when you reopen a class and override things, you are literally overwriting the original implementation. Since the method doesn't exist in the superclass, super doesn't work.
Instead you can use alias to save the original implementation before you declare the new method that would replace it.
module ActionDispatch
class Request < Rack::Request
alias :remote_ip_orig :remote_ip
def remote_ip
#remote_ip ||= (#env['HTTP_CF_CONNECTING_IP'] || remote_ip_orig)
end
end
end
If you you want to use CloudFlare and detect the true IP like I did without an Ngingx or Apache module, this monkey patching approach is a really bad idea, this will lead to unexpected results in the future. You'd be much better off using a Middleware as it should be used. Here is one that I came up with and have implemented.
module Rack
class CloudFlareFixup
def initialize(app)
#app = app
end
def call(env)
if env['HTTP_CF_CONNECTING_IP']
env['HTTP_X_FORWARDED_FOR'] = env['HTTP_CF_CONNECTING_IP']
env['REMOTE_ADDR'] = env['HTTP_CF_CONNECTING_IP']
end
#app.call(env)
end
end
end
Simply add this into your application.rb
config.middleware.insert_before(0, Rack::CloudFlareFixup)
You can see the complete Gist for this at https://gist.github.com/mattheworiordan/9024372
I tried many solutions to this problem. Here is what I found:
cloudflare-rails gem => this sets both ip and remote_ip to the originating ip. I wanted ip to represent the cloudflare ip that it came from, and remote_ip to represent the originating ip of the user, so that wouldn't work for me. It operates by looking up the list of Cloudflare IP ranges every 12 hours and adding those to trusted_proxies. It then patches: - ActionDispatch::Request.ip, ActionDispatch::Request.remote_ip and Rack::Attack::Request.trusted_proxy? with the cloudflare trusted proxies so that ip and remote_ip ignore them and use the originating ip of the user that initiated the request.
actionpack-cloudflare gem => this is much simpler. all it does is directly set ActionDispatch.remote_ip = #env['HTTP_CF_CONNECTING_IP'] which is in accordance with Cloudflare best recommended best practice, but it didn't seem worth it to use a gem for 1 line of code.
Hand Rolled
# config/initializers/cloudflare.rb
# These values should rarely or never change and Cloudflare should alert us before that happens.
# The list of Cloudflare proxy server ip ranges comes from https://www.cloudflare.com/ips/
CLOUDFLARE_IP_RANGES = [IPAddr.new("103.21.244.0/22"),IPAddr.new("103.22.200.0/22"),IPAddr.new("103.31.4.0/22"),IPAddr.new("104.16.0.0/12"),IPAddr.new("108.162.192.0/18"),IPAddr.new("131.0.72.0/22"),IPAddr.new("141.101.64.0/18"),IPAddr.new("162.158.0.0/15"),IPAddr.new("172.64.0.0/13"),IPAddr.new("173.245.48.0/20"),IPAddr.new("188.114.96.0/20"),IPAddr.new("190.93.240.0/20"),IPAddr.new("197.234.240.0/22"),IPAddr.new("2405:8100::/32"),IPAddr.new("2405:b500::/32"),IPAddr.new("2606:4700::/32"),IPAddr.new("2803:f800::/32"),IPAddr.new("2a06:98c0::/29"),IPAddr.new("2c0f:f248::/32")
]
# By adding the cloudflare IP ranges as trusted_proxies, rails ignores those IPs when setting remote_ip and correctly sets it to the originating IP
Rails.application.config.action_dispatch.trusted_proxies = CLOUDFLARE_IP_RANGES + ActionDispatch::RemoteIp::TRUSTED_PROXIES
Lastly, I wanted to block non-proxied requests
# config/initializers/rack_attack.rb
class Rack::Attack
class Request < ::Rack::Request
# Create a remote_ip method for rack request by setting it equal to the cloudflare connecting ip header
# To restore original visitor IP addresses at your origin web server, Cloudflare recommends your logs or applications
# look at CF-Connecting-IP instead of X-Forwarded-For since CF-Connecting-IP has a consistent format containing only one IP.
# https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-CloudFlare-handle-HTTP-Request-headers-
def remote_ip
#remote_ip ||= (env['HTTP_CF_CONNECTING_IP'] || ip).to_s
end
# This checks the request IP against Cloudflare IP ranges and the action dispatch default trusted proxies.
# These include various local IPs like 127.0.0.1 so that local requests won't be blocked.
def proxied?
Rails.application.config.action_dispatch.trusted_proxies.any? { |range| range === ip }
end
end
# Block all requests coming from non-Cloudflare IPs
blocklist("block non-proxied requests in production") do |req|
if req.proxied?
false
else
req.log :warn
true
end
end
end

How do you log the URL ActiveResource uses?

Rails ActiveResource is awesome ... except for one thing: as far as I can tell, there is no way to see what URL it is using behind the scenes. For instance, let's say I have an ActiveResource called Issue, for a webservice at myIssues.com/issues.xml. If I do:
Issue.find(:all, :params => {:page => 2})
I would expect that ActiveResource would make a call to:
myIssues.com/issues.xml?page=2
... but I don't actually know that. For all I know, ActiveResource could have decided it doesn't like the word "page", so it's actually using:
myIssues.com/issues.xml?mod_page=2
This makes debugging difficult. Right now I've got a situation where, if I go to the URL I think ActiveResource is using, it works just fine. However, when I actually use ActiveResource, it doesn't work. Seeing the URL it's GETing would be immensely helpful in this, so ...
Does anyone know a way to log (or otherwise output; if there's some resource.url method that would work great too) the URL(s) that ActiveResource uses to do its thing?
If you add the following line to your environment.rb file, it will at least log the requests so you know that URLs ActiveResource is hitting:
ActiveResource::Base.logger = ActiveRecord::Base.logger
I'm still searching for a better solution that shows me the response and the data posted to update calls, but at least this is a step in the right direction. I'm really not sure why ActiveResource has a separate logger to start with, but that's another matter.
I just ran into this same exact issue, and came across this post as I was looking for answers. What I did find, that proved useful, is the collection_path method on ActiveResource::Base. So for example, let's say you have the following resource:
class UserPost < ActiveResource::Base
self.site = "http://someApp.com/user/:user_id"
self.element_name = "post"
If you go to the rails console, here are some examples of the output:
>> UserPost.collection_path
"/user//post"
>> UserPost.collection_path(:user_id => 5)
"/user/5/post
This should provide you with exactly what you need to determine how ActiveResource is translating your request into a URL.
To get detail login for ActiveResource have to patch the request method inside the gem(method.
place bellow files inside config/initializers you will get http method, path, request body, request hedaers
response body and header is already there if you need. doc
config/initializers/activeresource_patch.rb
module ActiveResource
class Connection
private
def request(method, path, *arguments)
result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
payload[:method] = method
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
payload[:request_path] = path
payload[:request_body] = arguments[0]
payload[:request_headers] = arguments[1]
payload[:result] = http.send(method, path, *arguments)
end
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
rescue OpenSSL::SSL::SSLError => e
raise SSLError.new(e.message)
end
end
end
config/initializers/activeresource_logger.rb
Rails.application.configure do
def activeresource_logger
#activeresource_logger ||= Logger.new("#{Rails.root}/log/activeresource_logger.log")
end
ActiveSupport::Notifications.subscribe('request.active_resource') do |name, start, finish, id, payload|
if Rails.env.development?
activeresource_logger.info("====================== #{start} : #{payload[:method].upcase} ======================")
activeresource_logger.info("PATH: #{payload[:request_path]}")
activeresource_logger.info("BODY: #{payload[:request_body]}")
activeresource_logger.info("HEADERS: #{payload[:request_headers]}")
# activeresource_logger.info("STATUS_CODE: #{payload[:result].code}")
# activeresource_logger.info("RESPONSE_BODY: #{payload[:result].body}")
end
end
end

How do I access the Rack environment from within Rails?

I have a Rack application that looks like this:
class Foo
def initialize(app)
#app = app
end
def call(env)
env["hello"] = "world"
#app.call(env)
end
end
After hooking my Rack application into Rails, how do I get access to env["hello"] from within Rails?
Update: Thanks to Gaius for the answer. Rack and Rails let you store things for the duration of the request, or the duration of the session:
# in middleware
def call(env)
Rack::Request.new(env)["foo"] = "bar" # sticks around for one request
env["rack.session"] ||= {}
env["rack.session"]["hello"] = "world" # sticks around for duration of session
end
# in Rails
def index
if params["foo"] == "bar"
...
end
if session["hello"] == "world"
...
end
end
I'm pretty sure you can use the Rack::Request object for passing request-scope variables:
# middleware:
def call(env)
request = Rack::Request.new(env) # no matter how many times you do 'new' you always get the same object
request[:foo] = 'bar'
#app.call(env)
end
# Controller:
def index
if params[:foo] == 'bar'
...
end
end
Alternatively, you can get at that "env" object directly:
# middleware:
def call(env)
env['foo'] = 'bar'
#app.call(env)
end
# controller:
def index
if request.env['foo'] == 'bar'
...
end
end
Short answer: Use request.env or env inside a controller.
Long answer:
According to the Rails Guide on Rails controllers, ActionController provides a request method that you can use to access information about the current HTTP request your controller is responding to.
Upon further inspection of the docs for ActionController::Base#request, we see that it "Returns an ActionDispatch::Request instance that represents the current request."
If we look at the docs for ActionDispatch::Request, we see that it inherits from Rack::Request. Aha! Here we go.
Now, in case you're not familiar with the docs for Rack::Request, it's basically a wrapper around the Rack environment. So for most cases, you should just be able to use it as-is. If you really do want the raw environment hash though, you can get it with Rack::Request#env. So within the Rails controller, that would just be request.env.
Digging deeper:
After further examining the instance methods of ActionController::Base, I noticed there's not a whole lot there to look at. In particular, I noticed the params and session variables seem to be missing. So, I moved up one level to ActionController::Metal, which ActionController::Base inherits from.
In ActionController::Metal, I discovered a method env which had no documentation as to what it did - but I could guess. Turns out I was right. That variable was being assigned to request.env.
ActionController::Metal also contained the params method, which, according to the source, was set to request.parameters by default. As it turns out, request.parameters isn't from Rack::Request, but ActionDispatch::Http::Parameters, which is included by ActionDispatch::Request. This method is very similar to the Rack::Request#params method, except that altering it modifies a Rails-specific Rack environment variable (and therefore changes will remain persistent across instances of ActionDispatch::Request).
However, I still couldn't seem to find the session method. Turns out, it's not in the documentation at all. After searching the source code for ActionController::Metal, I finally found it on this line. That's right, it's just a shortcut for request.session.
To summarize:
In the controller...
Use request.env or env to get at the raw environment object
Use params to read Rack query strings and post data from the rack input stream. (E.g. Rack::Request#params)
Use session to access the value of rack.session in the rack environment
In the middleware...
Access properties of the environment the usual way through the environment hash
Access the Rails session through the rack.session property on the environment hash
Read params through Rack::Request#params
Update params through Rack::Request#update_param and Rack::Request#delete_param (as stated in the docs for Rack::Request#params)
Update params in a Rails specific way using ActionDispatch::Http::Parameters#params through ActionDispatch::Request

Resources