How to protect rspec_api_documentation apitome route without a controller - ruby-on-rails

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

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

rails_admin using separate login pages for admin and user

I'm using rails_admin and devise gem for my rails 5 project. I used CanCanCan for authorization. My problem is i must use separate login pages, one for normal user and one for admin user (now it uses same login page for both).
Does anyone has solution for my problem?
Many thanks!
You find more info in the below link for multiple devise user model:
https://github.com/plataformatec/devise/wiki/How-to-Setup-Multiple-Devise-User-Models
if you want to use two different paths use namespace in your routes.rb
Example:
Rails.application.routes.draw do
root to: 'user#index'
.
.
.
namespace :admin do
root to: 'dashboard#index'
.
.
.
end
end
Don't forget that you will have to namespace all the controllers that you plan to include in the admin section, as well as remember to have those files on the proper folder.
For the above example you should have the admin controller inside the path app/controllers/admin/dashboard_controller.rb and the content of the controller should be similar to the below code
class Admin::DashboardController < ActionController::Base
def index
end
end
if you want to use one single login page for both admins and users, then you'll have to use a common controller to handle both

Rails securing letter_opener

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

Rails localize path without locale param for certain url

I am fairly new to Rails and in the process of building localization for my rails project. I have followed Rails i18n guide and is able to use localized path to different language version of the site.
My routes.rb looks like the following:
scope "/:locale" do
# blah
end
namespace :admin do
# admin blah
end
However because I have used default_url_options in application controller as suggested in Rails guide, the path generated by URL helper will also contain locale params. When I create link to my admin root localhost:3000/admin?locale=en, I will get an error saying "admin" is not a valid locale
Is there a way to make exception for the generated url to not including the locale parameter?
I finally figured out a workaround for this. All I needed to do is to place the admin namespace routing before the scoped routes, such as
namespace :admin do
# admin blah
end
scope "/:locale" do
# blah
end
After I set it up like above, the error went away.

How to use Devise/CanCan to protect mounted Engine resources?

I have an engine mounted to my main app and I want to protect certain controllers and actions within that engine.
The engine is mounted with:
mount SomeEngine::Engine => '/some_engine'
Devise/CanCan is working with the rest of the main app's controllers and actions, but letting things run without anything else produces this error:
This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check.
So I open up the engine controllers from the main app using the decorator approach and add:
load_and_authorize_resource
Then I get this error:
No route matches {:action=>"new", :controller=>"devise/sessions"}
I can get things working using the following, but it's clunky when I try to implement roles:
authenticate :administrator do
mount SomeEngine::Engine => '/some_engine'
end
By clunky I mean I'll have to reproduce the above block of code in the routes.rb file for each role that has access to the engine...unless there's another way to use authenticate with roles that I don't know about???
I'd like to use the normal Devise/CanCan authorization/authentication approach in the controller if possible. But I think "no route match" error occurs because the engine does not know how to get to the main app's Devise controllers. But how do I get around this from the main app?
To throw one more issue into the mix...there is one specific controller/action in the engine that I do want to make public to all users. Thus far I've just added this before the authenticate block of code in the routes.rb file.
match '/some_engine' => 'some_engine/some_controller#public_action'
It works...but this line with the block in the routes.rb seems like I'm doing something wrong. And it doesn't allow me to implement roles nicely.
You can inherit application controller for use devise and cancan from main app.
module SomeEngine
class ApplicationController < ::ApplicationController
before_filter :merge_abilities
private
def merge_abilities
current_ability.merge(SomeEngine::Ability.new(current_user))
end
end
end
After this you can create abilities for engine by create own.
module SomeEngine
class Ability
include ::CanCan::Ability
def initialize(user)
return if user.nil?
can :manage, SomeModel
end
end
end
SomeModel (SomeEngine::SomeModel) is model at SomeEngine engine.
At resource controllers you must specify class name of resource.
load_and_authorize_resource class: SomeEngine::SomeModel
And do not forgot change route helper to main_app.MAIN_APP_PATHS at main application layout if you want to use it at engine.

Resources