This commit is contained in:
mdashlw 2025-03-22 20:14:59 +02:00 committed by GitHub
commit d250fedf3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 203 additions and 150 deletions

View file

@ -37,6 +37,8 @@ defmodule Philomena.Users do
"""
def get_user_by_authentication_token(token) when is_binary(token) do
Repo.get_by(User, authentication_token: token)
|> Repo.preload([:roles])
|> setup_roles()
end
@doc """
@ -943,7 +945,10 @@ defmodule Philomena.Users do
defp setup_roles(nil), do: nil
defp setup_roles(user) do
role_map = Map.new(user.roles, &{&1.resource_type || &1.name, &1.name})
role_map =
user.roles
|> Enum.group_by(& &1.resource_type, & &1.name)
|> Map.new(fn {type, names} -> {type, Map.new(names, &{&1, []})} end)
%{user | role_map: role_map}
end

View file

@ -46,7 +46,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "moderator"}, :show, %Filter{}), do: true
# Privileged mods can hard-delete images
def can?(%User{role: "moderator", role_map: %{"Image" => "admin"}}, :destroy, %Image{}),
def can?(%User{role: "moderator", role_map: %{"Image" => %{"admin" => _}}}, :destroy, %Image{}),
do: true
# ...but normal ones cannot
@ -144,72 +144,97 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# And some privileged moderators can...
# Manage site notices
def can?(%User{role: "moderator", role_map: %{"SiteNotice" => "admin"}}, _action, SiteNotice),
do: true
def can?(
%User{role: "moderator", role_map: %{"SiteNotice" => %{"admin" => _}}},
_action,
SiteNotice
),
do: true
def can?(%User{role: "moderator", role_map: %{"SiteNotice" => "admin"}}, _action, %SiteNotice{}),
do: true
def can?(
%User{role: "moderator", role_map: %{"SiteNotice" => %{"admin" => _}}},
_action,
%SiteNotice{}
),
do: true
# Manage badges
def can?(%User{role: "moderator", role_map: %{"Badge" => "admin"}}, _action, Badge), do: true
def can?(%User{role: "moderator", role_map: %{"Badge" => "admin"}}, _action, %Badge{}), do: true
def can?(%User{role: "moderator", role_map: %{"Badge" => %{"admin" => _}}}, _action, Badge),
do: true
def can?(%User{role: "moderator", role_map: %{"Badge" => %{"admin" => _}}}, _action, %Badge{}),
do: true
# Manage tags
def can?(%User{role: "moderator", role_map: %{"Tag" => "admin"}}, _action, Tag), do: true
def can?(%User{role: "moderator", role_map: %{"Tag" => "admin"}}, _action, %Tag{}), do: true
def can?(%User{role: "moderator", role_map: %{"Tag" => %{"admin" => _}}}, _action, Tag),
do: true
def can?(%User{role: "moderator", role_map: %{"Tag" => %{"admin" => _}}}, _action, %Tag{}),
do: true
# Manage user roles
def can?(%User{role: "moderator", role_map: %{"Role" => "admin"}}, _action, %Role{}), do: true
def can?(%User{role: "moderator", role_map: %{"Role" => %{"admin" => _}}}, _action, %Role{}),
do: true
# Manage users
def can?(%User{role: "moderator", role_map: %{"User" => "moderator"}}, _action, User), do: true
def can?(%User{role: "moderator", role_map: %{"User" => %{"moderator" => _}}}, _action, User),
do: true
def can?(%User{role: "moderator", role_map: %{"User" => "moderator"}}, _action, %User{}),
def can?(%User{role: "moderator", role_map: %{"User" => %{"moderator" => _}}}, _action, %User{}),
do: true
# Manage advertisements
def can?(%User{role: "moderator", role_map: %{"Advert" => "admin"}}, _action, Advert), do: true
def can?(%User{role: "moderator", role_map: %{"Advert" => %{"admin" => _}}}, _action, Advert),
do: true
def can?(%User{role: "moderator", role_map: %{"Advert" => "admin"}}, _action, %Advert{}),
def can?(%User{role: "moderator", role_map: %{"Advert" => %{"admin" => _}}}, _action, %Advert{}),
do: true
# Manage static pages
def can?(%User{role: "moderator", role_map: %{"StaticPage" => "admin"}}, _action, StaticPage),
do: true
def can?(
%User{role: "moderator", role_map: %{"StaticPage" => %{"admin" => _}}},
_action,
StaticPage
),
do: true
def can?(%User{role: "moderator", role_map: %{"StaticPage" => "admin"}}, _action, %StaticPage{}),
do: true
def can?(
%User{role: "moderator", role_map: %{"StaticPage" => %{"admin" => _}}},
_action,
%StaticPage{}
),
do: true
#
# Assistants can...
#
# Image assistant actions
def can?(%User{role: "assistant", role_map: %{"Image" => "moderator"}}, :show, %Image{}),
def can?(%User{role: "assistant", role_map: %{"Image" => %{"moderator" => _}}}, :show, %Image{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Image" => "moderator"}}, :hide, %Image{}),
def can?(%User{role: "assistant", role_map: %{"Image" => %{"moderator" => _}}}, :hide, %Image{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Image" => "moderator"}}, :edit, %Image{}),
def can?(%User{role: "assistant", role_map: %{"Image" => %{"moderator" => _}}}, :edit, %Image{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Image" => "moderator"}},
%User{role: "assistant", role_map: %{"Image" => %{"moderator" => _}}},
:edit_metadata,
%Image{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"Image" => "moderator"}},
%User{role: "assistant", role_map: %{"Image" => %{"moderator" => _}}},
:edit_description,
%Image{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"Image" => "moderator"}},
%User{role: "assistant", role_map: %{"Image" => %{"moderator" => _}}},
:approve,
%Image{}
),
@ -217,112 +242,137 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# Dupe assistant actions
def can?(
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
%User{role: "assistant", role_map: %{"DuplicateReport" => %{"moderator" => _}}},
:index,
DuplicateReport
),
do: true
def can?(
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
%User{role: "assistant", role_map: %{"DuplicateReport" => %{"moderator" => _}}},
:edit,
%DuplicateReport{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
%User{role: "assistant", role_map: %{"DuplicateReport" => %{"moderator" => _}}},
:show,
%Image{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
%User{role: "assistant", role_map: %{"DuplicateReport" => %{"moderator" => _}}},
:edit,
%Image{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
%User{role: "assistant", role_map: %{"DuplicateReport" => %{"moderator" => _}}},
:hide,
%Comment{}
),
do: true
# Comment assistant actions
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :show, %Comment{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Comment" => %{"moderator" => _}}},
:show,
%Comment{}
),
do: true
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :edit, %Comment{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Comment" => %{"moderator" => _}}},
:edit,
%Comment{}
),
do: true
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :hide, %Comment{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Comment" => %{"moderator" => _}}},
:hide,
%Comment{}
),
do: true
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :approve, %Comment{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Comment" => %{"moderator" => _}}},
:approve,
%Comment{}
),
do: true
# Topic assistant actions
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Topic{}),
def can?(%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}}, :show, %Topic{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :edit, %Topic{}),
def can?(%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}}, :edit, %Topic{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Topic{}),
def can?(%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}}, :hide, %Topic{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Post{}),
def can?(%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}}, :show, %Post{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :edit, %Post{}),
def can?(%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}}, :edit, %Post{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Post{}),
def can?(%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}}, :hide, %Post{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :approve, %Post{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Topic" => %{"moderator" => _}}},
:approve,
%Post{}
),
do: true
# Tag assistant actions
def can?(%User{role: "assistant", role_map: %{"Tag" => "moderator"}}, :edit, %Tag{}), do: true
def can?(%User{role: "assistant", role_map: %{"Tag" => "moderator"}}, :batch_update, Tag),
def can?(%User{role: "assistant", role_map: %{"Tag" => %{"moderator" => _}}}, :edit, %Tag{}),
do: true
def can?(
%User{role: "assistant", role_map: %{"Tag" => %{"moderator" => _}}},
:batch_update,
Tag
),
do: true
# Artist link assistant actions
def can?(
%User{role: "assistant", role_map: %{"ArtistLink" => "moderator"}},
%User{role: "assistant", role_map: %{"ArtistLink" => %{"moderator" => _}}},
_action,
%ArtistLink{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"ArtistLink" => "moderator"}},
%User{role: "assistant", role_map: %{"ArtistLink" => %{"moderator" => _}}},
:create_links,
%User{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"ArtistLink" => "moderator"}},
%User{role: "assistant", role_map: %{"ArtistLink" => %{"moderator" => _}}},
:edit,
%ArtistLink{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"ArtistLink" => "moderator"}},
%User{role: "assistant", role_map: %{"ArtistLink" => %{"moderator" => _}}},
:edit_links,
%User{}
),
do: true
def can?(
%User{role: "assistant", role_map: %{"ArtistLink" => "moderator"}},
%User{role: "assistant", role_map: %{"ArtistLink" => %{"moderator" => _}}},
:index,
%ArtistLink{}
),
@ -340,7 +390,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
#
# Batch tag
def can?(%User{role_map: %{"Tag" => "batch_update"}}, :batch_update, Tag), do: true
def can?(%User{role_map: %{"Tag" => %{"batch_update" => _}}}, :batch_update, Tag), do: true
# Edit their description and personal title
def can?(%User{id: id}, :edit_description, %User{id: id}), do: true
@ -386,6 +436,14 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
when action in [:show, :index],
do: true
def can?(
%User{role_map: %{"Image" => %{"duplicate_access" => _}}},
action,
%Image{hidden_from_users: true, duplicate_id: duplicate_id}
)
when action in [:show, :index] and not is_nil(duplicate_id),
do: true
def can?(_user, :show, %Tag{}), do: true
# Comment on images where that is allowed

