-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
API : proxy et auth pour l'API du validateur GTFS transport (#4251)
* API : proxy et auth pour l'API du validateur GTFS transport * PR review
- Loading branch information
1 parent
ac272d3
commit f453b1e
Showing
6 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
28 changes: 28 additions & 0 deletions
28
apps/transport/lib/transport_web/api/controllers/validators_controller.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
defmodule TransportWeb.API.ValidatorsController do | ||
use TransportWeb, :controller | ||
require Logger | ||
|
||
@doc """ | ||
Proxies an HTTP request to the GTFS transport validator while logging a message | ||
with the client calling the validator. | ||
This is used to keep the GTFS validator private, gather usage and can be used | ||
to add further metrics, quotas or queues. | ||
""" | ||
def gtfs_transport(%Plug.Conn{assigns: %{client: client}} = conn, %{"url" => url}) do | ||
Logger.info("Handling GTFS validation from #{client} for #{url}") | ||
|
||
case Shared.Validation.GtfsValidator.Wrapper.impl().validate_from_url(url) do | ||
{:ok, body} -> conn |> json(body) | ||
{:error, error} -> send_error_response(conn, error) | ||
end | ||
end | ||
|
||
def gtfs_transport(%Plug.Conn{} = conn, _) do | ||
send_error_response(conn, "You must include a GTFS URL") | ||
end | ||
|
||
def send_error_response(%Plug.Conn{} = conn, message) do | ||
conn |> put_status(:bad_request) |> json(%{error: message}) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
defmodule TransportWeb.API.Plugs.Auth do | ||
@moduledoc """ | ||
A very simple plug handling authorization for HTTP requests through tokens. | ||
It gets the list of (client, token) from the `API_AUTH_CLIENTS` environment variable. | ||
If the request is authorized, the plug adds the client name to the conn assigns for further use. | ||
Otherwise, a 401 response is sent. | ||
""" | ||
import Plug.Conn | ||
|
||
def init(default), do: default | ||
|
||
def call(%Plug.Conn{} = conn, _) do | ||
case find_client(get_req_header(conn, "authorization")) do | ||
{:ok, client} -> | ||
conn |> assign(:client, client) | ||
|
||
:error -> | ||
conn | ||
|> put_status(:unauthorized) | ||
|> Phoenix.Controller.json(%{error: "You must set a valid Authorization header"}) | ||
|> halt() | ||
end | ||
end | ||
|
||
defp find_client([authorization]) do | ||
# Expected format: `client1:secret_token;client2:other_token` | ||
Application.fetch_env!(:transport, :api_auth_clients) | ||
|> String.split(";") | ||
|> Enum.map(&(&1 |> String.split(":") |> List.to_tuple())) | ||
|> Enum.find_value(:error, fn {client, secret} -> | ||
if Plug.Crypto.secure_compare(authorization, secret) do | ||
{:ok, client} | ||
end | ||
end) | ||
end | ||
|
||
defp find_client(_), do: :error | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
apps/transport/test/transport_web/controllers/api/validators_controller_test.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
defmodule TransportWeb.API.ValidatorsControllerTest do | ||
use TransportWeb.ConnCase, async: true | ||
import Mox | ||
|
||
setup :verify_on_exit! | ||
|
||
describe "/gtfs-transport" do | ||
test "without an Authorization header", %{conn: conn} do | ||
conn = conn |> get(~p"/api/validators/gtfs-transport") | ||
assert %{"error" => "You must set a valid Authorization header"} == json_response(conn, 401) | ||
end | ||
|
||
test "with an invalid Authorization header", %{conn: conn} do | ||
conn = conn |> put_req_header("authorization", "invalid") |> get(~p"/api/validators/gtfs-transport") | ||
assert %{"error" => "You must set a valid Authorization header"} == json_response(conn, 401) | ||
end | ||
|
||
test "with a valid Authorization header, no URL", %{conn: conn} do | ||
conn = conn |> put_req_header("authorization", "secret_token") |> get(~p"/api/validators/gtfs-transport") | ||
assert %{"error" => "You must include a GTFS URL"} == json_response(conn, 400) | ||
end | ||
|
||
test "with a valid Authorization header, invalid URL passed", %{conn: conn} do | ||
url = "foobar" | ||
|
||
Shared.Validation.Validator.Mock | ||
|> expect(:validate_from_url, fn ^url -> {:error, "Not a valid URL"} end) | ||
|
||
%{token: token} = authorized_client() | ||
|
||
assert %{"error" => "Not a valid URL"} == | ||
conn | ||
|> put_req_header("authorization", token) | ||
|> get(~p"/api/validators/gtfs-transport?url=#{url}") | ||
|> json_response(400) | ||
end | ||
|
||
test "with a valid Authorization header, success response", %{conn: conn} do | ||
gtfs_url = "https://example.com/gtfs.zip" | ||
validator_response = %{"validator" => "response"} | ||
|
||
Shared.Validation.Validator.Mock | ||
|> expect(:validate_from_url, fn ^gtfs_url -> {:ok, validator_response} end) | ||
|
||
%{client: client, token: token} = authorized_client() | ||
|
||
logs = | ||
ExUnit.CaptureLog.capture_log(fn -> | ||
conn = | ||
conn | ||
|> put_req_header("authorization", token) | ||
|> get(~p"/api/validators/gtfs-transport?url=#{gtfs_url}") | ||
|
||
assert validator_response == json_response(conn, 200) | ||
end) | ||
|
||
assert logs =~ "Handling GTFS validation from #{client} for #{gtfs_url}" | ||
end | ||
|
||
@spec authorized_client() :: %{client: binary(), token: binary()} | ||
defp authorized_client do | ||
clients = Application.fetch_env!(:transport, :api_auth_clients) | ||
[client, token] = clients |> String.split(";") |> hd() |> String.split(":") | ||
%{client: client, token: token} | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters