diff --git a/lib/philomena/bans.ex b/lib/philomena/bans.ex index f8e38f5b..19a01664 100644 --- a/lib/philomena/bans.ex +++ b/lib/philomena/bans.ex @@ -293,4 +293,57 @@ defmodule Philomena.Bans do def change_user(%User{} = user) do User.changeset(user, %{}) end + + @doc """ + Returns the first ban, if any, that matches the specified request + attributes. + """ + def exists_for?(user, ip, fingerprint) do + now = DateTime.utc_now() + queries = + subnet_query(ip, now) ++ + fingerprint_query(fingerprint, now) ++ + user_query(user, now) + + union_all_queries(queries) + |> limit(1) + |> Repo.one() + end + + defp fingerprint_query(nil, _now), do: [] + defp fingerprint_query(fingerprint, now) do + [ + Fingerprint + |> select([:id, :reason, :valid_until]) + |> where([f], f.enabled and f.valid_until > ^now) + |> where([f], f.fingerprint == ^fingerprint) + ] + end + + defp subnet_query(nil, _now), do: [] + defp subnet_query(ip, now) do + {:ok, inet} = EctoNetwork.INET.cast(ip) + + [ + Subnet + |> select([:id, :reason, :valid_until]) + |> where([s], s.enabled and s.valid_until > ^now) + |> where(fragment("specification >>= ?", ^inet)) + ] + end + + defp user_query(nil, _now), do: [] + defp user_query(user, now) do + [ + User + |> select([:id, :reason, :valid_until]) + |> where([u], u.enabled and u.valid_until > ^now) + |> where([u], u.user_id == ^user.id) + ] + end + + defp union_all_queries([query]), + do: query + defp union_all_queries([query | rest]), + do: query |> union_all(^union_all_queries(rest)) end diff --git a/lib/philomena_web/endpoint.ex b/lib/philomena_web/endpoint.ex index f787a00d..22ae9a93 100644 --- a/lib/philomena_web/endpoint.ex +++ b/lib/philomena_web/endpoint.ex @@ -5,6 +5,9 @@ defmodule PhilomenaWeb.Endpoint do websocket: true, longpoll: false + # Overwrite remote_ip based on X-Forwarded-For + plug RemoteIp + # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phx.digest @@ -48,6 +51,6 @@ defmodule PhilomenaWeb.Endpoint do plug PhilomenaWeb.Plugs.ReloadUser plug PhilomenaWeb.Plugs.RenderTime - plug PhilomenaWeb.Plugs.CurrentFilter + plug PhilomenaWeb.Plugs.Referrer plug PhilomenaWeb.Router end diff --git a/lib/philomena_web/plugs/current_ban.ex b/lib/philomena_web/plugs/current_ban.ex new file mode 100644 index 00000000..e8d04eaf --- /dev/null +++ b/lib/philomena_web/plugs/current_ban.ex @@ -0,0 +1,32 @@ +defmodule PhilomenaWeb.Plugs.CurrentBan do + @moduledoc """ + This plug loads the ban for the current user. + + ## Example + + plug PhilomenaWeb.Plugs.Ban + """ + alias Philomena.Bans + alias Plug.Conn + alias Pow.Plug + + @doc false + @spec init(any()) :: any() + def init(opts), do: opts + + @doc false + @spec call(Conn.t(), any()) :: Conn.t() + def call(conn, _opts) do + conn = + conn + |> Conn.fetch_cookies() + + fingerprint = conn.cookies["_ses"] + user = Plug.current_user(conn) + ip = conn.remote_ip + + ban = Bans.exists_for?(user, ip, fingerprint) + + Conn.assign(conn, :current_ban, ban) + end +end \ No newline at end of file diff --git a/lib/philomena_web/plugs/referrer.ex b/lib/philomena_web/plugs/referrer.ex new file mode 100644 index 00000000..23f37530 --- /dev/null +++ b/lib/philomena_web/plugs/referrer.ex @@ -0,0 +1,30 @@ +defmodule PhilomenaWeb.Plugs.Referrer do + @moduledoc """ + This plug assigns the HTTP Referer, if it exists. Note the misspelling + in the standard. + + ## Example + + plug PhilomenaWeb.Plugs.Referrer + """ + + alias Plug.Conn + + @doc false + @spec init(any()) :: any() + def init(opts), do: opts + + @doc false + @spec call(Conn.t(), any()) :: Conn.t() + def call(conn, _opts) do + case Conn.get_req_header(conn, "referer") do + [] -> + conn + |> Conn.assign(:referrer, "/") + + [referrer] -> + conn + |> Conn.assign(:referrer, referrer) + end + end +end \ No newline at end of file diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index dd92a863..3a371a9c 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -9,9 +9,11 @@ defmodule PhilomenaWeb.Router do plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers + plug PhilomenaWeb.Plugs.CurrentFilter plug PhilomenaWeb.Plugs.ImageFilter plug PhilomenaWeb.Plugs.Pagination plug PhilomenaWeb.Plugs.EnsureUserEnabledPlug + plug PhilomenaWeb.Plugs.CurrentBan end pipeline :api do diff --git a/mix.exs b/mix.exs index dfb78a54..cbfa68d5 100644 --- a/mix.exs +++ b/mix.exs @@ -57,7 +57,8 @@ defmodule Philomena.MixProject do {:qrcode, "~> 0.1.5"}, {:redix, "~> 0.10.2"}, {:bamboo, "~> 1.2"}, - {:bamboo_smtp, "~> 1.7"} + {:bamboo_smtp, "~> 1.7"}, + {:remote_ip, "~> 0.1.5"} ] end diff --git a/mix.lock b/mix.lock index 9c332120..c310750d 100644 --- a/mix.lock +++ b/mix.lock @@ -5,6 +5,7 @@ "canada": {:hex, :canada, "1.0.2", "040e4c47609b0a67d5773ac1fbe5e99f840cef173d69b739beda7c98453e0770", [:mix], [], "hexpm"}, "canary": {:hex, :canary, "1.1.1", "4138d5e05db8497c477e4af73902eb9ae06e49dceaa13c2dd9f0b55525ded48b", [:mix], [{:canada, "~> 1.0.1", [hex: :canada, repo: "hexpm", optional: false]}, {:ecto, ">= 1.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"}, "comeonin": {:hex, :comeonin, "5.1.2", "fbbbbbfcf0f0e9900c0336d16c8d462edf838ba1759577e29cc5fbd7c28a4540", [:mix], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, @@ -23,6 +24,7 @@ "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.6.0", "0a148c836e8e5fbec82c3cea37465a603bd42e314b73a8448ad50020757a00bd", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, @@ -47,6 +49,7 @@ "qrcode": {:hex, :qrcode, "0.1.5", "551271830515c150f34568345b060c625deb0e6691db2a01b0a6de3aafc93886", [:mix], [], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "redix": {:hex, :redix, "0.10.2", "a9eabf47898aa878650df36194aeb63966d74f5bd69d9caa37babb32dbb93c5d", [:mix], [{:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "remote_ip": {:hex, :remote_ip, "0.1.5", "0d8eb8a80387e196b0f48b3e7efb75525d1097cdb0ec70a4c69dd2ce9237c16c", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:inet_cidr, "~> 1.0", [hex: :inet_cidr, repo: "hexpm", optional: false]}, {:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "retry": {:hex, :retry, "0.13.0", "bb9b2713f70f39337837852337ad280c77662574f4fb852a8386c269f3d734c4", [:mix], [], "hexpm"}, "scrivener": {:hex, :scrivener, "2.7.0", "fa94cdea21fad0649921d8066b1833d18d296217bfdf4a5389a2f45ee857b773", [:mix], [], "hexpm"}, "scrivener_ecto": {:hex, :scrivener_ecto, "2.2.0", "53d5f1ba28f35f17891cf526ee102f8f225b7024d1cdaf8984875467158c9c5e", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm"},