Rails controller rescue exception in `lib` directory - ruby-on-rails

I'm using a wrapper around a Wikipedia OmniAuth gem in my Devise initializer that is periodically throwing JWT::DecodeErrors. I'm trying to handle these errors with a custom error message, rather than throwing a 500. (Stick with me here.)
In my Devise initializer, I set up the strategy as such:
config.omniauth :mediawiki,
Figaro.env.wikipedia_token,
Figaro.env.wikipedia_secret,
strategy_class: CustomStrategy,
client_options: {
site: "https://#{Figaro.env.wiki_language}.wikipedia.org"
}
My CustomStrategy inherits from the default MediaWiki strategy and adds some data to the request object to check for it later in the ErrorsController:
# lib/custom_strategy.rb
class LoginError < StandardError; end
class CustomStrategy < OmniAuth::Strategies::Mediawiki
def parse_info(jwt_data)
begin
super
rescue JWT::DecodeError
request.env['JWT_ERROR'] = true
request.env['JWT_DATA'] = jwt_data
raise ::LoginError.new("\nA JWT::DecodeError occurred. Here is the web token data:\n#{jwt_data.body.inspect}")
end
end
end
So here I try to catch the JWT::DecodeError by raising a LoginError that I'm hoping to catch in my controller:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_login_error
...
protected
def handle_login_error
redirect_to errors_login_error_path
end
end
The problem is that rescue_from only handles exceptions thrown in the controller.
Since this exception is thrown in my strategy, I don't have the opportunity to rescue it, and Rails just treats it as a 500:
[2016-01-26 12:10:02.183 FATAL] StandardError (
A JWT::DecodeError occurred. Here is the web token data:
"{\"error\":\"mwoauthdatastore-access-token-not-found\",\"message\":\"No approved grant was found for that authorization token.\"}"):
lib/custom_strategy.rb:9:in `rescue in parse_info'
lib/custom_strategy.rb:6:in `parse_info'
[2016-01-26 12:10:02.251 INFO ] Processing by ErrorsController#internal_server_error as HTML
[2016-01-26 12:10:02.251 INFO ] Parameters: {"oauth_verifier"=>"XXXXXXXXXXXXXXXX", "oauth_token"=>"XXXXXXXXXXXXXXX"}
[2016-01-26 12:10:02.971 INFO ] Rendered errors/internal_server_error.html.erb within layouts/application (3.8ms)
[2016-01-26 12:10:03.204 INFO ] Rendered shared/_head.html.haml (10.1ms)
[2016-01-26 12:10:03.215 INFO ] Rendered shared/_nav.html.haml (9.7ms)
[2016-01-26 12:10:03.218 INFO ] Rendered shared/_foot.html.haml (2.3ms)
[2016-01-26 12:10:03.219 INFO ] Completed 500 Internal Server Error in 967ms (Views: 265.8ms | ActiveRecord: 0.0ms)
So here, Rails treats it as a 500, and I'm using custom error pages, not the Rails defaults.
For reference, here's my ErrorsController:
class ErrorsController < ApplicationController
respond_to :html, :json
def file_not_found
render status: 404
end
def unprocessable
render status: 422
end
def internal_server_error
if request.env['JWT_ERROR']
return login_error
end
render status: 500
end
def incorrect_passcode
render status: 401
end
def login_error
#return render status: 599
end
end
What's the best way to handle this exception that happens in the jwt-ruby gem, which is a dependency of the oauth-mediawiki gem that I'm wrapping with a custom strategy that hopes to catch the JWT::DecodeError and handle it within my app? If this specific error arises, I want to render a special view, not just the 500 view.
Edit 2: Further Articulation of the Problem
The problem is that I can't figure out a way to rescue the exception I've rescued and re-raised (JWT::DecodeError) in a way that allows me to render a custom view. As you can see from the log above, it just renders ErrorsContoller#internal_server_error.

Related

Rails rendering JSON causing helper method to be called twice

I'm getting strange behavior when I render JSON from my Rails app. A helper method is run twice when render :json is called. Here's the controller and method:
class UsersController < ApplicationController
def current
render json: { :errors => "Incorrect credentials" }, :status => :bad_request
end
end
I have the following helper module, with a puts statement for debugging:
module SessionsHelper
def current_user
puts "current_user"
if encrypted_id = request.headers[:user_id]
user = User.find_by(id: EncryptionService.decrypt(encrypted_id))
if user && user.authenticated?(request.headers[:remember_token])
#curent_user = user
end
end
end
end
The SessionsHelper is included in the Application Controller
class ApplicationController < ActionController::API
include SessionsHelper
end
When sent the request, I get the following:
Started GET "/user/current" for ::1 at 2021-02-12 22:06:47 -0800
Processing by UsersController#current as */*
Parameters: {"user"=>{}}
current_user
[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (0.06ms)
Completed 400 Bad Request in 1ms (Views: 0.7ms | ActiveRecord: 6.6ms | Allocations: 383)
current_user was printed, even though the function was never called. When I comment out the render json: statement, leaving:
class UsersController < ApplicationController
def current
end
end
I get the following:
Started GET "/user/current" for ::1 at 2021-02-12 22:09:43 -0800
Processing by UsersController#current as */*
Parameters: {"user"=>{}}
Completed 204 No Content in 0ms (ActiveRecord: 4.2ms | Allocations: 78)
current_user is not printed. Why would render json: call current_user? In my actual application, this is causing the database to be hit twice (although Rails wisely caches the result).
UPDATE: I'm onto something here. I ran puts caller[0] to see who was calling the function. The result:
/Users/izaguirrejoe/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/active_model_serializers-0.10.12/lib/action_controller/serialization.rb:40:in 'serialization_scope'
def serialization_scope
return unless _serialization_scope && respond_to?(_serialization_scope, true)
send(_serialization_scope)
end
Any ideas?
I see that you are using active_model_serializers, if you check out their docs it says here, the default serialisation scope is :current_user. It also emphasizes that
IMPORTANT: Since the scope is set at render, you may want to customize it so that current_user isn't called on every request. This was also a problem in 0.9.
This causes that the current_user method is always invoked. If you want to avoid this behaviour, you can set the serialization_scope in the controller for example:
class UsersController < ApplicationController
serialization_scope nil # also you can pass a custom method here
def current
render json: { :errors => "Incorrect credentials" }, :status => :bad_request
end
end
or in some cases only by calling self.class.serialization_scope nil.

Rails 5: How to declare HEAD 200 in Rescue

I have a webhook sent to a page on my app, and I am catching SOAP errors from processing the data, but I want to send to the webhook a 200 status. Is this possible in the controller?
controller
def example_method
...
rescue Savon::SOAPFault => error
...
# Respond to the HTTP POST request by giving the 'all clear'
head 200
raise
end
First off don't place soap calls directly into your controller - instead create a client class or service object that handles it.
Your controller should only really know that it calls some kind of service and that it may fail or succeed - if you are catching library specific errors in your controller you are on your way down the rabbit hole.
So how do you rescue exceptions in your controller?
You can rescue them inline:
class SomethingController
def do_something
begin
# Something.this_may_blow_up!
rescue SomeError => error
head :ok and return
end
end
end
The key here is and return which halts the controller flow and prevents double render errors.
The other method is by using rescue_from.
class ThingsController
rescue_from ActiveRecord::RecordNotFound do
render :not_found and return
end
def show
#thing = Thing.find(params[:id])
end
end
The above is an example of how you can create a custom 404 page per controller. What happens is that when the action on the controller is invoked it is wrapped in a begin..rescue block that looks for rescue_from handlers in the controller.

Prompt error status RAILS - API

I have basically this:
class Api::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
begin
cou = params[:data].count
rescue
render status: 422, json: {
message: "Error Loading data- Check your data",
}.to_json
end
input = params[:data]
# Operations with the input data
render status: 200, json: {
message: "Succesful data calculation",
data_output: input
}.to_json
end
end
The data is: {"data":[1,34,5]}
I would like to render the status error 422, when the data has a bad sintax (example: {"dat":[1,34,5]} or {"data":1,34,5]} etc)
With the example above doe not work. When sending via cURL {"data":1,34,5]} I got:
Started POST "/api/energy" for 127.0.0.1 at 2015-05-29 08:35:50 +0200
Error occurred while parsing request parameters.
Contents:
{"data":1,5,67]}
ActionDispatch::ParamsParser::ParseError (795: unexpected token at '{"data":1,5,67]}'):
When sending {"dat":[1,34,5]} I got
Processing by Api::EnergyController#create as JSON
Parameters: {"dat"=>[1, 5, 67], "energy"=>{"dat"=>[1, 5, 67]}}
Completed 500 Internal Server Error in 1ms (Views: 0.2ms | ActiveRecord: 0.0ms)
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".):
Could anyone help me putting this error status in the correct form?

Get exception content in Rails Router

So I handle exceptions with an error controller to display dynamic content to my users in production. I have it in my route file to do:
# Errors
%w( 404 422 500 ).each do |code|
get code, :to => "errors#show", :code => code
end
The only problem is now that I'm routing on errors such as that I lose information in my controller when I want to notify Airbrake. How can I maintain the exception information and send it to Airbrake on a 500? Right now all I get is the env that was occurring at the time of the exception which is less helpful for debugging purposes.
class ErrorsController < ApplicationController
def show
notify_airbrake(env)
render status_code.to_s, :status => status_code
end
protected
def status_code
params[:code] || 500
end
end
Are you handling an error by redirecting to a URL like http://your.site/500? That will be just an HTTP request like any other, losing the exception context you're after. Instead, you probably want to be using ActionController's Rescue functionality. It would look like this:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :render_error
private
def render_error(error)
notify_airbrake(error)
render text: 500, status: 500
end
end
You can add multiple rescue_from declarations to handle different kinds of error, like the ActiveRecord::RecordNotFound from the Rails guide's example.

New Relic 404, 422 and 500 Exceptions With Rails Dynamic Exception Handling

I'm running a Rails 4.0.0.rc application using New Relic for availability / exception monitoring. I modified application.rb with this snippet to enable dynamic exception pages:
config.exceptions_app = self.routes
However, I no longer see 404, 422 or 500 exceptions in New Relic. Any idea how I get them back?
Edit:
Note: this is what the controller handling the status looks like:
class ErrorsController < ApplicationController
# GET /404
def missing
render status: 404
end
# GET /422
def unprocessable
render status: 422
end
# GET /500
def exception
render status: 500
end
end
It sounds like you want to call NewRelic::Agent.notice_error manually.
You can reconstruct the request object from the Rack env and build an exception however you would like.
Something like this:
request = Rack::Request(env)
options = {
:uri => request.url,
:referrer => request.referrer,
:request_params => request.params
}
NewRelic::Agent.notice_error(your_custom_exception, options)
Note that the request parameters will be transmitted as is so be careful to filter anything sensitive.
Sources:
I work for New Relic as Ruby Agent Engineer
Documentation for NoticedError: http://rubydoc.info/gems/newrelic_rpm/frames
You will have to set the html status code to the correct value in your errors controller. If you for example have something like this:
class ErrorsController < ApplicationController
# 404
def not_found
render "not_found", status: 404
end
end
Otherwise will rails render the error page with a 200 status code, and new relic will not pick it up as an error.

Resources