For test purposes I want to change the return value of request.remote_ip. While being on my development machine it returns always 127.0.0.1 as it should but I would like to give myself different fake IPs to test the correct behavior of my app without deploying it to an live server first!
Thank you.
If you want this functionality in your whole application, it might be better/easier to override the remote_ip method in your app/helpers/application_helper.rb:
class ActionDispatch::Request #rails 2: ActionController::Request
def remote_ip
'1.2.3.4'
end
end
And the 1.2.3.4 address is available everywhere
For integration tests, this works with rails 5:
get "/path", params: { }, headers: { "REMOTE_ADDR" => "1.2.3.4" }
You can modify the request object using:
request = ActionController::Request.new('REMOTE_ADDR' => '1.2.3.4')
request.remote_ip now returns 1.2.3.4
You can cheat a bit by making a mutator for the remote_ip value in the test environment which is normally not defined.
For instance, alter the class inside of test/test_helper.rb with the following:
class ActionController::TestRequest
def remote_ip=(value)
#env['REMOTE_ADDR'] = value.to_s
end
end
Then, during your testing you can reassign as required:
def test_something
#request.remote_ip = '1.2.3.4'
end
This can be done either in the individual test, or within your setup routine, wherever is appropriate.
I have had to use this before when writing functional tests that verify IP banning, geolocation, etc.
rails 4.0.1 rc. After hour of searching found this simple solution while digging to code :)
get '/', {}, { 'REMOTE_ADDR' => '1.2.3.4' }
What I ended up doing now was to put this code in the end of the config/environments/development.rb file to make sure it's only executed while in development
# fake IP for manuel testing
class ActionController::Request
def remote_ip
"1.2.3.4"
end
end
So this sets remote_ip to 1.2.3.4 when the server starts. Everytime you change the value you have to restart the server!
This answer is only works for rails3 (I found this answer when trying to answer a similar question for rails 3),
So I will post it here in case if someone is trying to do the same thing in Rails3 env
class ActionDispatch::Request
def remote_ip
'1.2.3.4'
end
end
HTH
Related
I am working right now on a Rails 4.0 application (using Ruby 2.0.0).
I would like to interact with Jenkins using jenkins_api_client gem, from multiple pages of my Rails application.
This gem generally using a #client parameter, which is initialized to contain the credentials and other information of the Jenkins server.
This parameter in initialized using something like this:
#client = JenkinsApi::Client.new(:server_ip => '0.0.0.0',
:username => 'somename', :password => 'secret password')
Once initialized, I would like to access this parameter and run multiple sub-routines on it.
This initialization takes time, and I really want to avoid doing this process every time one of the clients would like to use this gem functionality, such as:
# Get a filtered list of jobs from the server
jobs_to_filter = "^test_job.*"
jobs = #client.job.list(jobs_to_filter)
So, I hope to do this only once- when the rails server starts.
I would like to use this parameter from multiple pages of my app, possibly with threaded solution further down the road (not critical at the moment).
Can anyone recommend how to achieve this?
I'd appreciate an answer which is consistent with Rails convention.
Thanks a lot!
as example you could create something like that:
module JenkinsApi
class Client
class << self
attr_reader :instance, :config
def configure(&block)
#config = OpenStruct.new
block.call #config
end
def instance
#instance ||= JenkinsApi::Client.new #config
end
end
end
end
which allow you write in initializer:
JenkinsApi::Client.configure do |config|
config.server_ip = '0.0.0.0'
config.username = 'somename'
config.password = 'secret password'
end
and then use it like: JenkinsApi::Client.instance.job.list(...
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
I am trying to avoid using my session[:referred_by], and would like to use the request.referrer. However, my RSpec tests fail because the TestRequest does not store a request.referrer
So I have to do the following in order for Rspec tests to work. Is there a way to make it better:
referrer = request.referrer ? request.referrer : '/'
redirect_to referrer, :alert => error_message
ActionDispatch::TestRequest extends ActionDispatch::Request that extends Rack::Request.
The method is defined as follows
def referer
#env['HTTP_REFERER']
end
alias referrer referer
As far as I remember, you can access the environment variable in the RSpec test by using request.env. It means, it should be possible to set something like
request.env['HTTP_REFERER'] = 'http://example.com'
Of course, it depends on the type of RSpec example group you are using.
Mock it:
specify 'foo' do
controller.request.should_receive(:referer).and_return('http://example.com')
# get whatever
end
Or if you don't care if it doesn't get called, stub it:
controller.request.stub referer: 'http://example.com'
On Rails 5 the accepted solution doesn't seem to work anyore, but setting request.env['HTTP_REFERER'] directly, as Simone Carletti suggests, works.
Solution for Rails 5.2+ & RSpec 3.8+
get :endpoint, params: {}, headers: { 'HTTP_REFERER' => 'stackoverflow.com' }
With Rails4/Rspec3, in request specs, request is not available until you make an http call. But you can assign request.referer by doing something like this:
get '/posts', {}, { referer: 'http://example.com' }
This also works in controllers specs:
controller.request.headers.merge({ 'HTTP_REFERER': 'https://stackoverflow.com/' })
get(:endpoint)
For anyone else Googling this, you can also stub the request in a helper spec like this:
allow(view).to receive_message_chain(:request, :referrer).and_return("http://example.com")
In my development environment, I use a copy of the production database when testing locally. For reasons both for testing and simply for protection against sending out test/dev emails to real users, what's the best way to override the mail-to address when in development mode?
I know I can write logic in each mailer, but I have several and it would be nice to put it all in once place. Can I override the mail() method somehow to make the :to parameter always point at an email address I specify?
I use an ActionMailer interceptor so all mails sent while in development or test environments are sent to the same address. Like this:
# config/initializers/override_mail_recipient.rb
if Rails.env.development? or Rails.env.test?
class OverrideMailRecipient
def self.delivering_email(mail)
mail.to = 'to#example.com'
end
ActionMailer::Base.register_interceptor(OverrideMailRecipient)
end
end
I think the best option is to use a service like mailtrap.io: http://mailtrap.io or mailcatcher gem: https://rubygems.org/gems/mailcatcher
What I like to do is configure action mailer in the development environment to use mailtrap.
You could do a default of
class UserMailer < ActionMailer::Base
default :to=> "to#example.com"
end
And then make the address an option in the methods. That way it would default to the :to you set. Another idea I had was a bit more:
class UserMailer < ActionMailer::Base
attr_accessor :email_address
def initialize
if RAILS_ENV == "development"
#email_address = "to#example.com"
end
end
end
That would require you to set a new address in your code but it would be overwritten each time in Development.
UPDATED:
I am setting default scope for some models in a runtime which seems working locally in my development env and my code is given below.
SET_OF_MODELS = [Event, Group, User]
#account = Account.find_by_subdomain(account_subdomain)
SET_OF_MODELS.each { |m| m.set_default_scope(#account.id) }
def set_default_scope(account_id)
default_scope :conditions=> { :account_id => account_id }
end
If I execute this code in ruby console with say #account1, User.first returns #account1 user whereas if I repeat the code with #account2 then User.first returns #account1 user instead of #account2. And this problem is not revealed while running app in local server but in staging server.
My guess is towards their states if they are really cached but not sure. Can someone explain in depth.
Thanks in advance
default_scope will save state in its class. It's harmful in concurrent environment because it leads to race condition. So you must isolate scope state between requests.
You can use around_filter
class ApplicationController < ActionController::Base
around_filter :set_default_scope
def set_default_scope
#account = Account.find_by_subdomain(account_subdomain)
opts = :condition => {:account_id => #account.id}
Event.send(:with_scope, opts) do
Group.send(:with_scope, opts) do
User.send(:with_scope, opts) do
yield
end
end
end
end
end
You can refactor .send(:with_scope, opts) to a class method like with_account_scope(account_id)
Development differs from production. In production all classes are loaded once and cached, so you can't redefine the default scopes on each request.
In development the classes are loaded on each request, to allow easy development: each change you do in the code is visible/active on the next request.
If you really want to, you can disable this behaviour in production. This will make your complete site slower, but maybe that is not really an issue. To turn this off, you have edit your config/environments/production.rb, find the line containing
config.cache_classes = true
and switch that to false.
Hope this helps.
There is nothing wrong with the above code but the problem was with the server used i.e. thin server. It worked perfectly after replacing thin with mongrel. I think thin wasn't allowing to execute set_default_scope more than once except after loading the application.