Filter updates

This commit is contained in:
byte[] 2020-06-06 21:03:17 -04:00
parent 61cafb9054
commit 9294e54771
17 changed files with 212 additions and 16 deletions

View file

@ -215,6 +215,18 @@ defmodule Philomena.Users do
|> Repo.update()
end
def force_filter(%User{} = user, user_params) do
user
|> User.force_filter_changeset(user_params)
|> Repo.update()
end
def unforce_filter(%User{} = user) do
user
|> User.unforce_filter_changeset()
|> Repo.update()
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.

View file

@ -48,6 +48,7 @@ defmodule Philomena.Users.User do
many_to_many :roles, Role, join_through: "users_roles", on_replace: :delete
belongs_to :current_filter, Filter
belongs_to :forced_filter, Filter
belongs_to :deleted_by_user, User
# Authentication
@ -308,6 +309,16 @@ defmodule Philomena.Users.User do
put_api_key(user)
end
def force_filter_changeset(user, params) do
user
|> cast(params, [:forced_filter_id])
|> foreign_key_constraint(:forced_filter_id)
end
def unforce_filter_changeset(user) do
change(user, forced_filter_id: nil)
end
def create_totp_secret_changeset(user) do
secret = :crypto.strong_rand_bytes(15) |> Base.encode32()
data = Philomena.Users.Encryptor.encrypt_model(secret)

View file

@ -0,0 +1,38 @@
defmodule PhilomenaWeb.Admin.User.ForceFilterController do
use PhilomenaWeb, :controller
alias Philomena.Users.User
alias Philomena.Users
plug :verify_authorized
plug :load_resource, model: User, id_name: "user_id", id_field: "slug", persisted: true
def new(conn, _params) do
changeset = Users.change_user(conn.assigns.user)
render(conn, "new.html", changeset: changeset, title: "Forcing filter for user")
end
def create(conn, %{"user" => user_params}) do
{:ok, user} = Users.force_filter(conn.assigns.user, user_params)
conn
|> put_flash(:info, "Filter was forced.")
|> redirect(to: Routes.profile_path(conn, :show, user))
end
def delete(conn, _params) do
{:ok, user} = Users.unforce_filter(conn.assigns.current_user)
conn
|> put_flash(:info, "Forced filter was removed.")
|> redirect(to: Routes.profile_path(conn, :show, user))
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
true -> conn
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
end

View file

@ -16,8 +16,9 @@ defmodule PhilomenaWeb.Image.CommentController do
edit: :create_comment,
update: :create_comment
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
plug :verify_authorized when action in [:show]
plug PhilomenaWeb.FilterForcedUsersPlug when action in [:create, :edit, :update]
# Undo the previous private parameter screwery
plug PhilomenaWeb.LoadCommentPlug, [param: "id", show_hidden: true] when action in [:show]

View file

@ -8,7 +8,8 @@ defmodule PhilomenaWeb.Image.FaveController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.CanaryMapPlug, create: :vote, delete: :vote
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
plug PhilomenaWeb.FilterForcedUsersPlug
def create(conn, _params) do
user = conn.assigns.current_user

View file

@ -8,7 +8,8 @@ defmodule PhilomenaWeb.Image.VoteController do
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.CanaryMapPlug, create: :vote, delete: :vote
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
plug PhilomenaWeb.FilterForcedUsersPlug
def create(conn, params) do
user = conn.assigns.current_user

View file