View file

@ -39,10 +39,6 @@
.block
.block__header
span.block__header__title General user flags
ul
p
strong> Be careful when issuing these permissions to staff members!
| Staff members with relevant roles may have these permissions anyway. Issuing them may break things, such as tag aliasing.
ul = collection_checkboxes f, :roles, filtered_roles(general_permissions(), @roles), mapper: &checkbox_mapper/6
.block

View file

@ -38,16 +38,18 @@
i.fa.fa-sitemap>
span.hide-limited-desktop.hide-mobile Related
.stretched-mobile-links
a href="#{pretty_url(@image, false, false)}" rel="nofollow" title="View (tags in filename)"
- can_show = can?(@conn, :show, @image)
a href="#{pretty_url(@image, can_show, false, false)}" rel="nofollow" title="View (tags in filename)"
i.fa.fa-eye>
| View
a href="#{pretty_url(@image, true, false)}" rel="nofollow" title="View (no tags in filename)"
a href="#{pretty_url(@image, can_show, true, false)}" rel="nofollow" title="View (no tags in filename)"
i.fa.fa-eye>
| VS
a href="#{pretty_url(@image, false, true)}" rel="nofollow" title="Download (tags in filename)"
a href="#{pretty_url(@image, can_show, false, true)}" rel="nofollow" title="Download (tags in filename)"
i.fa.fa-download>
| Download
a href="#{pretty_url(@image, true, true)}" title="Download (no tags in filename)"
a href="#{pretty_url(@image, can_show, true, true)}" title="Download (no tags in filename)"
i.fa.fa-download>
| DS
.image-metabar.flex.flex--wrap.block__header--user-credit.center--layout#extrameta

