subnet bans

This commit is contained in:
byte[] 2019-12-13 13:50:35 -05:00
parent f044ab6d3e
commit 304d5486d6
9 changed files with 241 additions and 8 deletions

View file

@ -145,9 +145,9 @@ defmodule Philomena.Bans do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def create_subnet(attrs \\ %{}) do def create_subnet(creator, attrs \\ %{}) do
%Subnet{} %Subnet{banning_user_id: creator.id}
|> Subnet.changeset(attrs) |> Subnet.save_changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end
@ -165,7 +165,7 @@ defmodule Philomena.Bans do
""" """
def update_subnet(%Subnet{} = subnet, attrs) do def update_subnet(%Subnet{} = subnet, attrs) do
subnet subnet
|> Subnet.changeset(attrs) |> Subnet.save_changeset(attrs)
|> Repo.update() |> Repo.update()
end end

View file

@ -3,6 +3,7 @@ defmodule Philomena.Bans.Subnet do
import Ecto.Changeset import Ecto.Changeset
alias Philomena.Users.User alias Philomena.Users.User
alias RelativeDate.Parser
schema "subnet_bans" do schema "subnet_bans" do
belongs_to :banning_user, User belongs_to :banning_user, User
@ -14,13 +15,47 @@ defmodule Philomena.Bans.Subnet do
field :specification, EctoNetwork.INET field :specification, EctoNetwork.INET
field :generated_ban_id, :string field :generated_ban_id, :string
field :until, :string, virtual: true
timestamps(inserted_at: :created_at) timestamps(inserted_at: :created_at)
end end
@doc false @doc false
def changeset(subnet, attrs) do def changeset(subnet_ban, attrs) do
subnet subnet_ban
|> cast(attrs, []) |> cast(attrs, [])
|> validate_required([]) |> populate_until()
end 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 end

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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)
' &bull;
=> 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

View file

@ -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)

View file

@ -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

View file

@ -63,7 +63,6 @@ defmodule PhilomenaWeb.ReportView do
do: link "User '#{r.name}'", to: Routes.profile_path(conn, :show, r) do: link "User '#{r.name}'", to: Routes.profile_path(conn, :show, r)
def link_to_reported_thing(_conn, report) do def link_to_reported_thing(_conn, report) do
IO.inspect report
"Reported item permanently destroyed." "Reported item permanently destroyed."
end end
end end