It's not uncommon to restrict access to some parts of your app by IP.
Recently I had to implement such restriction as a quick fix for an enterprise customer -
until a more permanent restriction of access using LDAP will be in place.
The easiest implementation, is just to check in a
before_action filter the IP of the current user (as many SO answers suggest)
The StackOverflow answers code look roughly like this:
class ApplicationController < ActionController::Base before_action :filter_ip_address protected def filter_ip_address current_ip_address = request.env['HTTP_X_REAL_IP'] || request.env['REMOTE_ADDR'] head :unauthorized if current_ip_address == "XX.XX.XX.XX" end end
But this code have this caveats (some where more relevant for me):
- No support for IP ranges
- Filter is implemented in the controller, where I need it to be utilized across many controllers
- Allowed IP is hardcoded
So to address each point:
- Support for IP ranges: We can use the IPAddr built in library, create an instance with our CIDR format for IP address range, and use the
include? method to determine if the current user ip is in the range.
- Filter need to be reused across multiple controllers: Here there are many different approaches. I could create a controller class implementing the filter in it, and make all the controllers in need of IP protection inherit from it. But in the "Composition over inheritance" war, I favor composition. So I created a module implementing the filter to be included in every controller.
- IPs are hardcoded: Out of the scope here, just a boring implementation of CRUD for each customer in our side.
So what was the code like in the end?
The module for the filters, to be included in each controller looked like this:
require 'ipaddr' module AdminAuthorizationFilters def self.included(base) base.before_action :filter_ips_for_brand, :many, :other, :possible, :filters end # Protected methods # -------------------------------------------------------------------------------- protected # For the current user, if his brand has a `whitelisted_ips` list, then verify the # current connection is from a verified IP def filter_ips_for_brand user_ip = IPAddr.new(request.remote_ip) # An array of IPs and IP ranges that should be allowed. Stored on the current user. allowed_ips = current_user.brand.whitelisted_ips # Validate IP only if allowed_ips array is set, otherwise there is no IP restriction if allowed_ips verified = false allowed_ips.each do |allowed_ip| allowed_ip = IPAddr.new(allowed_ip) if allowed_ip.include?(user_ip) verified = true end end # Redirect back to main page if not verified unless verified redirect_to "/" end end end end
And then on each controller that needs to utilize those filters:
class MyController < ApplicationController # Include the admin authorization "before_action" filters include AdminAuthorizationFilters ... end
Viola. Quick implementation for IP access restriction.