Merge branch 'duponin/remove-ssh' into 'develop'
Remove SSH/BBS feature from core Closes #932, #2389, and #2931 See merge request pleroma/pleroma!3872
This commit is contained in:
commit
d97425d49e
10 changed files with 6 additions and 422 deletions
1
changelog.d/3872.remove
Normal file
1
changelog.d/3872.remove
Normal file
|
@ -0,0 +1 @@
|
|||
remove BBS/SSH feature, replaced by an external bridge.
|
|
@ -617,9 +617,6 @@
|
|||
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
|
||||
uid: System.get_env("LDAP_UID") || "cn"
|
||||
|
||||
config :esshd,
|
||||
enabled: false
|
||||
|
||||
oauth_consumer_strategies =
|
||||
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|
||||
|> to_string()
|
||||
|
|
|
@ -2628,45 +2628,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :esshd,
|
||||
label: "ESSHD",
|
||||
type: :group,
|
||||
description:
|
||||
"Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <>
|
||||
"and generate host keys in your priv dir with ssh-keygen -m PEM -N \"\" -b 2048 -t rsa -f ssh_host_rsa_key",
|
||||
children: [
|
||||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "Enables SSH"
|
||||
},
|
||||
%{
|
||||
key: :priv_dir,
|
||||
type: :string,
|
||||
description: "Dir with SSH keys",
|
||||
suggestions: ["/some/path/ssh_keys"]
|
||||
},
|
||||
%{
|
||||
key: :handler,
|
||||
type: :string,
|
||||
description: "Handler module",
|
||||
suggestions: ["Pleroma.BBS.Handler"]
|
||||
},
|
||||
%{
|
||||
key: :port,
|
||||
type: :integer,
|
||||
description: "Port to connect",
|
||||
suggestions: [10_022]
|
||||
},
|
||||
%{
|
||||
key: :password_authenticator,
|
||||
type: :string,
|
||||
description: "Authenticator module",
|
||||
suggestions: ["Pleroma.BBS.Authenticator"]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :mime,
|
||||
label: "Mime Types",
|
||||
|
|
|
@ -873,21 +873,8 @@ This will probably take a long time.
|
|||
|
||||
### BBS / SSH access
|
||||
|
||||
To enable simple command line interface accessible over ssh, add a setting like this to your configuration file:
|
||||
|
||||
```exs
|
||||
app_dir = File.cwd!
|
||||
priv_dir = Path.join([app_dir, "priv/ssh_keys"])
|
||||
|
||||
config :esshd,
|
||||
enabled: true,
|
||||
priv_dir: priv_dir,
|
||||
handler: "Pleroma.BBS.Handler",
|
||||
port: 10_022,
|
||||
password_authenticator: "Pleroma.BBS.Authenticator"
|
||||
```
|
||||
|
||||
Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`
|
||||
This feature has been removed from Pleroma core.
|
||||
However, a client has been made and is available at https://git.pleroma.social/Duponin/sshocial.
|
||||
|
||||
### :gopher
|
||||
* `enabled`: Enables the gopher interface
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.BBS.Authenticator do
|
||||
use Sshd.PasswordAuthenticator
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
||||
|
||||
def authenticate(username, password) do
|
||||
username = to_string(username)
|
||||
password = to_string(password)
|
||||
|
||||
with %User{} = user <- User.get_by_nickname(username) do
|
||||
AuthenticationPlug.checkpw(password, user.password_hash)
|
||||
else
|
||||
_e -> false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,246 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.BBS.Handler do
|
||||
use Sshd.ShellHandler
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
def on_shell(username, _pubkey, _ip, _port) do
|
||||
:ok = IO.puts("Welcome to #{Pleroma.Config.get([:instance, :name])}!")
|
||||
user = Pleroma.User.get_cached_by_nickname(to_string(username))
|
||||
Logger.debug("#{inspect(user)}")
|
||||
loop(run_state(user: user))
|
||||
end
|
||||
|
||||
def on_connect(username, ip, port, method) do
|
||||
Logger.debug(fn ->
|
||||
"""
|
||||
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}
|
||||
"""
|
||||
end)
|
||||
end
|
||||
|
||||
def on_disconnect(username, ip, port) do
|
||||
Logger.debug(fn ->
|
||||
"Disconnecting SSH shell for #{username} from #{inspect(ip)}:#{inspect(port)}"
|
||||
end)
|
||||
end
|
||||
|
||||
defp loop(state) do
|
||||
self_pid = self()
|
||||
counter = state.counter
|
||||
prefix = state.prefix
|
||||
user = state.user
|
||||
|
||||
input = spawn(fn -> io_get(self_pid, prefix, counter, user.nickname) end)
|
||||
wait_input(state, input)
|
||||
end
|
||||
|
||||
def puts_activity(activity) do
|
||||
status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity})
|
||||
|
||||
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
|
||||
|
||||
status.content
|
||||
|> String.split("<br/>")
|
||||
|> Enum.map(&HTML.strip_tags/1)
|
||||
|> Enum.map(&HtmlEntities.decode/1)
|
||||
|> Enum.map(&IO.puts/1)
|
||||
end
|
||||
|
||||
def puts_notification(activity, user) do
|
||||
notification =
|
||||
Pleroma.Web.MastodonAPI.NotificationView.render("show.json", %{
|
||||
notification: activity,
|
||||
for: user
|
||||
})
|
||||
|
||||
IO.puts(
|
||||
"== (#{notification.type}) #{notification.status.id} by #{notification.account.display_name} (#{notification.account.acct})"
|
||||
)
|
||||
|
||||
notification.status.content
|
||||
|> String.split("<br/>")
|
||||
|> Enum.map(&HTML.strip_tags/1)
|
||||
|> Enum.map(&HtmlEntities.decode/1)
|
||||
|> (fn x ->
|
||||
case x do
|
||||
[content] ->
|
||||
"> " <> content
|
||||
|
||||
[head | _tail] ->
|
||||
# "> " <> hd <> "..."
|
||||
head
|
||||
|> String.slice(1, 80)
|
||||
|> (fn x -> "> " <> x <> "..." end).()
|
||||
end
|
||||
end).()
|
||||
|> IO.puts()
|
||||
|
||||
IO.puts("")
|
||||
end
|
||||
|
||||
def handle_command(state, "help") do
|
||||
IO.puts("Available commands:")
|
||||
IO.puts("help - This help")
|
||||
IO.puts("home - Show the home timeline")
|
||||
IO.puts("p <text> - Post the given text")
|
||||
IO.puts("r <id> <text> - Reply to the post with the given id")
|
||||
IO.puts("t <id> - Show a thread from the given id")
|
||||
IO.puts("n - Show notifications")
|
||||
IO.puts("n read - Mark all notifactions as read")
|
||||
IO.puts("f <id> - Favourites the post with the given id")
|
||||
IO.puts("R <id> - Repeat the post with the given id")
|
||||
IO.puts("quit - Quit")
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "r " <> text) do
|
||||
text = String.trim(text)
|
||||
[activity_id, rest] = String.split(text, " ", parts: 2)
|
||||
|
||||
with %Activity{} <- Activity.get_by_id(activity_id),
|
||||
{:ok, _activity} <-
|
||||
CommonAPI.post(user, %{status: rest, in_reply_to_status_id: activity_id}) do
|
||||
IO.puts("Replied!")
|
||||
else
|
||||
_e -> IO.puts("Could not reply...")
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "t " <> activity_id) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
|
||||
activities =
|
||||
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||
blocking_user: user,
|
||||
user: user,
|
||||
exclude_id: activity.id
|
||||
})
|
||||
|
||||
case activities do
|
||||
[] ->
|
||||
activity_id
|
||||
|> Activity.get_by_id()
|
||||
|> puts_activity()
|
||||
|
||||
_ ->
|
||||
activities
|
||||
|> Enum.reverse()
|
||||
|> Enum.each(&puts_activity/1)
|
||||
end
|
||||
else
|
||||
_e -> IO.puts("Could not show this thread...")
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "n read") do
|
||||
Pleroma.Notification.clear(user)
|
||||
IO.puts("All notifications were marked as read")
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "n") do
|
||||
user
|
||||
|> Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(%{})
|
||||
|> Enum.each(&puts_notification(&1, user))
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "p " <> text) do
|
||||
text = String.trim(text)
|
||||
|
||||
with {:ok, activity} <- CommonAPI.post(user, %{status: text}) do
|
||||
IO.puts("Posted! ID: #{activity.id}")
|
||||
else
|
||||
_e -> IO.puts("Could not post...")
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "f " <> id) do
|
||||
id = String.trim(id)
|
||||
|
||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||
{:ok, _activity} <- CommonAPI.favorite(user, activity) do
|
||||
IO.puts("Favourited!")
|
||||
else
|
||||
_e -> IO.puts("Could not Favourite...")
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(state, "home") do
|
||||
user = state.user
|
||||
|
||||
params =
|
||||
%{}
|
||||
|> Map.put(:type, ["Create"])
|
||||
|> Map.put(:blocking_user, user)
|
||||
|> Map.put(:muting_user, user)
|
||||
|> Map.put(:user, user)
|
||||
|
||||
activities =
|
||||
[user.ap_id | Pleroma.User.following(user)]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|
||||
Enum.each(activities, fn activity ->
|
||||
puts_activity(activity)
|
||||
end)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(state, command) do
|
||||
IO.puts("Unknown command '#{command}'")
|
||||
state
|
||||
end
|
||||
|
||||
defp wait_input(state, input) do
|
||||
receive do
|
||||
{:input, ^input, "quit\n"} ->
|
||||
IO.puts("Exiting...")
|
||||
|
||||
{:input, ^input, code} when is_binary(code) ->
|
||||
code = String.trim(code)
|
||||
|
||||
state = handle_command(state, code)
|
||||
|
||||
loop(%{state | counter: state.counter + 1})
|
||||
|
||||
{:input, ^input, {:error, :interrupted}} ->
|
||||
IO.puts("Caught Ctrl+C...")
|
||||
loop(%{state | counter: state.counter + 1})
|
||||
|
||||
{:input, ^input, msg} ->
|
||||
:ok = Logger.warn("received unknown message: #{inspect(msg)}")
|
||||
loop(%{state | counter: state.counter + 1})
|
||||
end
|
||||
end
|
||||
|
||||
defp run_state(opts) do
|
||||
%{prefix: "pleroma", counter: 1, user: opts[:user]}
|
||||
end
|
||||
|
||||
defp io_get(pid, prefix, counter, username) do
|
||||
prompt = prompt(prefix, counter, username)
|
||||
send(pid, {:input, self(), IO.gets(:stdio, prompt)})
|
||||
end
|
||||
|
||||
defp prompt(prefix, counter, username) do
|
||||
prompt = "#{username}@#{prefix}:#{counter}>"
|
||||
prompt <> " "
|
||||
end
|
||||
end
|
4
mix.exs
4
mix.exs
|
@ -78,8 +78,7 @@ def application do
|
|||
:comeonin,
|
||||
:fast_sanitize,
|
||||
:os_mon,
|
||||
:ssl,
|
||||
:esshd
|
||||
:ssl
|
||||
],
|
||||
included_applications: [:ex_syslogger]
|
||||
]
|
||||
|
@ -181,7 +180,6 @@ defp deps do
|
|||
{:joken, "~> 2.0"},
|
||||
{:benchee, "~> 1.0"},
|
||||
{:pot, "~> 1.0"},
|
||||
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
|
||||
{:ex_const, "~> 0.2"},
|
||||
{:plug_static_index_html, "~> 1.0.0"},
|
||||
{:flake_id, "~> 0.1.0"},
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -35,7 +35,6 @@
|
|||
"eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
|
||||
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
|
||||
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
|
||||
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"},
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.BBS.HandlerTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.BBS.Handler
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
import Pleroma.Factory
|
||||
import Ecto.Query
|
||||
|
||||
test "getting the home timeline" do
|
||||
user = insert(:user)
|
||||
followed = insert(:user)
|
||||
|
||||
{:ok, user, followed} = User.follow(user, followed)
|
||||
|
||||
{:ok, _first} = CommonAPI.post(user, %{status: "hey"})
|
||||
{:ok, _second} = CommonAPI.post(followed, %{status: "hello"})
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Handler.handle_command(%{user: user}, "home")
|
||||
end)
|
||||
|
||||
assert output =~ user.nickname
|
||||
assert output =~ followed.nickname
|
||||
|
||||
assert output =~ "hey"
|
||||
assert output =~ "hello"
|
||||
end
|
||||
|
||||
test "posting" do
|
||||
user = insert(:user)
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Handler.handle_command(%{user: user}, "p this is a test post")
|
||||
end)
|
||||
|
||||
assert output =~ "Posted"
|
||||
|
||||
activity =
|
||||
Repo.one(
|
||||
from(a in Activity,
|
||||
where: fragment("?->>'type' = ?", a.data, "Create")
|
||||
)
|
||||
)
|
||||
|
||||
assert activity.actor == user.ap_id
|
||||
object = Object.normalize(activity, fetch: false)
|
||||
assert object.data["content"] == "this is a test post"
|
||||
end
|
||||
|
||||
test "replying" do
|
||||
user = insert(:user)
|
||||
another_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(another_user, %{status: "this is a test post"})
|
||||
activity_object = Object.normalize(activity, fetch: false)
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Handler.handle_command(%{user: user}, "r #{activity.id} this is a reply")
|
||||
end)
|
||||
|
||||
assert output =~ "Replied"
|
||||
|
||||
reply =
|
||||
Repo.one(
|
||||
from(a in Activity,
|
||||
where: fragment("?->>'type' = ?", a.data, "Create"),
|
||||
where: a.actor == ^user.ap_id
|
||||
)
|
||||
)
|
||||
|
||||
assert reply.actor == user.ap_id
|
||||
|
||||
reply_object_data = Object.normalize(reply, fetch: false).data
|
||||
assert reply_object_data["content"] == "this is a reply"
|
||||
assert reply_object_data["inReplyTo"] == activity_object.data["id"]
|
||||
end
|
||||
end
|
|
@ -1502,15 +1502,14 @@ test "filters by database configuration whitelist", %{conn: conn} do
|
|||
clear_config(:database_config_whitelist, [
|
||||
{:pleroma, :instance},
|
||||
{:pleroma, :activitypub},
|
||||
{:pleroma, Pleroma.Upload},
|
||||
{:esshd}
|
||||
{:pleroma, Pleroma.Upload}
|
||||
])
|
||||
|
||||
conn = get(conn, "/api/pleroma/admin/config/descriptions")
|
||||
|
||||
children = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
assert length(children) == 4
|
||||
assert length(children) == 3
|
||||
|
||||
assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
|
||||
|
||||
|
@ -1522,9 +1521,6 @@ test "filters by database configuration whitelist", %{conn: conn} do
|
|||
|
||||
web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
|
||||
assert web_endpoint["children"]
|
||||
|
||||
esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
|
||||
assert esshd["children"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue