How do I add a host to my permitted rails hosts dynamically? - ruby-on-rails

I need to dynamically add a permitted host to my Rails 6 application during runtime.
I've managed to append to Rails.application.config.hosts at runtime, but I'm still receiving an error Blocked host.
Rails appears to ignore the hosts added to Rails.application.config.hosts that are added outside of application.rb and initializers.
Looking at host_authorization.rb, I can't see an obvious way of asking it to listen to new hosts.
My Reasoning:
User's can programmatically create their own 'shop' on our platform, which adds a new 'tenant' (apartment gem) to our multi-tenanted application We also allow them to point their own domains to our application.
However, in order to allow traffic from their custom domain, we currently would require a manual application restart if we stick with the default whitelisted domains.
I know I could use a workaround, and just set the hosts to whitelist everything (Rails.application.config.hosts = nil), but we need to keep the platform secure and automated.

The items in config.hosts are documented as compared using the === operator, which means we may include a lambda in that array, and thus make dynamic checking possible.
For the sake of an example, let's say we're returning a global permit-list from MyApp.all_permitted_domains. Then in config/application.rb we might write:
config.hosts << ->(host) {
MyApp.all_permitted_domains.include?(host)
}
Note however that in this case the additional processing normally performed on host authorization (such as handling port numbers, or the special case of a leading ".") is not included. If you need that behaviour too, review the sanitize_hosts method in that host_authorization.rb source file for how.

Related

Rails uses different default url options in views vs controllers?

Scratching my head over this.
Recently opened a new reverse proxy to our site via nginx to support a new domain. When you access via either old.server.com or new.server.com you hit our backend.
If you hit via old.server.com, all the url helpers work fine and produce valid absolute URLs. But if you hit via new.server.com (the new reverse proxy host) then url helpers only work correctly in views. URL helpers used in the controllers use the hostname plus the actual unicorn port -- which is incorrect for URLs we want to send out.
Even more confusing, this is only the case in the controllers. In the views, this works correctly in all cases. In order to make this work in controllers when using the new.server.com I need to override the default_url_options method in the ApplicationController.
Yes, I have both the Rails.application.routes.default_url_options[:host] and Rails.application.routes.default_url_options[:protocol] configurations setup correctly.
I feel like I probably missed forwarding some required header in the reverse proxy, but haven't figured it out. But more importantly, why are the views and controllers using different contexts for generating absolute URLs?
UPDATE:
The difference between the two hosts appeared to be the value being provided by the reverse proxy for the Host header. The "old" proxy was sending the public host for it, and the "new" proxy was sending nothing. Rails appears to use the Host header in constructing URLs in the absence of explicit data. In order to fix this I either need to set the Host header correctly or implement the default_url_options method in my controller.
So, I now have working code, but it seems like a Really Bad Idea(TM) that controllers and views are not using the exact same code/setup for creating URLs. Is this just standard rails, or did I miss some configuration?

Running a rails app on an internal network with access to an external form possible?

I am currently working on a rails project that will be deployed on an internal network for large businesses. It will be for internal use only. However, one of the feature requirements for this project is that external non-users can receive an email with a url/token that takes them to a form to supply information to the application's database from outside the network. What would be the best way, or is it even possible, to approach this?
If you want your app to be internal only you should create another app in the same network that has external visibility, and you can communicate with your internal app between the two, maybe through an API.
hope this gives you some ideas.
It should be possible to limit access to any given route or controller action from within the Rails project (the Rack request object contains the IP that generated the request), but a better way would be to leverage features in your reverse proxy. If you're using nginx, see this page. You should be able to set something up like this in your nginx.conf:
location / {
allow 192.168.1.1/24; # or whatever IP range you want to permit through
allow 127.0.0.1; # for local development
deny all; # block everyone else
... # the reset of your app config
}
location /path-to-external-form {
allow all;
}
Of course, it might be easier to stand up a separate app and just share a database

better way to configure the email address with rails application

