# frozen-string-literal: true#classRodamoduleRodaPlugins# The host_authorization plugin allows configuring an authorized host or# an array of authorized hosts. Then in the routing tree, you can check# whether the request uses an authorized host via the +check_host_authorized!+# method.# # If the request doesn't match one of the authorized hosts, the# request processing stops at that point. Using this plugin can prevent# DNS rebinding attacks if the application can receive requests for# arbitrary hosts.## By default, an empty response using status 403 will be returned for requests# with unauthorized hosts.## Because +check_host_authorized!+ is an instance method, you can easily choose# to only check for authorization in certain routes, or to check it after# other processing. For example, you could check for authorized hosts after# serving static files, since the serving of static files should not be# vulnerable to DNS rebinding attacks.## = Usage## In your routing tree, call the +check_host_authorized!+ method at the point you# want to check for authorized hosts:## plugin :host_authorization, 'www.example.com'# plugin :public## route do |r|# r.public# check_host_authorized!## # ...# end## = Specifying authorized hosts## For applications hosted on a single domain name, you can use a single string:## plugin :host_authorization, 'www.example.com'## For applications hosted on multiple domain names, you can use an array of strings:## plugin :host_authorization, %w'www.example.com www.example2.com'## For applications supporting arbitrary subdomains, you can use a regexp. If using# a regexp, make sure you use <tt>\A<tt> and <tt>\z</tt> in your regexp, and restrict# the allowed characters to the minimum required, otherwise you can potentionally# introduce a security issue:## plugin :host_authorization, /\A[-0-9a-f]+\.example\.com\z/## For applications with more complex requirements, you can use a proc. Similarly# to the regexp case, the proc should be aware the host contains user-submitted# values, and not assume it is in any particular format:## plugin :host_authorization, proc{|host| ExternalService.allowed_host?(host)}## If an array of values is passed as the host argument, the host is authorized if# it matches any value in the array. All host authorization checks use the# <tt>===</tt> method, which is why it works for strings, regexps, and procs.# It can also work with arbitrary objects that support <tt>===</tt>.## For security reasons, only the +Host+ header is checked by default. If you are# sure that your application is being run behind a forwarding proxy that sets the# <tt>X-Forwarded-Host</tt> header, you should enable support for checking that# header using the +:check_forwarded+ option:# # plugin :host_authorization, 'www.example.com', check_forwarded: true## In this case, the trailing host in the <tt>X-Forwarded-Host</tt> header is checked,# which should be the host set by the forwarding proxy closest to the application.# In cases where multiple forwarding proxies are used that append to the# <tt>X-Forwarded-Host</tt> header, you should not use this plugin.## = Customizing behavior## By default, an unauthorized host will receive an empty 403 response. You can# customize this by passing a block when loading the plugin. For example, for# sites using the render plugin, you could return a page that uses your default# layout:## plugin :render# plugin :host_authorization, 'www.example.com' do |r|# response.status = 403# view(:content=>"<h1>Forbidden</h1>")# end## The block passed to this plugin is treated as a match block.moduleHostAuthorizationdefself.configure(app,host,opts=OPTS,&block)app.opts[:host_authorization_host]=hostapp.opts[:host_authorization_check_forwarded]=opts[:check_forwarded]ifopts.key?(:check_forwarded)ifblockapp.define_roda_method(:host_authorization_unauthorized,1,&block)endendmoduleInstanceMethods# Check whether the host is authorized. If not authorized, return a response# immediately based on the plugin block.defcheck_host_authorization!r=@_requestreturnifhost_authorized?(_convert_host_for_authorization(r.env["HTTP_HOST"].to_s.dup))ifopts[:host_authorization_check_forwarded]&&(host=r.env["HTTP_X_FORWARDED_HOST"])ifi=host.rindex(',')host=host[i+1,10000000].to_sendhost=_convert_host_for_authorization(host.strip)if!host.empty?&&host_authorized?(host)returnendendr.ondohost_authorization_unauthorized(r)endendprivate# Remove the port information from the passed string (mutates the passed argument).def_convert_host_for_authorization(host)host.sub!(/:\d+\z/,"")hostend# Whether the host given is one of the authorized hosts for this application.defhost_authorized?(host,authorized_host=opts[:host_authorization_host])caseauthorized_hostwhenArrayauthorized_host.any?{|auth_host|host_authorized?(host,auth_host)}elseauthorized_host===hostendend# Action to take for unauthorized hosts. Sets a 403 status by default.defhost_authorization_unauthorized(_)@_response.status=403nilendendendregister_plugin(:host_authorization,HostAuthorization)endend