View file

@ -12,7 +12,7 @@
' .
= if size == :full and not embed_display do
.image-target.hidden.image-show data-scaled=scaled_value(@conn.assigns.current_user) data-uris=Jason.encode!(thumb_urls(@image, can?(@conn, :hide, @image))) data-width=@image.image_width data-height=@image.image_height data-image-size=@image.image_size data-mime-type=@image.image_mime_type
.image-target.hidden.image-show data-scaled=scaled_value(@conn.assigns.current_user) data-uris=Jason.encode!(thumb_urls(@image, can?(@conn, :show, @image))) data-width=@image.image_width data-height=@image.image_height data-image-size=@image.image_size data-mime-type=@image.image_mime_type
= if @image.image_mime_type == "video/webm" do
video controls=true
- else

View file

@ -32,7 +32,7 @@
= link "rules of the site", to: "/pages/rules"
' . Other useful links can be found at the bottom of the page.
= if can?(@conn, :hide, @image) do
= if can?(@conn, :show, @image) do
= render PhilomenaWeb.ImageView, "show.html", assigns
- else
p

View file

@ -19,7 +19,7 @@
- @conn.assigns.current_ban ->
= render PhilomenaWeb.BanView, "_ban_reason.html", conn: @conn
- @image.commenting_allowed ->
- can?(@conn, :create_comment, @image) ->
= render PhilomenaWeb.Image.CommentView, "_form.html", image: @image, changeset: @comment_changeset, remote: true, conn: @conn
- true ->

