How to abort execution in a local plug? - phoenix-framework

If I use a plug locally in a controller.
For example:
defmodule MyApp.MyController do
use MyApp.Web, :controller
plug :authenticate_user_by_api_key!
def some_method(conn, params) do
end
defp authenticate_user_by_api_key!(conn, params) do
# Authenticate
#How to permit execution or abort it?
end
end
As I understand, authenticate_user_by_api_key will be called before any method, correct?
I need to allow or abort execution of called method, how would I do so?

As I understand, authenticate_user_by_api_key will be called before any method, correct?
Yes.
I need to allow or abort execution of called method, how would I do so?
You can use Plug.Conn.halt/1 to halt the execution of further plugs, including the function that was originally going to be called by the Phoenix Router. For valid authentication, you can just return the original conn and execution will continue.
(Note: the second argument to the plug function is the plug options, not Phoenix's params. To access params in the plug, you can use conn.params.)
defp authenticate_user_by_api_key!(conn, opts) do
if some_condition_which_is_true_for_valid_key do
conn
else
# you'll generally want to respond with something before halting execution
conn
|> put_status(400)
|> text("invalid api key")
|> halt
end
end

Related

RSpec: Expecting a method to be called causes that method to not actually be called

I have some code that could be represented in very simple terms as:
def method_a(key)
hash = method b(key)
hash.delete(key)
end
def method_b(key)
return { key => 1 }
end
and then an rspec test like
it 'calls method_b'
expect(someClass).to receive(:method_b).with(key)
method_a(key)
end
However I then get an error in the second line of method_a because it's trying to call delete on a nil object. When I debug, I can see that the logic inside method_b is never actually being invoked. It's not failing somewhere in method_b, it's literally not calling it at all. If I get rid of the expect statement in the test, this error goes away. It seems like the expect statement is causing it to just skip over the actual call to method_b, leaving me with a nil value instead of the hash I'm expecting.
Is there a way I can stop it from skipping over method_b, or at least terminate execution once the expect statement is successful, so I don't run into the error on the next line?
When you set a message expectation, it overrides the original code, unless you explicitly tell RSpec not to:
expect(someClass).to receive(:method_b).with(key).and_call_original

Upgrading Rspec to Rspec 2.99, want to use let connection variable in before/after hooks

I have a head scratcher. When upgrading rspec I am getting:
DEPRECATION: let declaration `directory` accessed in an `after(:all)` hook
at:
`let` and `subject` declarations are not intended to be called
Now I understand that I cannot use let defined variables in before/after hooks. However, the methods that are used with my test suite uses a connection to preform some REST API action:
let {:connection} {user_base}
after(:all) do
connection.delete_folder
end
My question is this: Is there anyway of getting around this without making every connection an instance variable? I want to avoid calling connection variable each time I want to preform an action e.g.
before(:all) do
#connection = user_base
end
it "adds folder" do
#connection.add_folder
end
it "edits folder" do
#connection.edit_folder
end
I think RSpec wants you to run the block before each example instead of your once before all examples:
let(:connection) { user_base }
after do # short for `after(:each) do`
connection.delete_folder
end
it "adds folder" do
connection.add_folder
end
it "edits folder" do
connection.edit_folder
end

How to setup session and CSRF protection in Plug?

I'm currently learning Elixir by trying to make a small Plug project. Most of it goes fine except for sessions and CSRF protection. When I make GET requests, I see no session cookies in Firefox or HTTPie, and when I make POST requests, I get a 500 error (but the logger is silent).
Here's my current router code:
defmodule ElxSimpleApi.Web do
require Logger
use Plug.Router
import Plug.Conn
alias ElxSimpleApi.{Models, Repo}
plug Plug.Logger, log: :debug
plug Plug.Parsers, parsers: [:urlencoded, :json],
pass: ["text/*", "application/json"],
json_decoder: Poison
plug :put_secret_key_base
plug Plug.Session, store: :cookie,
key: "_elx_simple_api_session",
encryption_salt: "elxsimpleapienc",
signing_salt: "elxsimpleapisign",
log: :debug
plug :fetch_session
plug Plug.CSRFProtection
plug :match
plug :dispatch
# A bunch of routes here, omitted for clarity
match _ do
send_resp(conn, 404, "oops")
end
defp fetch_person(:int, id), do: Models.Person |> Repo.get(id)
defp fetch_person(:str, sid), do: fetch_person(:int, String.to_integer(sid))
defp ecto_to_map(struct) do
struct |> Map.from_struct |> Map.drop([:__meta__])
end
defp put_secret_key_base(conn, _) do
put_in conn.secret_key_base, "d5b2hHZGsUfcYB8lImcxooaLfVBlB5bg/z9a99jjHuXTvt7yb5neykHrYEjuNFnD"
end
end
Please tell me what I'm doing wrong. Thank you!
Update: Thanks to #josé-valim's advice, I now know that the 500 error is due to the invalid CSRF token. But the cookie still isn't being set.
Apparently, the problem was connected to this issue: Plug.CSRFProtection doesn't automatically put the CSRF token in session, and Plug.Session doesn't actually create a session until something is put in it.
I had to add this plug (right after plug Plug.CSRFProtection):
defp put_csrf_token_in_session(conn, _) do
Plug.CSRFProtection.get_csrf_token
conn |> put_session("_csrf_token", Process.get(:plug_unmasked_csrf_token))
end
If you want to get csrf_token it is better to use
Plug.CSRFProtection.get_csrf_token()
instead of hitting Process.get directly.
Currently, if csrf_protection(protect_from_forgery) enabled it will set token to the session field "_csrf_token" by default. Name of the field can be configured.
If you make your own session store(#behaviour Plug.Session.Store) and you want to have csrf_protection work you need to handle "_csrf_token" in your custom session store yourself.
I also add a plug like
defp put_csrf_token_in_session(conn, _) do
conn
|> Plug.Conn.put_req_header("x-csrf-token", Plug.CSRFProtection.get_csrf_token)
|> put_session("_csrf_token", Process.get(:plug_unmasked_csrf_token))
end

How to fake a web service with ruby and MiniTest

I'm writing a Rails app to send text messages using the Twilio API:
http://www.twilio.com/docs/api/rest/sending-sms
and to this end, I do:
client = Twilio::REST::Client.new account_sid, auth_token
client.account.sms.messages.create({
# stuff ...
})
That's all good and nice -- however, I don't want my tests to send a bunch of text messages because that would be stupid. So, I'd like to override Twilio::REST::Client.new to give me an object that'll let me call acccount.sms.messages.create in my tests without undue fuss.
I have a solution that works, but feels ugly:
def mock_twilio_service(stub_client)
Twilio::REST::Client.stub :new, stub_client do
yield
end
end
class Recordy
attr_accessor :calls
def initialize
#calls = []
end
def method_missing(method, *args)
ret = self.class.new
#calls << {
method: method,
args: args,
block_given: block_given?,
ret: ret
}
yield if block_given?
ret
end
end
and then in my test:
test "send a text" do
cli = Recordy.new
mock_twilio_service cli do
# ... stuff
end
end
I feel like I'm missing something Super Obvious, but I'm not sure. Am I? Or am I totally barking up the wrong tree? (Yes, I've looked at How do I mock a Class with Ruby? but I don't think it's quite the same...?)
Another idea would be to use WebMock. As your client is making requests to Twilio. You can just stub out the requests. Within the stub you can also define what is returned from the requests and with which parameters it can be called.
And when you set
WebMock.disable_net_connect!
it is sure that no real requests can be made from the test.
This way you don't change any behavior of your test and will not rely on an external API for your tests to pass.
Twilio evangelist here.
We wrote Test Credentials exactly for this scenario. Test Credentials are a special set of credentials (AccountSid and AuthToken) that you can use when you make requests to the Twilio REST API that tell it to basically just go through the motions of making a phone call or sending a text message, but not actually do it (or charge you for it).
You can also use a special set of phone numbers to get Twilio to return specific success or error conditions.
You can find your test credentials in your Twilio dashboard.
Hope that helps.

Ruby Pipe and C Extensions

I have some ruby code (1.9) like
#rd,#wd = IO.pipe
def callback()
puts #wd.class
# do stuff
end
pid = fork do
#rd.close
register_callback(:callback)
end
#wd.close
# do some stuff in parent process
register_callback is a C extension that makes a blocking system call, and upon certain conditions will call the ruby function associated with the symbol passed in.
However, #wd is of type NilClass according to the message I get when I run this program and it tries to access #wd in the callback function, which doesn't make any sense to me. Any help is appreciated.
after you call the register_callback method. The rest of the code continues to execute (as you do the register_callback method call inside fork). So #wd.close runs, before your callback is made. Hence when the callback() method is finally called. #wd is nil (which is the result of #wd.close).

Resources