Currently, I'm working on the application which is developed in rails 3* and ruby 1.9.3. I have configured some email address in the initializers section as YML file for each environments.
But, the requirement is keep on changing (but it'll happened every month 1 or 2 times) that need to add/remove the email address from the configuration. Hence, I need to restart the server on every changes. Because, I configured those address in the initializers.
Is there any better way to handle this situation?
If you want to change the email without re-deploying / restarting the server, you can always create a Email model and persist it to the database. By adding a current field / column (boolean value) and a scope scope :current, -> { where(current: true) } you can access the email via Email.current.first.address, for instance. You might need to ensure that one and only one 'current' Email object ist present at any given time.
edit
creating a model does not mean you have to create UI for it. Just use the console to change the email if you have to.
the configuration and models are loaded at startup, and if you don't want to do any reloading in production, which is slow and not recommended, you have to use the DB for persistence.
if you really want to go down the "reload" route, set config.cache_classes = true in config/environments/production.rb and specify the email in a constant outside the config directory (in some model or controller) like this EMAIL = 'whatever#email.com'. You would have to change the production code on each production machine, without a server restart. Sounds very hacky. Look into zero-downtime deploys à la Github for a more elegant solution. Redeploying should be cheap and painless.

How to get domain/subdomains of an app outside of controllers/views?

How can I get the domain and subdomains of my application without being in a controller/view?
The reason for needing this is that I am subscribing to process_action.action_controller notifications within an initializer, and I need my applications' url from within that initializer.
If the host part of the URL (domain, subdomain) is dynamic ... which is to say "depends on the request" then, of course, the only way to get it is within the context of the request itself, which the controllers and views know about.
So I am assuming the application has a known host, perhaps dependent upon runtime environment (production, test, development, etc.), or maybe based on the server environment, but otherwise static. In this case, you could define a custom config variable containing the name, as noted in the more recent answer from Jack Pratt on this SO question: How to define custom configuration variables in rails.

Account based lookup in ASP.NET

I'm looking at using ASP.NET for a new SaaS service, but for the love of me I can't seem to figure out how to do account lookups based on subdomains like most SaaS applications (e.g. 37Signals) do.
For example, if I offer yourname.mysite.com, then how would I use ASP.NET (MVC specifically) to extract the subdomain so I can load the right template (displaying your company's name and the like)? Can it be done with regular routing?
This seems to be a common thing in SaaS so there has to be an easy way to do it in ASP.NET; I know there are plugins that do it for other frameworks like Ruby on Rails.
This works for me:
//--------------------------------------------------------------------------------------------------------------------------
public string GetSubDomain()
{
string SubDomain = "";
if (Request.Url.HostNameType == UriHostNameType.Dns)
SubDomain = Regex.Replace(Request.Url.Host, "((.*)(\\..*){2})|(.*)", "$2");
if (SubDomain.Length == 0)
SubDomain = "www";
return SubDomain;
}
I'm assuming that you would like to handle multiple accounts within the same web application rather than building separate sites using the tools in IIS. In our work, we started out creating a new web site for each subdomain but have found that this approach doesn't scale well - especially when you release an update and then have to modify dozens of sites! Thus, I do recommend this approach rather than the server-oriented techniques suggested above based on several years worth of experience doing exactly what you propose.
The code above just makes sure that this is a fully formed URL (rather, say, than an IP address) and returns the subdomain. It has worked well for us in a fairly high-volume environment.
You should be able to pick this up from the ServerVariables collection, but first you need to configure IIS and DNS to work correctly. So you know 37Signals probably use Apache or another open source, unix web server. On Apache this is referred to as VirtualHosting.
To do this with IIS you would need to create a new DNS entry (create a CNAME yourname.mysite.com to application.mysite.com) for each domain that points to your application in IIS (application.mysite.com).
You then create a host header entry in the IIS application (application.mysite.com) that will accept the header yourname.mysite.com. Users will actually hit application.mysite,com but the address is the custom subdomain. You then access the ServerVariables collection to get the value to decide on how to customize the site.
Note: there are several alternative implementations you could follow depending on requirements.
Handle the host header processing at a hardware load balancer (more likely 37Signals do this, than rely on the web server), and create a custom HTTP header to pass to the web application.
Create a new web application and host header for each individual application. This is probably an inefficient implementation for a large number of users, but could offer better isolation and security for some people.
You need to configure your DNS to support wildcard subdomains. It can be done by adding an A record pointing to your IP address, like this:
* A 1.2.3.4
Once its done, whatever you type before your domain will be sent to your root domain, where you can get by splitting the HTTP_HOST server variable, like the user buggs said above:
string user = HttpContext.Request.ServerVariables["HTTP_HOST"].Split(".")
//use the user variable to query the database for specific data
PS. If you are using a shared hosting you're probably going to have to by a Unique IP addon from them, since it's mandatory for the wildcard domains to work. If you're using a dedicated hosting you already have your own IP.
The way I have done it is with HttpContext.Request.ServerVariables["HTTP_HOST"].Split(".").
Let me know if you need more help.

Resources