From 304d5486d6682597cecf30d94f64f2e00cda028c Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Fri, 13 Dec 2019 13:50:35 -0500 Subject: [PATCH] subnet bans --- lib/philomena/bans.ex | 8 +- lib/philomena/bans/subnet.ex | 41 ++++++- .../admin/subnet_ban_controller.ex | 100 ++++++++++++++++++ .../admin/subnet_ban/_form.html.slime | 22 ++++ .../admin/subnet_ban/edit.html.slime | 6 ++ .../admin/subnet_ban/index.html.slime | 52 +++++++++ .../templates/admin/subnet_ban/new.html.slime | 5 + .../views/admin/subnet_ban_view.ex | 14 +++ lib/philomena_web/views/report_view.ex | 1 - 9 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 lib/philomena_web/controllers/admin/subnet_ban_controller.ex create mode 100644 lib/philomena_web/templates/admin/subnet_ban/_form.html.slime create mode 100644 lib/philomena_web/templates/admin/subnet_ban/edit.html.slime create mode 100644 lib/philomena_web/templates/admin/subnet_ban/index.html.slime create mode 100644 lib/philomena_web/templates/admin/subnet_ban/new.html.slime create mode 100644 lib/philomena_web/views/admin/subnet_ban_view.ex diff --git a/lib/philomena/bans.ex b/lib/philomena/bans.ex index 6d2e1685..2f907a60 100644 --- a/lib/philomena/bans.ex +++ b/lib/philomena/bans.ex @@ -145,9 +145,9 @@ defmodule Philomena.Bans do {:error, %Ecto.Changeset{}} """ - def create_subnet(attrs \\ %{}) do - %Subnet{} - |> Subnet.changeset(attrs) + def create_subnet(creator, attrs \\ %{}) do + %Subnet{banning_user_id: creator.id} + |> Subnet.save_changeset(attrs) |> Repo.insert() end @@ -165,7 +165,7 @@ defmodule Philomena.Bans do """ def update_subnet(%Subnet{} = subnet, attrs) do subnet - |> Subnet.changeset(attrs) + |> Subnet.save_changeset(attrs) |> Repo.update() end diff --git a/lib/philomena/bans/subnet.ex b/lib/philomena/bans/subnet.ex index fcff1de9..5b683d34 100644 --- a/lib/philomena/bans/subnet.ex +++ b/lib/philomena/bans/subnet.ex @@ -3,6 +3,7 @@ defmodule Philomena.Bans.Subnet do import Ecto.Changeset alias Philomena.Users.User + alias RelativeDate.Parser schema "subnet_bans" do belongs_to :banning_user, User @@ -14,13 +15,47 @@ defmodule Philomena.Bans.Subnet do field :specification, EctoNetwork.INET field :generated_ban_id, :string + field :until, :string, virtual: true + timestamps(inserted_at: :created_at) end @doc false - def changeset(subnet, attrs) do - subnet + def changeset(subnet_ban, attrs) do + subnet_ban |> cast(attrs, []) - |> validate_required([]) + |> populate_until() end + + def save_changeset(subnet_ban, attrs) do + subnet_ban + |> cast(attrs, [:reason, :note, :enabled, :specification, :until]) + |> populate_valid_until() + |> put_ban_id() + |> validate_required([:reason, :enabled, :specification, :valid_until]) + end + + defp populate_until(%{data: data} = changeset) do + put_change(changeset, :until, to_string(data.valid_until)) + end + + defp populate_valid_until(changeset) do + changeset + |> get_field(:until) + |> Parser.parse() + |> case do + {:ok, time} -> + change(changeset, valid_until: time) + + {:error, _err} -> + add_error(changeset, :until, "is not a valid absolute or relative date and time") + end + end + + defp put_ban_id(%{data: %{generated_ban_id: nil}} = changeset) do + ban_id = Base.encode16(:crypto.strong_rand_bytes(3)) + + put_change(changeset, :generated_ban_id, "S#{ban_id}") + end + defp put_ban_id(changeset), do: changeset end diff --git a/lib/philomena_web/controllers/admin/subnet_ban_controller.ex b/lib/philomena_web/controllers/admin/subnet_ban_controller.ex new file mode 100644 index 00000000..839bfb07 --- /dev/null +++ b/lib/philomena_web/controllers/admin/subnet_ban_controller.ex @@ -0,0 +1,100 @@ +defmodule PhilomenaWeb.Admin.SubnetBanController do + use PhilomenaWeb, :controller + + alias Philomena.Bans.Subnet, as: SubnetBan + alias Philomena.Bans + alias Philomena.Repo + import Ecto.Query + + plug :verify_authorized + plug :load_resource, model: SubnetBan, only: [:edit, :update, :delete] + + def index(conn, %{"q" => q}) when is_binary(q) do + like_q = "%#{q}%" + + SubnetBan + |> where([sb], + sb.generated_ban_id == ^q + or fragment("to_tsvector(?) @@ plainto_tsquery(?)", sb.reason, ^q) + or fragment("to_tsvector(?) @@ plainto_tsquery(?)", sb.note, ^q) + ) + |> load_bans(conn) + end + + def index(conn, %{"ip" => ip}) when is_binary(ip) do + {:ok, ip} = EctoNetwork.INET.cast(ip) + + SubnetBan + |> where([sb], fragment("? >>= ?", sb.specification, ^ip)) + |> load_bans(conn) + end + + def index(conn, _params) do + load_bans(SubnetBan, conn) + end + + def new(conn, %{"specification" => ip}) do + {:ok, ip} = EctoNetwork.INET.cast(ip) + changeset = Bans.change_subnet(%SubnetBan{specification: ip}) + render(conn, "new.html", changeset: changeset) + end + + def new(conn, _params) do + changeset = Bans.change_subnet(%SubnetBan{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"subnet" => subnet_ban_params}) do + case Bans.create_subnet(conn.assigns.current_user, subnet_ban_params) do + {:ok, _subnet_ban} -> + conn + |> put_flash(:info, "User was successfully banned.") + |> redirect(to: Routes.admin_subnet_ban_path(conn, :index)) + + {:error, changeset} -> + render(conn, "new.html", changeset: changeset) + end + end + + def edit(conn, _params) do + changeset = Bans.change_subnet(conn.assigns.subnet) + render(conn, "edit.html", changeset: changeset) + end + + def update(conn, %{"subnet" => subnet_ban_params}) do + case Bans.update_subnet(conn.assigns.subnet, subnet_ban_params) do + {:ok, _subnet_ban} -> + conn + |> put_flash(:info, "Subnet ban successfully updated.") + |> redirect(to: Routes.admin_subnet_ban_path(conn, :index)) + + {:error, changeset} -> + render(conn, "edit.html", changeset: changeset) + end + end + + def delete(conn, _params) do + {:ok, _subnet_ban} = Bans.delete_subnet(conn.assigns.subnet) + + conn + |> put_flash(:info, "Subnet ban successfully deleted.") + |> redirect(to: Routes.admin_subnet_ban_path(conn, :index)) + end + + defp load_bans(queryable, conn) do + subnet_bans = + queryable + |> order_by(desc: :created_at) + |> preload(:banning_user) + |> Repo.paginate(conn.assigns.scrivener) + + render(conn, "index.html", layout_class: "layout--wide", subnet_bans: subnet_bans) + end + + defp verify_authorized(conn, _opts) do + case Canada.Can.can?(conn.assigns.current_user, :index, SubnetBan) do + true -> conn + false -> PhilomenaWeb.NotAuthorizedPlug.call(conn) + end + end +end diff --git a/lib/philomena_web/templates/admin/subnet_ban/_form.html.slime b/lib/philomena_web/templates/admin/subnet_ban/_form.html.slime new file mode 100644 index 00000000..3ad336d4 --- /dev/null +++ b/lib/philomena_web/templates/admin/subnet_ban/_form.html.slime @@ -0,0 +1,22 @@ += form_for @changeset, @action, fn f -> + = if @changeset.action do + .alert.alert-danger + p Oops, something went wrong! Please check the errors below. + + .field + => label f, :specification, "Specification:" + = text_input f, :specification, class: "input", placeholder: "Specification", required: true + + .field + => label f, :reason, "Reason (shown to the banned user, and to staff on the user's profile page):" + = text_input f, :reason, class: "input input--wide", placeholder: "Reason", required: true + + .field + => label f, :note, "Admin-only note:" + = text_input f, :note, class: "input input--wide", placeholder: "Note" + + .field + => label f, :until, "End time relative to now, in simple English (e.g. \"1 week from now\"):" + = text_input f, :until, class: "input input--wide", placeholder: "Until", required: true + + = submit "Save Ban", class: "button" diff --git a/lib/philomena_web/templates/admin/subnet_ban/edit.html.slime b/lib/philomena_web/templates/admin/subnet_ban/edit.html.slime new file mode 100644 index 00000000..98f35634 --- /dev/null +++ b/lib/philomena_web/templates/admin/subnet_ban/edit.html.slime @@ -0,0 +1,6 @@ +h1 Editing ban + += render PhilomenaWeb.Admin.SubnetBanView, "_form.html", changeset: @changeset, action: Routes.admin_subnet_ban_path(@conn, :update, @subnet), conn: @conn + +br += link "Back", to: Routes.admin_subnet_ban_path(@conn, :index) diff --git a/lib/philomena_web/templates/admin/subnet_ban/index.html.slime b/lib/philomena_web/templates/admin/subnet_ban/index.html.slime new file mode 100644 index 00000000..ed3cd6e3 --- /dev/null +++ b/lib/philomena_web/templates/admin/subnet_ban/index.html.slime @@ -0,0 +1,52 @@ +h1 Subnet Bans + +- route = fn p -> Routes.admin_subnet_ban_path(@conn, :index, p) end +- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @subnet_bans, route: route + +.block + .block__header + = pagination + + .block__content + table.table + thead + tr + th Specification + th Created + th Expires + th Reason/Note + th Ban ID + th Options + + tbody + = for ban <- @subnet_bans do + tr + td + = link ban.specification, to: Routes.ip_profile_path(@conn, :show, to_string(ban.specification)) + + td + => pretty_time ban.created_at + = user_abbrv @conn, ban.banning_user + + td class=ban_row_class(ban) + = pretty_time ban.valid_until + + td + = ban.reason + + = if present?(ban.note) do + p.block.block--fixed + em + ' Note: + = ban.note + + td + = ban.generated_ban_id + + td + => link "Edit", to: Routes.admin_subnet_ban_path(@conn, :edit, ban) + ' • + => link "Destroy", to: Routes.admin_subnet_ban_path(@conn, :delete, ban), data: [confirm: "Are you really, really sure?", method: "delete"] + + .block__header.block__header--light + = pagination diff --git a/lib/philomena_web/templates/admin/subnet_ban/new.html.slime b/lib/philomena_web/templates/admin/subnet_ban/new.html.slime new file mode 100644 index 00000000..50d01a78 --- /dev/null +++ b/lib/philomena_web/templates/admin/subnet_ban/new.html.slime @@ -0,0 +1,5 @@ +h1 New Subnet Ban += render PhilomenaWeb.Admin.SubnetBanView, "_form.html", changeset: @changeset, action: Routes.admin_subnet_ban_path(@conn, :create), conn: @conn + +br += link "Back", to: Routes.admin_subnet_ban_path(@conn, :index) diff --git a/lib/philomena_web/views/admin/subnet_ban_view.ex b/lib/philomena_web/views/admin/subnet_ban_view.ex new file mode 100644 index 00000000..3ac4ec9b --- /dev/null +++ b/lib/philomena_web/views/admin/subnet_ban_view.ex @@ -0,0 +1,14 @@ +defmodule PhilomenaWeb.Admin.SubnetBanView do + use PhilomenaWeb, :view + + import PhilomenaWeb.ProfileView, only: [user_abbrv: 2] + + defp ban_row_class(%{valid_until: until, enabled: enabled}) do + now = DateTime.utc_now() + + case enabled and DateTime.diff(until, now) > 0 do + true -> "success" + _false -> "danger" + end + end +end diff --git a/lib/philomena_web/views/report_view.ex b/lib/philomena_web/views/report_view.ex index 686c4dc6..6c89f8c8 100644 --- a/lib/philomena_web/views/report_view.ex +++ b/lib/philomena_web/views/report_view.ex @@ -63,7 +63,6 @@ defmodule PhilomenaWeb.ReportView do do: link "User '#{r.name}'", to: Routes.profile_path(conn, :show, r) def link_to_reported_thing(_conn, report) do - IO.inspect report "Reported item permanently destroyed." end end