fingerprint bans

This commit is contained in:
byte[] 2019-12-13 14:06:08 -05:00
parent 642ded69a5
commit fefb91644b
9 changed files with 238 additions and 8 deletions

View file

@ -49,9 +49,9 @@ defmodule Philomena.Bans do
{:error, %Ecto.Changeset{}}
"""
def create_fingerprint(attrs \\ %{}) do
%Fingerprint{}
|> Fingerprint.changeset(attrs)
def create_fingerprint(creator, attrs \\ %{}) do
%Fingerprint{banning_user_id: creator.id}
|> Fingerprint.save_changeset(attrs)
|> Repo.insert()
end
@ -69,7 +69,7 @@ defmodule Philomena.Bans do
"""
def update_fingerprint(%Fingerprint{} = fingerprint, attrs) do
fingerprint
|> Fingerprint.changeset(attrs)
|> Fingerprint.save_changeset(attrs)
|> Repo.update()
end

View file

@ -3,6 +3,7 @@ defmodule Philomena.Bans.Fingerprint do
import Ecto.Changeset
alias Philomena.Users.User
alias RelativeDate.Parser
schema "fingerprint_bans" do
belongs_to :banning_user, User
@ -14,13 +15,47 @@ defmodule Philomena.Bans.Fingerprint do
field :fingerprint, :string
field :generated_ban_id, :string
field :until, :string, virtual: true
timestamps(inserted_at: :created_at)
end
@doc false
def changeset(fingerprint, attrs) do
fingerprint
def changeset(fingerprint_ban, attrs) do
fingerprint_ban
|> cast(attrs, [])
|> validate_required([])
|> populate_until()
end
def save_changeset(fingerprint_ban, attrs) do
fingerprint_ban
|> cast(attrs, [:reason, :note, :enabled, :fingerprint, :until])
|> populate_valid_until()
|> put_ban_id()
|> validate_required([:reason, :enabled, :fingerprint, :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, "F#{ban_id}")
end
defp put_ban_id(changeset), do: changeset
end

View file

@ -0,0 +1,96 @@
defmodule PhilomenaWeb.Admin.FingerprintBanController do
use PhilomenaWeb, :controller
alias Philomena.Bans.Fingerprint, as: FingerprintBan
alias Philomena.Bans
alias Philomena.Repo
import Ecto.Query
plug :verify_authorized
plug :load_resource, model: FingerprintBan, only: [:edit, :update, :delete]
def index(conn, %{"q" => q}) when is_binary(q) do
FingerprintBan
|> where([fb],
ilike(fb.fingerprint, ^"%#{q}%")
or fb.generated_ban_id == ^q
or fragment("to_tsvector(?) @@ plainto_tsquery(?)", fb.reason, ^q)
or fragment("to_tsvector(?) @@ plainto_tsquery(?)", fb.note, ^q)
)
|> load_bans(conn)
end
def index(conn, %{"fingerprint" => fingerprint}) when is_binary(fingerprint) do
FingerprintBan
|> where(fingerprint: ^fingerprint)
|> load_bans(conn)
end
def index(conn, _params) do
load_bans(FingerprintBan, conn)
end
def new(conn, %{"fingerprint" => fingerprint}) do
changeset = Bans.change_fingerprint(%FingerprintBan{fingerprint: fingerprint})
render(conn, "new.html", changeset: changeset)
end
def new(conn, _params) do
changeset = Bans.change_fingerprint(%FingerprintBan{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"fingerprint" => fingerprint_ban_params}) do
case Bans.create_fingerprint(conn.assigns.current_user, fingerprint_ban_params) do
{:ok, _fingerprint_ban} ->
conn
|> put_flash(:info, "Fingerprint was successfully banned.")
|> redirect(to: Routes.admin_fingerprint_ban_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def edit(conn, _params) do
changeset = Bans.change_fingerprint(conn.assigns.fingerprint)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"fingerprint" => fingerprint_ban_params}) do
case Bans.update_fingerprint(conn.assigns.fingerprint, fingerprint_ban_params) do
{:ok, _fingerprint_ban} ->
conn
|> put_flash(:info, "Fingerprint ban successfully updated.")
|> redirect(to: Routes.admin_fingerprint_ban_path(conn, :index))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end
end
def delete(conn, _params) do
{:ok, _fingerprint_ban} = Bans.delete_fingerprint(conn.assigns.fingerprint)
conn
|> put_flash(:info, "Fingerprint ban successfully deleted.")
|> redirect(to: Routes.admin_fingerprint_ban_path(conn, :index))
end
defp load_bans(queryable, conn) do
fingerprint_bans =
queryable
|> order_by(desc: :created_at)
|> preload(:banning_user)
|> Repo.paginate(conn.assigns.scrivener)
render(conn, "index.html", layout_class: "layout--wide", fingerprint_bans: fingerprint_bans)
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :index, FingerprintBan) do
true -> conn
false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
end

View file

@ -46,7 +46,7 @@ defmodule PhilomenaWeb.Admin.SubnetBanController do
case Bans.create_subnet(conn.assigns.current_user, subnet_ban_params) do
{:ok, _subnet_ban} ->
conn
|> put_flash(:info, "User was successfully banned.")
|> put_flash(:info, "Subnet was successfully banned.")
|> redirect(to: Routes.admin_subnet_ban_path(conn, :index))
{:error, changeset} ->

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, :fingerprint, "Fingerprint:"
= text_input f, :fingerprint, class: "input", placeholder: "Fingerprint", 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.FingerprintBanView, "_form.html", changeset: @changeset, action: Routes.admin_fingerprint_ban_path(@conn, :update, @fingerprint), conn: @conn
br
= link "Back", to: Routes.admin_fingerprint_ban_path(@conn, :index)

View file

@ -0,0 +1,52 @@
h1 Fingerprint Bans
- route = fn p -> Routes.admin_fingerprint_ban_path(@conn, :index, p) end
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @fingerprint_bans, route: route
.block
.block__header
= pagination
.block__content
table.table
thead
tr
th Fingerprint
th Created
th Expires
th Reason/Note
th Ban ID
th Options
tbody
= for ban <- @fingerprint_bans do
tr
td
= link ban.fingerprint, to: Routes.fingerprint_profile_path(@conn, :show, ban.fingerprint)
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_fingerprint_ban_path(@conn, :edit, ban)
' &bull;
=> link "Destroy", to: Routes.admin_fingerprint_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 Fingerprint Ban
= render PhilomenaWeb.Admin.FingerprintBanView, "_form.html", changeset: @changeset, action: Routes.admin_fingerprint_ban_path(@conn, :create), conn: @conn
br
= link "Back", to: Routes.admin_fingerprint_ban_path(@conn, :index)

View file

@ -0,0 +1,14 @@
defmodule PhilomenaWeb.Admin.FingerprintBanView 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