@ -223,8 +223,9 @@ defmodule PhilomenaWeb.ProfileController do
defp set_admin_metadata(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
true ->
user = Repo.preload(conn.assigns.user, :current_filter)
user = Repo.preload(conn.assigns.user, [:current_filter, :forced_filter])
filter = user.current_filter
forced = user.forced_filter
last_ip =
UserIp
@ -242,6 +243,7 @@ defmodule PhilomenaWeb.ProfileController do
conn
|> assign(:filter, filter)
|> assign(:forced, forced)
|> assign(:last_ip, last_ip)
|> assign(:last_fp, last_fp)

View file

@ -12,22 +12,25 @@ defmodule PhilomenaWeb.CurrentFilterPlug do
conn = conn |> fetch_session()
user = conn |> Plug.current_user()
filter =
{filter, forced_filter} =
if user do
user
|> Repo.preload(:current_filter)
|> maybe_set_default_filter()
|> Map.get(:current_filter)
user =
user
|> Repo.preload([:current_filter, :forced_filter])
|> maybe_set_default_filter()
{user.current_filter, user.forced_filter}
else
filter_id = conn |> get_session(:filter_id)
filter = if filter_id, do: Repo.get(Filter, filter_id)
filter || Filters.default_filter()
{filter || Filters.default_filter(), nil}
end
conn
|> assign(:current_filter, filter)
|> assign(:forced_filter, forced_filter)
end
defp maybe_set_default_filter(%{current_filter: nil} = user) do

View file

@ -0,0 +1,59 @@
defmodule PhilomenaWeb.FilterForcedUsersPlug do
@moduledoc """
Halts the request pipeline if the current image belongs to the conn's
"forced filter".
"""
import Phoenix.Controller
import Plug.Conn
alias Philomena.Search.String, as: SearchString
alias Philomena.Search.Evaluator
alias Philomena.Images.Query
alias PhilomenaWeb.ImageView
def init(_opts) do
[]
end
def call(conn, _opts) do
maybe_fetch_forced(conn, conn.assigns.forced_filter)
end
defp maybe_fetch_forced(conn, nil), do: conn
defp maybe_fetch_forced(conn, forced) do
maybe_halt(conn, matches_filter?(conn.assigns.current_user, conn.assigns.image, forced))
end
defp maybe_halt(conn, false), do: conn
defp maybe_halt(conn, true) do
conn
|> put_flash(:error, "You have been blocked from performing this action on this image.")
|> redirect(external: conn.assigns.referrer)
|> halt()
end
defp matches_filter?(user, image, filter) do
matches_tag_filter?(image, filter.hidden_tag_ids) or
matches_complex_filter?(user, image, filter.hidden_complex_str)
end
defp matches_tag_filter?(image, tag_ids) do
image.tags
|> MapSet.new(& &1.id)
|> MapSet.intersection(MapSet.new(tag_ids))
|> Enum.any?()
end
defp matches_complex_filter?(user, image, search_string) do
image
|> ImageView.image_filter_data()
|> Evaluator.hits?(compile_filter(user, search_string))
end
defp compile_filter(user, search_string) do
case Query.compile(user, SearchString.normalize(search_string)) do
{:ok, query} -> query
_error -> %{match_all: %{}}
end
end
end

View file

@ -11,11 +11,19 @@ defmodule PhilomenaWeb.ImageFilterPlug do
# Assign current filter
def call(conn, _opts) do
user = conn |> Plug.current_user()
filter = conn.assigns[:current_filter]
filter = defaults(conn.assigns[:current_filter])
forced = defaults(conn.assigns[:forced_filter])
tag_exclusion = %{terms: %{tag_ids: filter.hidden_tag_ids}}
query_exclusion = invalid_filter_guard(user, filter.hidden_complex_str)
query_spoiler = invalid_filter_guard(user, filter.spoilered_complex_str)
query_exclusion = %{
bool: %{
should: [
invalid_filter_guard(user, filter.hidden_complex_str),
invalid_filter_guard(user, forced.hidden_complex_str)
]
}
}
query = %{
bool: %{
@ -29,6 +37,18 @@ defmodule PhilomenaWeb.ImageFilterPlug do
|> assign(:compiled_filter, query)
end
defp defaults(nil) do
%{
hidden_tag_ids: [],
hidden_complex_str: nil,
spoilered_complex_str: nil
}
end
defp defaults(filter) do
filter
end
defp invalid_filter_guard(user, search_string) do
case Query.compile(user, normalize(search_string)) do
{:ok, query} -> query

View file

@ -366,6 +366,7 @@ defmodule PhilomenaWeb.Router do
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
resources "/votes", User.VoteController, only: [:delete], singleton: true
resources "/wipe", User.WipeController, only: [:create], singleton: true
resources "/force_filter", User.ForceFilterController, only: [:new, :create, :delete], singleton: true
end
resources "/batch/tags", Batch.TagController, only: [:update], singleton: true

View file

@ -0,0 +1,9 @@
h1
' Force-assigning a filter for user
= @user.name
= form_for @changeset, Routes.admin_user_force_filter_path(@conn, :create, @user), [method: "post"], fn f ->
.field
=> text_input f, :forced_filter_id, placeholder: "Filter ID", class: "input", required: true
.field
= submit "Force", class: "button button--state-primary"

View file

@ -12,6 +12,12 @@
em
' (none)
= if @forced do
br
i.fa.fa-fw.fa-filter>
strong.comment_deleted> Forced Filter:
= link @forced.name, to: Routes.filter_path(@conn, :show, @filter)
br
i.far.fa-fw.fa-clock>
' Last seen
@ -78,6 +84,17 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
i.fas.fa-fw.fa-edit
span.admin__button Edit User
li
= link to: Routes.admin_user_force_filter_path(@conn, :new, @user) do
i.fas.faw-fw.fa-filter
span.admin__button Force Filter
= if @forced do
li
= link to: Routes.admin_user_force_filter_path(@conn, :delete, @user), data: [confirm: "Are you really, really sure?", method: "delete"] do
i.fas.faw-fw.fa-filter
span.admin__button Remove Force Filter
= if @user.deleted_at do
li
= link to: Routes.admin_user_activation_path(@conn, :create, @user), data: [confirm: "Are you really, really sure?", method: "post"] do

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Admin.User.ForceFilterView do
use PhilomenaWeb, :view
end

View file

@ -216,7 +216,7 @@ defmodule PhilomenaWeb.ImageView do
defp thumb_format(_, :rendered, _download), do: "png"
defp thumb_format(format, _name, _download), do: format
defp image_filter_data(image) do
def image_filter_data(image) do
%{
id: image.id,
"namespaced_tags.name": String.split(image.tag_list_plus_alias_cache || "", ", "),

View file

@ -0,0 +1,9 @@
defmodule Philomena.Repo.Migrations.AddForcedFilter do
use Ecto.Migration
def change do
alter table(:users) do
add :forced_filter_id, references(:filters), on_delete: :restrict
end
end
end

View file

@ -3,7 +3,7 @@
--
-- Dumped from database version 12.2 (Debian 12.2-2.pgdg100+1)
-- Dumped by pg_dump version 12.2 (Debian 12.2-2.pgdg90+1)
-- Dumped by pg_dump version 12.3 (Debian 12.3-1.pgdg90+1)
SET statement_timeout = 0;
SET lock_timeout = 0;
@ -1976,7 +1976,8 @@ CREATE TABLE public.users (
consumed_timestep integer,
otp_required_for_login boolean,
otp_backup_codes character varying[],
last_renamed_at timestamp without time zone DEFAULT '1970-01-01 00:00:00'::timestamp without time zone NOT NULL
last_renamed_at timestamp without time zone DEFAULT '1970-01-01 00:00:00'::timestamp without time zone NOT NULL,
forced_filter_id bigint
);
@ -4709,9 +4710,17 @@ ALTER TABLE ONLY public.image_sources
ADD CONSTRAINT image_sources_image_id_fkey FOREIGN KEY (image_id) REFERENCES public.images(id);
--
-- Name: users users_forced_filter_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_forced_filter_id_fkey FOREIGN KEY (forced_filter_id) REFERENCES public.filters(id);
--
-- PostgreSQL database dump complete
--
INSERT INTO public."schema_migrations" (version) VALUES (20200503002523);
INSERT INTO public."schema_migrations" (version) VALUES (20200503002523), (20200607000511);