View file

@ -58,6 +58,8 @@ defmodule PhilomenaWeb.Admin.UserView do
def description("admin", "StaticPage"), do: "Manage static pages"
def description("admin", "Image"), do: "Hard-delete images"
def description("duplicate_access", "Image"), do: "View duplicate images"
def description(_name, _resource_type), do: "(unknown permission)"
def filtered_roles(permission_set, roles) do
@ -68,6 +70,7 @@ defmodule PhilomenaWeb.Admin.UserView do
def general_permissions do
[
["duplicate_access", "Image"],
["batch_update", "Tag"]
]
end

View file

@ -17,81 +17,67 @@ defmodule PhilomenaWeb.Api.Json.ImageView do
}
end
def render("image.json", %{
image: %{hidden_from_users: true, duplicate_id: duplicate_id} = image
})
when not is_nil(duplicate_id) do
%{
id: image.id,
created_at: image.created_at,
updated_at: image.updated_at,
first_seen_at: image.first_seen_at,
duplicate_of: image.duplicate_id,
deletion_reason: nil,
hidden_from_users: true
}
end
def render("image.json", %{image: image} = assigns) do
user =
case assigns do
%{conn: %{assigns: %{current_user: current_user}}} -> current_user
_ -> nil
end
def render("image.json", %{image: %{hidden_from_users: true} = image}) do
%{
id: image.id,
created_at: image.created_at,
updated_at: image.updated_at,
first_seen_at: image.first_seen_at,
deletion_reason: image.deletion_reason,
duplicate_of: nil,
hidden_from_users: true
}
end
def render("image.json", %{conn: conn, image: %{hidden_from_users: false} = image}) do
result = render_one(image, PhilomenaWeb.Api.Json.ImageView, "image.json", %{image: image})
Map.put(result, :spoilered, ImageView.filter_or_spoiler_hits?(conn, image))
end
def render("image.json", %{image: %{hidden_from_users: false} = image}) do
%{
id: image.id,
created_at: image.created_at,
updated_at: image.updated_at,
first_seen_at: image.first_seen_at,
width: image.image_width,
height: image.image_height,
mime_type: image.image_mime_type,
size: image.image_size,
orig_size: image.image_orig_size,
duration: image.image_duration,
animated: image.image_is_animated,
format: image.image_format,
aspect_ratio: image.image_aspect_ratio,
name: image.image_name,
sha512_hash: image.image_sha512_hash,
orig_sha512_hash: image.image_orig_sha512_hash,
tags: Enum.map(image.tags, & &1.name),
tag_ids: Enum.map(image.tags, & &1.id),
uploader: if(!!image.user and !image.anonymous, do: image.user.name),
uploader_id: if(!!image.user and !image.anonymous, do: image.user.id),
wilson_score: Philomena.Images.SearchIndex.wilson_score(image),
intensities: intensities(image),
score: image.score,
upvotes: image.upvotes_count,
downvotes: image.downvotes_count,
faves: image.faves_count,
comment_count: image.comments_count,
tag_count: length(image.tags),
description: image.description,
source_url:
if(Enum.count(image.sources) > 0, do: Enum.at(image.sources, 0).source, else: ""),
source_urls: Enum.map(image.sources, & &1.source),
view_url: ImageView.pretty_url(image, false, false),
representations: ImageView.thumb_urls(image, false),
thumbnails_generated: image.thumbnails_generated,
processed: image.processed,
deletion_reason: nil,
duplicate_of: nil,
hidden_from_users: false
}
if Canada.Can.can?(user, :show, image) do
%{
id: image.id,
created_at: image.created_at,
updated_at: image.updated_at,
first_seen_at: image.first_seen_at,
width: image.image_width,
height: image.image_height,
mime_type: image.image_mime_type,
size: image.image_size,
orig_size: image.image_orig_size,
duration: image.image_duration,
animated: image.image_is_animated,
format: image.image_format,
aspect_ratio: image.image_aspect_ratio,
name: image.image_name,
sha512_hash: image.image_sha512_hash,
orig_sha512_hash: image.image_orig_sha512_hash,
tags: Enum.map(image.tags, & &1.name),
tag_ids: Enum.map(image.tags, & &1.id),
uploader: if(!!image.user and !image.anonymous, do: image.user.name),
uploader_id: if(!!image.user and !image.anonymous, do: image.user.id),
wilson_score: Philomena.Images.SearchIndex.wilson_score(image),
intensities: intensities(image),
score: image.score,
upvotes: image.upvotes_count,
downvotes: image.downvotes_count,
faves: image.faves_count,
comment_count: image.comments_count,
tag_count: length(image.tags),
description: image.description,
source_url:
if(Enum.count(image.sources) > 0, do: Enum.at(image.sources, 0).source, else: ""),
source_urls: Enum.map(image.sources, & &1.source),
view_url: ImageView.pretty_url(image, true, false, false),
representations: ImageView.thumb_urls(image, true),
thumbnails_generated: image.thumbnails_generated,
processed: image.processed,
deletion_reason: image.deletion_reason,
duplicate_of: image.duplicate_id,
hidden_from_users: image.hidden_from_users,
spoilered: spoilered(assigns, image)
}
else
%{
id: image.id,
created_at: image.created_at,
updated_at: image.updated_at,
first_seen_at: image.first_seen_at,
deletion_reason: image.deletion_reason,
duplicate_of: image.duplicate_id,
hidden_from_users: image.hidden_from_users
}
end
end
def render("error.json", %{changeset: changeset}) do
@ -104,4 +90,9 @@ defmodule PhilomenaWeb.Api.Json.ImageView do
do: %{nw: nw, ne: ne, sw: sw, se: se}
defp intensities(_), do: nil
defp spoilered(%{conn: conn}, image),
do: ImageView.filter_or_spoiler_hits?(conn, image)
defp spoilered(_assigns, _image), do: false
end

View file

@ -70,14 +70,8 @@ defmodule PhilomenaWeb.ImageView do
|> Map.get(version_name, :full)
end
defp append_full_url(urls, %{hidden_from_users: false} = image, _show_hidden),
do: Map.put(urls, :full, pretty_url(image, true, false))
defp append_full_url(urls, %{hidden_from_users: true} = image, true),
do: Map.put(urls, :full, thumb_url(image, true, :full))
defp append_full_url(urls, _image, _show_hidden),
do: urls
defp append_full_url(urls, image, show_hidden),
do: Map.put(urls, :full, pretty_url(image, show_hidden, true, false))
defp append_gif_urls(urls, %{image_mime_type: "image/gif"} = image, show_hidden) do
full_url = thumb_url(image, show_hidden, :full)
@ -114,7 +108,10 @@ defmodule PhilomenaWeb.ImageView do
"#{root}/#{year}/#{month}/#{day}/#{id_fragment}/#{name}.#{format}"
end
def pretty_url(image, short, download) do
def pretty_url(%{hidden_from_users: true} = image, true, _short, _download),
do: thumb_url(image, true, :full)
def pretty_url(image, _show_hidden, short, download) do
%{year: year, month: month, day: day} = image.created_at
root = image_url_root()

View file

@ -90,7 +90,8 @@
{"name": "moderator", "resource_type": "Topic"},
{"name": "admin", "resource_type": "Advert"},
{"name": "admin", "resource_type": "StaticPage"},
{"name": "admin", "resource_type": "Image"}
{"name": "admin", "resource_type": "Image"},
{"name": "duplicate_access", "resource_type": "Image"}
],
"pages": []
}