Rails securing letter_opener - ruby-on-rails

Alright, I'm actually using letter_opener_web as a way to preview emails I'm about to send. Understand, I'm not on the developer side (letter_opener is originally meant for dev environment) but on the side of the user of my website. I am using letter_opener as a production component.
I need to generare an access to letter_opener views, however the default implementation of letter_opener is done in routes.rb
mount LetterOpenerWeb::Engine, at: "/letter_opener"
I have for now used a trick of generating a random path and only communicating the URL in the web views I render, however, well, it's just bad :D
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: "/letter_opener"
else
mount LetterOpenerWeb::Engine, at: "/#{SecureRandom.hex(12)}"
end
Is there a way I can override some controller to add my own access control implementation ? My current access control (which I won't change) is done this way
class ApplicationController
before_action :access_denied
end
class SomeController < ApplicationController
prepend_before_action :allow_access_to_xxxx
end
Which is why I need some way to override a controler of letter_opener while still making its views available (if possible by keeping the simple syntax to add in routes !)
EDIT : my user case. Only a couple lines of code to preview instead of sending an email
...
mail(
to: dst,
from: message[:from],
subject: #subject_full,
delivery_method: real_or_preview(message))
...
# Switch delivery method to letter_opener if preview
def real_or_preview(message)
if message[:preview]
:letter_opener
else
ActionMailer::Base.delivery_method
end
end

Related

Rails 5.2: authorize access to ActiveStorage::BlobsController#show

I would like to authorize access to ActiveStorage attachments and looking at the source code of BlobsController (https://github.com/rails/rails/blob/master/activestorage/app/controllers/active_storage/blobs_controller.rb) is stated the following:
# Take a signed permanent reference for a blob and turn it into an expiring service URL for download.
# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
# security-through-obscurity factor of the signed blob references, you'll need to implement your own
# authenticated redirection controller.
class ActiveStorage::BlobsController < ActiveStorage::BaseController
include ActiveStorage::SetBlob
def show
expires_in ActiveStorage.service_urls_expire_in
redirect_to #blob.service_url(disposition: params[:disposition])
end
end
But even the notes above suggest to create a custom controller I would need also to override the routes generated by ActiveStorage, since they are pointing to the original controllers, and redefining them on my routes.rb seems to throw an exception. Also I don't want to expose these routes anymore as they are not being authorized and someone could take the signed_id of the blob and get the attachment using the original endpoint.
Looping over the routes on the app initialization and deleting the old ActiveStorage routes and inserting the new ones seems the best solution for now, but I would like to avoid that.
Any suggestions? 🙄
Create a new controller to override the original: app/controllers/active_storage/blobs_controller.rb then add the authorization method accordingly with your needs:
#app/controllers/active_storage/blobs_controller.rb
class ActiveStorage::BlobsController < ActiveStorage::BaseController
include ActiveStorage::SetBlob
def show
redirect_to #blob.service_url(disposition: params[:disposition])
authorize! :show, #blob # NOT TESTED!
end
end
The show action is triggered when you click on a link to the attachment.
#blob.class #=> ActiveStorage::Blob

How to protect rspec_api_documentation apitome route without a controller

I have a set of API documentation pages that I want to password protect using devise. The docs are generated using rspec_api_documentation. The gem uses rspec to execute the API methods and create html or json doc files. I'm generating json and using another gem, apitome, as a viewer.
This is all working beautifully and my api docs can be found at /api/docs, but I can't figure out how to require authentication to view the docs.
The docs are viewed through apitome so it using rails. There isn't a route in routes.rb, but the apitom initializer mounts the docs.
From apitom initializer:
# This determines where the Apitome routes will be mounted. Changing this to "/api/documentation" for instance would
# allow you to browse to http://localhost:3000/api/documentation to see your api documentation. Set to nil and mount
# it yourself if you need to.
config.mount_at = "/api/docs"
https://github.com/zipmark/rspec_api_documentation
https://github.com/modeset/apitome
I found this How-to that is supposed to make every page on the site require authenication, but I'm still able to hit the docs url without signing in.
#https://github.com/plataformatec/devise/wiki/How-To:-Require-authentication-for-all-pages
Thanks for any help.
You can take control of Apitome controller and request auhentication.
configure apitome to not mount itself by setting mount_at to nil
write your controller ApidocsController extending Apitome::DocsController
add before_filter :authenticate_user! in your controller
add a route to your controller: get '/api/docs', to: 'apidocs#index'
Controller implementatin would go like this:
class ApidocsController < Apitome::DocsController
before_filter :authenticate_user!
end
If you have Devise already configured (for example if you already use ActiveAdmin) you can share the same authenticated session.
You have to disable the default mounting of Apitome:
# config/initializers/apitome.rb
Apitome.setup do |config|
config.mount_at = nil
# ...
end
Since this will break links in Apitome, you have to add to your application.rb:
# config/application.rb
# ...
class Application < Rails::Application
config.after_initialize do
Apitome.configuration.mount_at = '/docs'
end
# ...
end
Then force mount of the Apitome engine under Devise authenticated session:
# config/routes.rb
Rails.application.routes.draw do
# ...
authenticated :admin_user do
mount Apitome::Engine => '/docs'
end
# root_to ...
end
Now /docs is accessible only for already authenticated users.
PS: replace :admin_user (default for Active Admin) to :user or any other model name where Devise is configured.
I am using the cut down rails api, so did not have any views or devise and so no simple way to implement password protection. Accordingly I went for the following solution.
Apitome.setup do |config|
config.mount_at = nil
# ...
end
and in the routes.rb file:
mount Apitome::Engine => '/api/docs' unless Rails.env.production?
Finally, I just did a pdf of the api/docs page, and then sent the pdf to anyone what had a right to see the documentation. Obviously not suitable for a large user base, but works fine for a small one, e.g. within a company's IT group.
If anyone is using Raddocs, add mounted route inside authentication block as below:
authenticate :user do
mount Raddocs::App => "/docs"
end

How to restrict public access to a rails app

Is there a way to upload an app, but only have it accessible by me? Or perhaps by a specific set of IP's?
Reason being, we want to run a few private online tests before opening the app up to the general public. So far I have come up with the following code:
class ApplicationController < ActionController::Base
before_filter :restrict_access
def restrict_access
whitelist = ['127.0.0.123', '10.0.1.7', '10.0.1.8'].freeze
unless( whitelist.include? request.env['REMOTE_ADDR'] )
render :file => "#{Rails.public_path}/500.html", :status => :unauthorized
return
end
end
end
However, the above code still renders the main layout file (app/views/layouts/application.html.erb) which exposes the logo and footer. For un-authorised access we want to display a page that says something like "Ooops, we are still doing a few tests and will be public soon!". No logo of the site, no nothing. Just a simple message.
We are using devise as our authentication gem. We don't want to add authentication functionality just to restrict access for private beta testing. We want to do it by IP instead.
Is such a thing possible? Perhaps the code above just needs working on? Or is there a gem that we can use solely for this requirement?
If you're deploying on Apache or Nginx, this should be easy enough to configure in the relevant site config files. Doesn't need to be in the app itself, in that case.
I'm not totally sure what the issue is with your existing code. Are you saying that the filter seems to be ignored, or that the file renders within the layout? If it's the latter, specifying :layout => false as a render option should take care of that.
class ApplicationController < ActionController::Base
before_filter :check, :except=>:unauth
def unauth
render(:layout=>false)
end
private
def check
whitelist = ['127.0.0.123', '10.0.1.7', '10.0.1.8'].freeze
if !(whitelist.include? request.env['REMOTE_ADDR'])
redirect_to('/unauth')
return
end
end
end
untested but should do it, now you can place a plain error msg or whatever in unauth.rhtml
I will suggest of use some Rack middelware a.e. rack-rewrite
require 'rack-rewrite'
#in your environment
config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
r301 %r{.*}, "YOUR REDIRECTPAGE$&" ,:if => Proc.new {|rack_env|
#puts here your conditions basing on rack_env
}
end
NOT TESTED

Dynamic Rails routing based on database

I'm building a CMS with various modules (blog, calendar, etc.) using Rails 2.3. Each module is handled by a different controller and that works just fine.
The only problem I have is with the root URL. Depending on the configuration chosen by the user, this default URL should show a different module i.e. a different controller, but the only way I have to determine the correct controller is by checking the database for what "default" module is to be shown.
For the moment I'm using a specific "root" controller which checks the database and redirects to the correct controller. However I'd prefer the URL not to be changed, which means I want to invoke the correct controller from the very same request.
I've tried using Rails Metal to fetch this info and manually calling the controller I want but I'm thinking I may be reinventing the wheel (identify the request path to choose the controller, manage session, etc.).
Any idea? Thanks a lot in advance!
This problem can be solved with some Rack middleware:
This code in lib/root_rewriter.rb:
module DefV
class RootRewriter
def initialize(app)
#app = app
end
def call(env)
if env['REQUEST_URI'] == '/' # Root is requested!
env['REQUEST_URI'] = Page.find_by_root(true).uri # for example /blog/
end
#app.call(env)
end
end
end
Then in your config/environment.rb at the bottom
require 'root_rewriter'
ActionController::Dispatcher.middleware.insert_after ActiveRecord::QueryCache, DefV::RootRewriter
This middleware will check if the requested page (REQUEST_URI) is '/' and then do a lookup for the actual path (Implementation to this is up to you ;-)). You might do good on caching this info somewhere (Cache.fetch('root_path') { Page.find... })
There are some problems with checking REQUEST_URI, since not all webservers pass this correctly. For the whole implementation detail in Rails see http://api.rubyonrails.org/classes/ActionController/Request.html#M000720 (Click "View source")
In Rails 3.2 this was what I came up with (still a middleware):
class RootRewriter
def initialize(app)
#app = app
end
def call(env)
if ['', '/'].include? env['PATH_INFO']
default_thing = # Do your model lookup here to determine your default item
env['PATH_INFO'] = # Assemble your new 'internal' path here (a string)
# I found useful methods to be: ActiveModel::Naming.route_key() and to_param
end
#app.call(env)
end
end
This tells Rails that the path is different from what was requested (the root path) so references to link_to_unless_current and the like still work well.
Load the middleware in like so in an initialiser:
MyApp::Application.config.middleware.use RootRewriter

Backend administration in Ruby on Rails

I'd like to build a real quick and dirty administrative backend for a Ruby on Rails application I have been attached to at the last minute. I've looked at activescaffold and streamlined and think they are both very attractive and they should be simple to get running, but I don't quite understand how to set up either one as a backend administration page. They seem designed to work like standard Ruby on Rails generators/scaffolds for creating visible front ends with model-view-controller-table name correspondence.
How do you create a admin_players interface when players is already in use and you want to avoid, as much as possible, affecting any of its related files?
The show, edit and index of the original resource are not usuable for the administrator.
I think namespaces is the solution to the problem you have here:
map.namespace :admin do |admin|
admin.resources :customers
end
Which will create routes admin_customers, new_admin_customers, etc.
Then inside the app/controller directory you can have an admin directory. Inside your admin directory, create an admin controller:
./script/generate rspec_controller admin/admin
class Admin::AdminController < ApplicationController
layout "admin"
before_filter :login_required
end
Then create an admin customers controller:
./script/generate rspec_controller admin/customers
And make this inhert from your ApplicationController:
class Admin::CustomersController < Admin::AdminController
This will look for views in app/views/admin/customers
and will expect a layout in app/views/layouts/admin.html.erb.
You can then use whichever plugin or code you like to actually do your administration, streamline, ActiveScaffold, whatever personally I like to use resourcecs_controller, as it saves you a lot of time if you use a REST style architecture, and forcing yourself down that route can save a lot of time elsewhere. Though if you inherited the application that's a moot point by now.
Do check out active_admin at https://github.com/gregbell/active_admin.
I have used Streamlined pretty extensively.
To get Streamline working you create your own controllers - so you can actually run it completely apart from the rest of your application, and you can even run it in a separate 'admin' folder and namespace that can be secured with .
Here is the Customers controller from a recent app:
class CustomersController < ApplicationController
layout 'streamlined'
acts_as_streamlined
Streamlined.ui_for(Customer) do
exporters :csv
new_submit_button :ajax => false
default_order_options :order => "created_at desc"
list_columns :name, :email, :mobile, :comments, :action_required_yes_no
end
end
Use https://github.com/sferik/rails_admin.

Resources