Receiving AbstractController::DoubleRenderError when using authenticate_or_request_with_http_basic() - ruby-on-rails-3.1

I have a working controller action that breaks when I add a call to authenticate_or_request_with_http_basic at the beginning of the block. Here is the non-functioning action code:
def index
authenticate_or_request_with_http_basic do |username, password|
username == "test1" and password == "test"
end
render layout: false, content_type: 'application/xml'
end
I am receiving a "AbstractController::DoubleRenderError" error response in my browser window with the following message:
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".
When I place the "authenticate_or_request_with_http_basic" logic in a separate action and then configure a before_filter to run it for the index action, all is well. However, I'm not reusing this logic for any actions other than index, and I'd like to know why the above code doesn't work.
Solution
I was able to find a solution with the help of Tyler and RKB. Here it is:
authenticated = authenticate_or_request_with_http_basic "Authentication Required" do |username, password|
#user = User.find_by_username(username)
#user != nil && password == "test"
end
return unless authenticated == true
authenticate_or_request_with_http_basic returns "true" if authentication was successful. It returns a 401 on failure.

It sounds like authenticate_or_request_with_http_basic is either render or redirecting. I'm guessing it works when you move it to a before_filter because it returns false which cancels the callback chain and causes the 2nd render to not occur? Just a guess...

authenticate_or_request_with_http_basic calls render to send the HTTP response needed to do HTTP basic authentication (status code 401 Unauthorized). See the Rails code for the details.

Related

how to send SystemExit message to server

So I have a Sinatra API containing this piece of code in a model:
def self.delete(account_id)
# using Sequel, not ActiveRecord:
if Account[account_id][:default] == true
abort("Impossible to delete a default account. Please first set another account to default.")
end
# rest of the code
end
then, in app.rb :
delete '/account/:id' do
if Account.delete(params[:id]) == 1
status 200
else
status 500
end
end
On the client side (vuejs app), I would like the error message to be displayed. Instead, when the error produces, I get a SystemExit with the error message.
How do I send that SystemExit message to the server?
In Ruby in general you want to break out of execution either by using return, exceptions or throw/catch. abort is rarely if ever used as it will immediately halt execution of the entire program - and prevent you from doing stuff like cleaning up or actually sending a meaningful response to the client besides whatever error page the web server will render if you just quit the job half way though.
You can easily implement this by creating your own exception class:
class AccountDeletionError < StandardError
end
def self.delete(account_id)
# using Sequel, not ActiveRecord:
if Account[account_id][:default] == true
raise AccountDeletionError.new, "Impossible to delete a default account. Please first set another account to default."
end
end
delete '/account/:id' do
if Account.delete(params[:id]) == 1
status 200
end
rescue AccountDeletionError => e
status 409 # not 500.
content_type :json
{ error: e.message }.to_json
end
end
Now that you know how to handle errors you should probally address the next possible one - when an account cannot be found.

Return immediately from `call(env)` after providing a response

I have a Rack web server app to validate in call(env) the provided HTTP query string and return different responses immediately (with appropriate erb) if those bits of validations fail.
I'm calling the following method to provide a response:
def respond(http_status, http_headers, html_body = '')
# Provide HTTP response
html_body = yield if block_given?
[http_status, http_headers, [html_body]]
end
which I gleaned from the web.
I'm then calling respond(...) from different points inside my call(env) method rather like this:
def call(env)
case blah
when '/'
if validation_a_fails
respond(invalid_a)
else
set up a variable for later use...
end
if validation_b_fails
respond(invalid_b)
else
set up another variable for later use...
end
if validation_c_fails
respond(invalid_c)
else
set up something else for later use...
end
else # not root url
respond(404_situation)
end
end
end
I expected that a call to respond(invalid_a) would exit the call method immediately. However, it doesn't. Instead, the rest of the call method are executed.
How can I get the respond(...) to return immediately to the calling browser?
if validation_a_fails
return respond(invalid_a)
else
in your case you can't omit return because you have multiple if-elses so it'd go through them.

Ruby on Rails - Redirect/render via action does not work

