forked from AkkomaGang/akkoma
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms
This commit is contained in:
commit
064c4f86f3
10 changed files with 268 additions and 104 deletions
|
@ -141,6 +141,12 @@ def following_query(%User{} = user) do
|
||||||
|> where([r], r.state == ^:follow_accept)
|
|> where([r], r.state == ^:follow_accept)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def outgoing_pending_follow_requests_query(%User{} = follower) do
|
||||||
|
__MODULE__
|
||||||
|
|> where([r], r.follower_id == ^follower.id)
|
||||||
|
|> where([r], r.state == ^:follow_pending)
|
||||||
|
end
|
||||||
|
|
||||||
def following(%User{} = user) do
|
def following(%User{} = user) do
|
||||||
following =
|
following =
|
||||||
following_query(user)
|
following_query(user)
|
||||||
|
|
|
@ -1489,6 +1489,8 @@ def perform(:delete, %User{} = user) do
|
||||||
|
|
||||||
delete_user_activities(user)
|
delete_user_activities(user)
|
||||||
|
|
||||||
|
delete_outgoing_pending_follow_requests(user)
|
||||||
|
|
||||||
delete_or_deactivate(user)
|
delete_or_deactivate(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1611,6 +1613,12 @@ defp delete_activity(%{data: %{"type" => type}} = activity, user)
|
||||||
|
|
||||||
defp delete_activity(_activity, _user), do: "Doing nothing"
|
defp delete_activity(_activity, _user), do: "Doing nothing"
|
||||||
|
|
||||||
|
defp delete_outgoing_pending_follow_requests(user) do
|
||||||
|
user
|
||||||
|
|> FollowingRelationship.outgoing_pending_follow_requests_query()
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
def html_filter_policy(%User{no_rich_text: true}) do
|
def html_filter_policy(%User{no_rich_text: true}) do
|
||||||
Pleroma.HTML.Scrubber.TwitterText
|
Pleroma.HTML.Scrubber.TwitterText
|
||||||
end
|
end
|
||||||
|
|
|
@ -222,9 +222,9 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|
||||||
|
|
||||||
media_type =
|
media_type =
|
||||||
cond do
|
cond do
|
||||||
is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
|
is_map(url) && MIME.valid?(url["mediaType"]) -> url["mediaType"]
|
||||||
is_binary(data["mediaType"]) -> data["mediaType"]
|
MIME.valid?(data["mediaType"]) -> data["mediaType"]
|
||||||
is_binary(data["mimeType"]) -> data["mimeType"]
|
MIME.valid?(data["mimeType"]) -> data["mimeType"]
|
||||||
true -> nil
|
true -> nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.Pipeline
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
|
||||||
alias Pleroma.Web.AdminAPI
|
alias Pleroma.Web.AdminAPI
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
alias Pleroma.Web.AdminAPI.ModerationLogView
|
alias Pleroma.Web.AdminAPI.ModerationLogView
|
||||||
|
@ -59,7 +58,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["write:follows"], admin: true}
|
%{scopes: ["write:follows"], admin: true}
|
||||||
when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
|
when action in [:user_follow, :user_unfollow]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -74,7 +73,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
when action in [
|
when action in [
|
||||||
:list_log,
|
:list_log,
|
||||||
:stats,
|
:stats,
|
||||||
:relay_list,
|
|
||||||
:need_reboot
|
:need_reboot
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -491,50 +489,6 @@ def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname"
|
||||||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||||
end
|
end
|
||||||
|
|
||||||
def relay_list(conn, _params) do
|
|
||||||
with {:ok, list} <- Relay.list() do
|
|
||||||
json(conn, %{relays: list})
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
|> put_status(500)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
|
|
||||||
with {:ok, _message} <- Relay.follow(target) do
|
|
||||||
ModerationLog.insert_log(%{
|
|
||||||
action: "relay_follow",
|
|
||||||
actor: admin,
|
|
||||||
target: target
|
|
||||||
})
|
|
||||||
|
|
||||||
json(conn, target)
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
|> put_status(500)
|
|
||||||
|> json(target)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
|
|
||||||
with {:ok, _message} <- Relay.unfollow(target) do
|
|
||||||
ModerationLog.insert_log(%{
|
|
||||||
action: "relay_unfollow",
|
|
||||||
actor: admin,
|
|
||||||
target: target
|
|
||||||
})
|
|
||||||
|
|
||||||
json(conn, target)
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
|> put_status(500)
|
|
||||||
|> json(target)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "Get a password reset token (base64 string) for given nickname"
|
@doc "Get a password reset token (base64 string) for given nickname"
|
||||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||||
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
|
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
|
||||||
|
|
67
lib/pleroma/web/admin_api/controllers/relay_controller.ex
Normal file
67
lib/pleroma/web/admin_api/controllers/relay_controller.ex
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.RelayController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.ModerationLog
|
||||||
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["write:follows"], admin: true}
|
||||||
|
when action in [:follow, :unfollow]
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :index)
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.RelayOperation
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
with {:ok, list} <- Relay.list() do
|
||||||
|
json(conn, %{relays: list})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
|
||||||
|
with {:ok, _message} <- Relay.follow(target) do
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
action: "relay_follow",
|
||||||
|
actor: admin,
|
||||||
|
target: target
|
||||||
|
})
|
||||||
|
|
||||||
|
json(conn, target)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(500)
|
||||||
|
|> json(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
|
||||||
|
with {:ok, _message} <- Relay.unfollow(target) do
|
||||||
|
ModerationLog.insert_log(%{
|
||||||
|
action: "relay_unfollow",
|
||||||
|
actor: admin,
|
||||||
|
target: target
|
||||||
|
})
|
||||||
|
|
||||||
|
json(conn, target)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_status(500)
|
||||||
|
|> json(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
83
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
Normal file
83
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Admin", "Relays"],
|
||||||
|
summary: "List Relays",
|
||||||
|
operationId: "AdminAPI.RelayController.index",
|
||||||
|
security: [%{"oAuth" => ["read"]}],
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Response", "application/json", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
relays: %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: %Schema{type: :string},
|
||||||
|
example: ["lain.com", "mstdn.io"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Admin", "Relays"],
|
||||||
|
summary: "Follow a Relay",
|
||||||
|
operationId: "AdminAPI.RelayController.follow",
|
||||||
|
security: [%{"oAuth" => ["write:follows"]}],
|
||||||
|
requestBody:
|
||||||
|
request_body("Parameters", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
relay_url: %Schema{type: :string, format: :uri}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Status", "application/json", %Schema{
|
||||||
|
type: :string,
|
||||||
|
example: "http://mastodon.example.org/users/admin"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Admin", "Relays"],
|
||||||
|
summary: "Unfollow a Relay",
|
||||||
|
operationId: "AdminAPI.RelayController.unfollow",
|
||||||
|
security: [%{"oAuth" => ["write:follows"]}],
|
||||||
|
requestBody:
|
||||||
|
request_body("Parameters", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
relay_url: %Schema{type: :string, format: :uri}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Status", "application/json", %Schema{
|
||||||
|
type: :string,
|
||||||
|
example: "http://mastodon.example.org/users/admin"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -160,9 +160,9 @@ defmodule Pleroma.Web.Router do
|
||||||
:right_delete_multiple
|
:right_delete_multiple
|
||||||
)
|
)
|
||||||
|
|
||||||
get("/relay", AdminAPIController, :relay_list)
|
get("/relay", RelayController, :index)
|
||||||
post("/relay", AdminAPIController, :relay_follow)
|
post("/relay", RelayController, :follow)
|
||||||
delete("/relay", AdminAPIController, :relay_unfollow)
|
delete("/relay", RelayController, :unfollow)
|
||||||
|
|
||||||
post("/users/invite_token", InviteController, :create)
|
post("/users/invite_token", InviteController, :create)
|
||||||
get("/users/invites", InviteController, :index)
|
get("/users/invites", InviteController, :index)
|
||||||
|
|
|
@ -1159,6 +1159,9 @@ test "it deactivates a user, all follow relationships and all activities", %{use
|
||||||
follower = insert(:user)
|
follower = insert(:user)
|
||||||
{:ok, follower} = User.follow(follower, user)
|
{:ok, follower} = User.follow(follower, user)
|
||||||
|
|
||||||
|
locked_user = insert(:user, name: "locked", locked: true)
|
||||||
|
{:ok, _} = User.follow(user, locked_user, :follow_pending)
|
||||||
|
|
||||||
object = insert(:note, user: user)
|
object = insert(:note, user: user)
|
||||||
activity = insert(:note_activity, user: user, note: object)
|
activity = insert(:note_activity, user: user, note: object)
|
||||||
|
|
||||||
|
@ -1177,6 +1180,8 @@ test "it deactivates a user, all follow relationships and all activities", %{use
|
||||||
refute User.following?(follower, user)
|
refute User.following?(follower, user)
|
||||||
assert %{deactivated: true} = User.get_by_id(user.id)
|
assert %{deactivated: true} = User.get_by_id(user.id)
|
||||||
|
|
||||||
|
assert [] == User.get_follow_requests(locked_user)
|
||||||
|
|
||||||
user_activities =
|
user_activities =
|
||||||
user.ap_id
|
user.ap_id
|
||||||
|> Activity.Queries.by_actor()
|
|> Activity.Queries.by_actor()
|
||||||
|
|
|
@ -1604,57 +1604,6 @@ test "sets password_reset_pending to true", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "relays" do
|
|
||||||
test "POST /relay", %{conn: conn, admin: admin} do
|
|
||||||
conn =
|
|
||||||
post(conn, "/api/pleroma/admin/relay", %{
|
|
||||||
relay_url: "http://mastodon.example.org/users/admin"
|
|
||||||
})
|
|
||||||
|
|
||||||
assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
|
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
|
||||||
|
|
||||||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
|
||||||
"@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "GET /relay", %{conn: conn} do
|
|
||||||
relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
|
|
||||||
|
|
||||||
["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
|
|
||||||
|> Enum.each(fn ap_id ->
|
|
||||||
{:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
|
|
||||||
User.follow(relay_user, user)
|
|
||||||
end)
|
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/relay")
|
|
||||||
|
|
||||||
assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
|
|
||||||
end
|
|
||||||
|
|
||||||
test "DELETE /relay", %{conn: conn, admin: admin} do
|
|
||||||
post(conn, "/api/pleroma/admin/relay", %{
|
|
||||||
relay_url: "http://mastodon.example.org/users/admin"
|
|
||||||
})
|
|
||||||
|
|
||||||
conn =
|
|
||||||
delete(conn, "/api/pleroma/admin/relay", %{
|
|
||||||
relay_url: "http://mastodon.example.org/users/admin"
|
|
||||||
})
|
|
||||||
|
|
||||||
assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
|
|
||||||
|
|
||||||
[log_entry_one, log_entry_two] = Repo.all(ModerationLog)
|
|
||||||
|
|
||||||
assert ModerationLog.get_log_entry_message(log_entry_one) ==
|
|
||||||
"@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
|
|
||||||
|
|
||||||
assert ModerationLog.get_log_entry_message(log_entry_two) ==
|
|
||||||
"@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "instances" do
|
describe "instances" do
|
||||||
test "GET /instances/:instance/statuses", %{conn: conn} do
|
test "GET /instances/:instance/statuses", %{conn: conn} do
|
||||||
user = insert(:user, local: false, nickname: "archaeme@archae.me")
|
user = insert(:user, local: false, nickname: "archaeme@archae.me")
|
||||||
|
|
92
test/web/admin_api/controllers/relay_controller_test.exs
Normal file
92
test/web/admin_api/controllers/relay_controller_test.exs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.AdminAPI.RelayControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.ModerationLog
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
admin = insert(:user, is_admin: true)
|
||||||
|
token = insert(:oauth_admin_token, user: admin)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, admin)
|
||||||
|
|> assign(:token, token)
|
||||||
|
|
||||||
|
{:ok, %{admin: admin, token: token, conn: conn}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "relays" do
|
||||||
|
test "POST /relay", %{conn: conn, admin: admin} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/pleroma/admin/relay", %{
|
||||||
|
relay_url: "http://mastodon.example.org/users/admin"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(conn, 200) ==
|
||||||
|
"http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
|
||||||
|
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||||
|
"@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /relay", %{conn: conn} do
|
||||||
|
relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
|
||||||
|
|
||||||
|
["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
|
||||||
|
|> Enum.each(fn ap_id ->
|
||||||
|
{:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
|
||||||
|
User.follow(relay_user, user)
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/pleroma/admin/relay")
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(conn, 200)["relays"] --
|
||||||
|
["mastodon.example.org", "mstdn.io"] == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "DELETE /relay", %{conn: conn, admin: admin} do
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/pleroma/admin/relay", %{
|
||||||
|
relay_url: "http://mastodon.example.org/users/admin"
|
||||||
|
})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> delete("/api/pleroma/admin/relay", %{
|
||||||
|
relay_url: "http://mastodon.example.org/users/admin"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(conn, 200) ==
|
||||||
|
"http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
[log_entry_one, log_entry_two] = Repo.all(ModerationLog)
|
||||||
|
|
||||||
|
assert ModerationLog.get_log_entry_message(log_entry_one) ==
|
||||||
|
"@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
assert ModerationLog.get_log_entry_message(log_entry_two) ==
|
||||||
|
"@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue