From 3a753312c1033f6373e3e216398cc01ada68a429 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Fri, 14 Feb 2020 09:22:17 +0100
Subject: [PATCH] Validate Date header in HTTPSignatures

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/web/plugs/http_signatures.ex        | 14 ++++++-
 test/web/plugs/http_signatures_test.exs | 52 +++++++++++++++++++++++++
 2 files changed, 65 insertions(+), 1 deletion(-)
 create mode 100644 test/web/plugs/http_signatures_test.exs

diff --git a/lib/web/plugs/http_signatures.ex b/lib/web/plugs/http_signatures.ex
index fa4ae1fbc..80333a782 100644
--- a/lib/web/plugs/http_signatures.ex
+++ b/lib/web/plugs/http_signatures.ex
@@ -43,7 +43,8 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
 
           signature_valid = HTTPSignatures.validate_conn(conn)
           Logger.debug("Is signature valid ? #{inspect(signature_valid)}")
-          assign(conn, :valid_signature, signature_valid)
+          date_valid = date_valid?(conn)
+          assign(conn, :valid_signature, signature_valid && date_valid)
         else
           Logger.debug("No signature header!")
           conn
@@ -53,4 +54,15 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
         conn
     end
   end
+
+  @spec date_valid?(Plug.Conn.t()) :: boolean()
+  defp date_valid?(conn) do
+    with [date | _] <- get_req_header(conn, "date") || [""],
+         {:ok, date} <- Timex.parse(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") do
+      Timex.diff(date, DateTime.utc_now(), :hours) <= 12 &&
+        Timex.diff(date, DateTime.utc_now(), :hours) >= -12
+    else
+      _ -> false
+    end
+  end
 end
diff --git a/test/web/plugs/http_signatures_test.exs b/test/web/plugs/http_signatures_test.exs
new file mode 100644
index 000000000..581e4419d
--- /dev/null
+++ b/test/web/plugs/http_signatures_test.exs
@@ -0,0 +1,52 @@
+defmodule Mobilizon.Web.Plug.HTTPSignaturesTest do
+  use Mobilizon.Web.ConnCase
+  import Mock
+
+  alias Mobilizon.Federation.HTTPSignatures.Signature
+  alias Mobilizon.Web.Plugs.HTTPSignatures, as: HTTPSignaturesPlug
+
+  test "tests that date window is acceptable" do
+    date = NaiveDateTime.utc_now() |> Timex.shift(hours: -3) |> Signature.generate_date_header()
+
+    with_mock HTTPSignatures, validate_conn: fn _ -> true end do
+      conn =
+        build_conn()
+        |> put_req_header("signature", "signature")
+        |> put_req_header("date", date)
+        |> HTTPSignaturesPlug.call(%{})
+
+      assert called(HTTPSignatures.validate_conn(:_))
+      assert conn.assigns.valid_signature
+    end
+  end
+
+  test "tests that date window is not acceptable (already passed)" do
+    date = NaiveDateTime.utc_now() |> Timex.shift(hours: -30) |> Signature.generate_date_header()
+
+    with_mock HTTPSignatures, validate_conn: fn _ -> true end do
+      conn =
+        build_conn()
+        |> put_req_header("signature", "signature")
+        |> put_req_header("date", date)
+        |> HTTPSignaturesPlug.call(%{})
+
+      refute conn.assigns.valid_signature
+      assert called(HTTPSignatures.validate_conn(:_))
+    end
+  end
+
+  test "tests that date window is not acceptable (in the future)" do
+    date = NaiveDateTime.utc_now() |> Timex.shift(hours: 30) |> Signature.generate_date_header()
+
+    with_mock HTTPSignatures, validate_conn: fn _ -> true end do
+      conn =
+        build_conn()
+        |> put_req_header("signature", "signature")
+        |> put_req_header("date", date)
+        |> HTTPSignaturesPlug.call(%{})
+
+      refute conn.assigns.valid_signature
+      assert called(HTTPSignatures.validate_conn(:_))
+    end
+  end
+end