I have the following code:
def isLoggedIn
if session[:username].nil?
puts "Not logged in!"
return render :controller=>"admin", :action=>"login"
end
end
And the following code which calls this action at the top to check the status of the user.
def view_gallery
isLoggedIn
.
.
.
.
\/
return render "view_gallery"
end
The problem I am having is that the code continues to execute past isLoggedIn although the console is logging "Not logged in!" because as expected the user isn't logged in. This should then render an alternative layout which it doesn't do. If I mispell the action (say "logib") then it complains about a missing template - so I can see it running the render function.
The same occurs if I change render to redirect_to and/or if I move the return statement to the end of the line.
Any idea's?
Many thanks
The Rails Guides says to do this in the following way:
render :controller=>"admin", :action=>"login" and return

What does Sinatra::Base.condition actually do?

I've come across the sinatra condition method and am puzzled in how it works.
I have a piece of code:
def auth user
condition do
redirect '/login' unless user_logged_in?
end
end
Which checks to see if a user is logged for certain routes, an example route:
get '/', :auth => :user do
erb :index
end
The method user_logged_in? is defined in a helper file in the lib directory of the project:
def user_logged_in?
if session[:user]
#user = session[:user]
return #user
end
return nil
end
So, the question is:
How does the condition block know what the session[:user] contains, when at the get '/' route the session[:user] hasn't even been set?
The condition method is defined in the following GitHub page: sinatra base condition method
Thanks.
When you define a route, the key of each member of the options hash is called as a method, with the value passed as the arguments.
So when you do get '/', :auth => :user do ... the method auth gets called with the argument :user. This in turn calls the condition method with the block.
The condition method is actually defined just above where you link to which is a usage of it. It looks like this:
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
#conditions << generate_method(name, &block)
end
The generate_method method converts the block into a method with the given name, and then this method is saved in the #conditions array. The contents of #conditions are then saved with the definition of the route, and #conditions is cleared ready for the next route definition.
At this point, the block of code passed to condition hasn't been executed. It has in effect been saved for later.
When an actual request comes in, if the request path matches the route, then each condition associated with that route is executed to check that it is fulfilled. In this example, this is when redirect '/login' unless user_logged_in? is first executed, so the session will have been set up and session[:user] will be available (or not if they're not logged in).
The important thing to understand about this is that when you pass a block to a method, the code in that block is not necessarily called right away. In this case the code in the block is only called when an actual request arrives.
Because Sinatra is responsible for calling both the condition methods and the route methods. Therefore, it should be safe to assume that whatever is set when your route method executes is also set when your condition execute.
Take a look at the code starting here: conditions are called one by one; if all conditions match, then the block gets called. Nothing much happens between checking conditions and calling the block: they are basically run with the same context.

Selectively allow some urls through Rack::Auth::Basic

I've set up a blog that I'd like to be minimally secured (i.e., I just want to keep out random people I don't know, I'm not trying to implement NSA-like security measures). I'm using toto with Rack::Auth::Basic to "secure" the site. I'd like to let through index.xml so that blog readers will be able to read the feed without dealing with password (and yes, I know that this is a big hole in my "security").
How do I let through this one url with Rack::Auth::Basic?
This is how I added basic auth to my site:
use Rack::Auth::Basic, "blog" do |username, password|
[username, password] == ['generic', 'stupidanddumbpassword']
end
How about some good ol' fashioned inheritance? Rack::Auth::Basic is a simple rack app (source: https://github.com/rack/rack/blob/master/lib/rack/auth/basic.rb), so it's possible to override the #call method and skip authentication when the request path matches '/index.xml':
class BlogAuth < Rack::Auth::Basic
def call(env)
request = Rack::Request.new(env)
case request.path
when '/index.xml'
#app.call(env) # skip auth
else
super # perform auth
end
end
end
use BlogAuth, "blog" do |username, password|
[username, password] == ['generic', 'stupidanddumbpassword']
end
For more background on rack, check out: http://rack.rubyforge.org/doc/SPEC.html
I haven't tried #Iain's suggestion about Rack::URLMap, but it looks like it could also be a good option.
Thanks for the answer!
I used this solution too, but made a small change.
because the current solution will probably result in a duplication of code if an app will require more then one path to be accessible,
I changed the code to:
class AppBasicAuth < Rack::Auth::Basic
def call(env)
request = Rack::Request.new(env)
allowed_paths = ['/api/v2/get_new.json']
if allowed_paths.include? request.path
#app.call(env) # skip auth
else
super # perform auth
end
end
end

Resources