101 lines
4.1 KiB
Ruby
101 lines
4.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_dependency 'barong/authorize'
|
|
|
|
# Rails Metal base controller to manage AuthZ story
|
|
class AuthorizeController < ActionController::Metal
|
|
include AbstractController::Rendering
|
|
|
|
# /api/v2/auth endpoint
|
|
def authorize
|
|
@restrictions = Rails.cache.fetch('restrictions', expires_in: 5.minutes) { fetch_restrictions }
|
|
|
|
# whitelink path
|
|
unless params[:path] == 'api/v2/barong/identity/users/access'
|
|
Restriction::CATEGORIES.each do |category|
|
|
restriction = first_matched_restriction(category)
|
|
if restriction && category.in?(%w[blacklist maintenance])
|
|
return deny_access(category, restriction)
|
|
elsif restriction && category == 'blocklogin' && params[:path] == 'api/v2/barong/identity/sessions'
|
|
return deny_access(category, restriction)
|
|
elsif restriction && category == 'whitelist'
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
req = Barong::Authorize.new(request, params[:path]) # initialize params of request
|
|
# checks if request is blacklisted
|
|
return access_error!('authz.permission_denied', 401) if req.under_path_rules?('block')
|
|
|
|
response.status = 200
|
|
return if req.under_path_rules?('pass') # check if request is whitelisted
|
|
|
|
response.headers['Authorization'] = req.auth # sets bearer token
|
|
rescue Barong::Authorize::AuthError => e # returns error from validations
|
|
response.body = e.message
|
|
response.status = e.code
|
|
end
|
|
|
|
private
|
|
|
|
# finds first (if exist) the most precise restriction ip -> ip_subnet -> continent -> country
|
|
def first_matched_restriction(category)
|
|
request_ip = remote_ip
|
|
country = Barong::GeoIP.info(ip: request_ip, key: :country)
|
|
continent = Barong::GeoIP.info(ip: request_ip, key: :continent)
|
|
if restriction = @restrictions[category]['all']&.find { |r| r.present? } then return restriction end
|
|
if restriction = @restrictions[category]['ip']&.find { |r| r.include?(request_ip) } then return restriction end
|
|
if restriction = @restrictions[category]['ip_subnet']&.find { |r| IPAddr.new(r[0])&.include?(request_ip) } then return restriction end
|
|
if restriction = @restrictions[category]['continent']&.find { |r| r[0]&.casecmp?(continent) } then return restriction end
|
|
if restriction = @restrictions[category]['country']&.find { |r| r[0]&.casecmp?(country) } then return restriction end
|
|
end
|
|
|
|
# as a result gives complex Hash, { category: { scope: values, scope: values }, category: { scope: values, scope: values } }
|
|
# { "blacklist"=>{"continent"=>[], "country"=>[], "ip"=>[], "ip_subnet"=>[]},
|
|
# "whitelist"=>{"continent"=>[], "country"=>[], "ip"=>[], "ip_subnet"=>[]},
|
|
# "maintenance"=>{"continent"=>[], "country"=>[], "ip"=>[], "ip_subnet"=>[]} }
|
|
def fetch_restrictions
|
|
enabled = Restriction.where(state: 'enabled').to_a
|
|
|
|
Restriction::CATEGORIES.inject({}) do |table, category|
|
|
grouped_by_category = enabled.select { |r| r.category == category }
|
|
|
|
grouped_by_scope = Restriction::SCOPES.inject({}) do |table, scope|
|
|
scope_restrictions = grouped_by_category.select { |r| r.scope == scope }.pluck(:value, :code)
|
|
table.tap { |t| t[scope] = scope_restrictions }
|
|
end
|
|
table.tap { |t| t[category] = grouped_by_scope }
|
|
end
|
|
end
|
|
|
|
def deny_access(category, restriction)
|
|
Rails.logger.info("Access denied for ip #{request.remote_ip} because of #{restriction[0]} restriction")
|
|
access_error!("authz.restrict.#{category}", restriction[1])
|
|
end
|
|
|
|
def session
|
|
request.session
|
|
end
|
|
|
|
# error for blacklisted routes
|
|
def access_error!(text, code)
|
|
response.status = code
|
|
response.body = { 'errors': [text] }.to_json
|
|
end
|
|
|
|
def remote_ip
|
|
# default behaviour, IP from HTTP_X_FORWARDED_FOR
|
|
ip = request.remote_ip
|
|
|
|
if Barong::App.config.gateway == 'akamai'
|
|
# custom header that contains only client IP
|
|
true_client_ip = request.env['HTTP_TRUE_CLIENT_IP']
|
|
# take IP from TRUE_CLIENT_IP only if its not nil or empty
|
|
ip = true_client_ip unless true_client_ip.nil? || true_client_ip.empty?
|
|
end
|
|
|
|
ip
|
|
end
|
|
end
|