### Changed
- **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
+- **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
- In Conversations, return only direct messages as `last_status`
- Using the `only_media` filter on timelines will now exclude reblog media
- MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
- Configuration: `:media_proxy, whitelist` format changed to host with scheme (e.g. `http://example.com` instead of `example.com`). Domain format is deprecated.
+- **Breaking:** Configuration: `:instance, welcome_user_nickname` moved to `:welcome, :direct_message, :sender_nickname`, `:instance, :welcome_message` moved to `:welcome, :direct_message, :message`. Old config namespace is deprecated.
<details>
<summary>API Changes</summary>
- **Breaking:** Notification Settings API option for hiding push notification
contents has been renamed to `hide_notification_contents`
- Mastodon API: Added `pleroma.metadata.post_formats` to /api/v1/instance
+- Mastodon API (legacy): Allow query parameters for `/api/v1/domain_blocks`, e.g. `/api/v1/domain_blocks?domain=badposters.zone`
</details>
<details>
- Support pagination in emoji packs API (for packs and for files in pack)
- Support for viewing instances favicons next to posts and accounts
- Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
+- "By approval" registrations mode.
+- Configuration: Added `:welcome` settings for the welcome message to newly registered users.
<details>
<summary>API Changes</summary>
- Admin API: fix `GET /api/pleroma/admin/users/:nickname/credentials` returning 404 when getting the credentials of a remote user while `:instance, :limit_to_local_content` is set to `:unauthenticated`
- Fix CSP policy generation to include remote Captcha services
- Fix edge case where MediaProxy truncates media, usually caused when Caddy is serving content for the other Federated instance.
+- Emoji Packs could not be listed when instance was set to `public: false`
## [Unreleased (patch)]
registrations_open: true,
invites_enabled: false,
account_activation_required: false,
+ account_approval_required: false,
federating: true,
federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7,
autofollowed_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
- welcome_user_nickname: nil,
- welcome_message: nil,
max_report_comment_size: 1000,
safe_dm_mentions: false,
healthcheck: false,
max_remote_account_fields: 20,
account_field_name_length: 512,
account_field_value_length: 2048,
+ registration_reason_length: 500,
external_user_synchronization: true,
extended_nickname_format: true,
cleanup_attachments: false,
]
]
+config :pleroma, :welcome,
+ direct_message: [
+ enabled: false,
+ sender_nickname: nil,
+ message: nil
+ ],
+ email: [
+ enabled: false,
+ sender: nil,
+ subject: "Welcome to <%= instance_name %>",
+ html: "Welcome to <%= instance_name %>",
+ text: "Welcome to <%= instance_name %>"
+ ]
+
config :pleroma, :feed,
post_title: %{
max_length: 100,
federator_outgoing: 5
]
-config :auto_linker,
- opts: [
- extra: true,
- # TODO: Set to :no_scheme when it works properly
- validate_tld: true,
- class: false,
- strip_prefix: false,
- new_window: false,
- rel: "ugc"
- ]
+config :pleroma, Pleroma.Formatter,
+ class: false,
+ rel: "ugc",
+ new_window: false,
+ truncate: false,
+ strip_prefix: false,
+ extra: true,
+ validate_tld: :no_scheme
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
config :pleroma, :static_fe, enabled: false
+# Example of frontend configuration
+# This example will make us serve the primary frontend from the
+# frontends directory within your `:pleroma, :instance, static_dir`.
+# e.g., instance/static/frontends/pleroma/develop/
+#
+# With no frontend configuration, the bundled files from the `static` directory will
+# be used.
+#
+# config :pleroma, :frontends, primary: %{"name" => "pleroma", "ref" => "develop"}
+
config :pleroma, :web_cache_ttl,
activity_pub: nil,
activity_pub_question: 30_000
type: :boolean,
description: "Require users to confirm their emails before signing in"
},
+ %{
+ key: :account_approval_required,
+ type: :boolean,
+ description: "Require users to be manually approved by an admin before signing in"
+ },
%{
key: :federating,
type: :boolean,
type: :boolean,
description: "Enable to automatically add attachment link text to statuses"
},
- %{
- key: :welcome_message,
- type: :string,
- description:
- "A message that will be sent to a newly registered users as a direct message",
- suggestions: [
- "Hi, @username! Welcome on board!"
- ]
- },
- %{
- key: :welcome_user_nickname,
- type: :string,
- description: "The nickname of the local user that sends the welcome message",
- suggestions: [
- "lain"
- ]
- },
%{
key: :max_report_comment_size,
type: :integer,
2048
]
},
+ %{
+ key: :registration_reason_length,
+ type: :integer,
+ description: "Maximum registration reason length. Default: 500.",
+ suggestions: [
+ 500
+ ]
+ },
%{
key: :external_user_synchronization,
type: :boolean,
}
]
},
+ %{
+ group: :welcome,
+ type: :group,
+ description: "Welcome messages settings",
+ children: [
+ %{
+ group: :direct_message,
+ type: :group,
+ descpiption: "Direct message settings",
+ children: [
+ %{
+ key: :enabled,
+ type: :boolean,
+ description: "Enables sends direct message for new user after registration"
+ },
+ %{
+ key: :message,
+ type: :string,
+ description:
+ "A message that will be sent to a newly registered users as a direct message",
+ suggestions: [
+ "Hi, @username! Welcome on board!"
+ ]
+ },
+ %{
+ key: :sender_nickname,
+ type: :string,
+ description: "The nickname of the local user that sends the welcome message",
+ suggestions: [
+ "lain"
+ ]
+ }
+ ]
+ },
+ %{
+ group: :email,
+ type: :group,
+ descpiption: "Email message settings",
+ children: [
+ %{
+ key: :enabled,
+ type: :boolean,
+ description: "Enables sends direct message for new user after registration"
+ },
+ %{
+ key: :sender,
+ type: [:string, :tuple],
+ description:
+ "The email address or tuple with `{nickname, email}` that will use as sender to the welcome email.",
+ suggestions: [
+ {"Pleroma App", "welcome@pleroma.app"}
+ ]
+ },
+ %{
+ key: :subject,
+ type: :string,
+ description:
+ "The subject of welcome email. Can be use EEX template with `user` and `instance_name` variables.",
+ suggestions: ["Welcome to <%= instance_name%>"]
+ },
+ %{
+ key: :html,
+ type: :string,
+ description:
+ "The html content of welcome email. Can be use EEX template with `user` and `instance_name` variables.",
+ suggestions: ["<h1>Hello <%= user.name%>. Welcome to <%= instance_name%></h1>"]
+ },
+ %{
+ key: :text,
+ type: :string,
+ description:
+ "The text content of welcome email. Can be use EEX template with `user` and `instance_name` variables.",
+ suggestions: ["Hello <%= user.name%>. \n Welcome to <%= instance_name%>\n"]
+ }
+ ]
+ }
+ ]
+ },
%{
group: :logger,
type: :group,
group: :pleroma,
key: :mrf_simple,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
label: "MRF Simple",
type: :group,
description: "Simple ingress policies",
group: :pleroma,
key: :mrf_activity_expiration,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy",
label: "MRF Activity Expiration Policy",
type: :group,
description: "Adds automatic expiration to all local activities",
group: :pleroma,
key: :mrf_subchain,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
label: "MRF Subchain",
type: :group,
description:
group: :pleroma,
key: :mrf_rejectnonpublic,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic",
description: "RejectNonPublic drops posts with non-public visibility settings.",
label: "MRF Reject Non Public",
type: :group,
group: :pleroma,
key: :mrf_hellthread,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
label: "MRF Hellthread",
type: :group,
description: "Block messages with excessive user mentions",
group: :pleroma,
key: :mrf_keyword,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
label: "MRF Keyword",
type: :group,
description: "Reject or Word-Replace messages with a keyword or regex",
group: :pleroma,
key: :mrf_mention,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
label: "MRF Mention",
type: :group,
description: "Block messages which mention a specific user",
group: :pleroma,
key: :mrf_vocabulary,
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
label: "MRF Vocabulary",
type: :group,
description: "Filter messages which belong to certain activity vocabularies",
# %{
# group: :pleroma,
# key: :mrf_user_allowlist,
+ # tab: :mrf,
+ # related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy",
# type: :map,
# description:
# "The keys in this section are the domain names that the policy should apply to." <>
]
},
%{
- group: :auto_linker,
- key: :opts,
+ group: :pleroma,
+ key: Pleroma.Formatter,
label: "Auto Linker",
type: :group,
- description: "Configuration for the auto_linker library",
+ description:
+ "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.",
children: [
%{
key: :class,
- type: [:string, false],
+ type: [:string, :boolean],
description: "Specify the class to be added to the generated link. Disable to clear.",
suggestions: ["auto-linker", false]
},
%{
key: :rel,
- type: [:string, false],
+ type: [:string, :boolean],
description: "Override the rel attribute. Disable to clear.",
suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,
type: :boolean,
- description: "Link URLs will open in new window/tab"
+ description: "Link URLs will open in a new window/tab."
},
%{
key: :truncate,
- type: [:integer, false],
+ type: [:integer, :boolean],
description:
- "Set to a number to truncate URLs longer then the number. Truncated URLs will end in `..`",
+ "Set to a number to truncate URLs longer than the number. Truncated URLs will end in `...`",
suggestions: [15, false]
},
%{
key: :strip_prefix,
type: :boolean,
- description: "Strip the scheme prefix"
+ description: "Strip the scheme prefix."
},
%{
key: :extra,
type: :boolean,
description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
+ },
+ %{
+ key: :validate_tld,
+ type: [:atom, :boolean],
+ description:
+ "Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)",
+ suggestions: [:no_scheme, true]
}
]
},
},
%{
group: :pleroma,
- tab: :mrf,
key: :mrf_normalize_markup,
+ tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup",
label: "MRF Normalize Markup",
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
type: :group,
%{
group: :pleroma,
key: :mrf_object_age,
- label: "MRF Object Age",
tab: :mrf,
+ related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy",
+ label: "MRF Object Age",
type: :group,
description:
"Rejects or delists posts based on their timestamp deviance from your server's clock.",
suggestions: ["s3.eu-central-1.amazonaws.com"]
}
]
+ },
+ %{
+ group: :pleroma,
+ key: :frontends,
+ type: :group,
+ description: "Installed frontends management",
+ children: [
+ %{
+ key: :primary,
+ type: :map,
+ description: "Primary frontend, the one that is served for all pages by default",
+ children: [
+ %{
+ key: "name",
+ type: :string,
+ description: "Name of the installed primary frontend"
+ },
+ %{
+ key: "ref",
+ type: :string,
+ description: "reference of the installed primary frontend to be used"
+ }
+ ]
+ }
+ ]
}
]
streaming_enabled: true,
public_endpoint: nil
+config :tzdata, :autoupdate, :disabled
+
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else
- `local`: only local users
- `external`: only external users
- `active`: only active users
+ - `need_approval`: only unapproved users
- `deactivated`: only deactivated users
- `is_admin`: users with admin role
- `is_moderator`: users with moderator role
"local": bool,
"tags": array,
"avatar": string,
- "display_name": string
+ "display_name": string,
+ "confirmation_pending": bool,
+ "approval_pending": bool,
+ "registration_reason": string,
},
...
]
}
```
+## `PATCH /api/pleroma/admin/users/approve`
+
+### Approve user
+
+- Params:
+ - `nicknames`: nicknames array
+- Response:
+
+```json
+{
+ users: [
+ {
+ // user object
+ }
+ ]
+}
+```
+
## `GET /api/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in.
+* `account_approval_required`: Require users to be manually approved by an admin before signing in.
* `federating`: Enable federation with other instances.
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
* `attachment_links`: Set to true to enable automatically adding attachment link text to statuses.
-* `welcome_message`: A message that will be send to a newly registered users as a direct message.
-* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`).
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
* `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`).
* `account_field_name_length`: An account field name maximum length (default: `512`).
* `account_field_value_length`: An account field value maximum length (default: `2048`).
+* `registration_reason_length`: Maximum registration reason length (default: `500`).
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
+## Welcome
+* `direct_message`: - welcome message sent as a direct message.
+ * `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`.
+ * `sender_nickname`: The nickname of the local user that sends the welcome message.
+ * `message`: A message that will be send to a newly registered users as a direct message.
+* `email`: - welcome message sent as a email.
+ * `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`.
+ * `sender`: The email address or tuple with `{nickname, email}` that will use as sender to the welcome email.
+ * `subject`: A subject of welcome email.
+ * `html`: A html that will be send to a newly registered users as a email.
+ * `text`: A text that will be send to a newly registered users as a email.
+
+ Example:
+
+ ```elixir
+ config :pleroma, :welcome,
+ direct_message: [
+ enabled: true,
+ sender_nickname: "lain",
+ message: "Hi, @username! Welcome on board!"
+ ],
+ email: [
+ enabled: true,
+ sender: {"Pleroma App", "welcome@pleroma.app"},
+ subject: "Welcome to <%= instance_name %>",
+ html: "Welcome to <%= instance_name %>",
+ text: "Welcome to <%= instance_name %>"
+ ]
+ ```
+
## Message rewrite facility
### :mrf
### :uri_schemes
* `valid_schemes`: List of the scheme part that is considered valid to be an URL.
-### :auto_linker
+### Pleroma.Formatter
-Configuration for the `auto_linker` library:
+Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.
-* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear.
-* `rel: "noopener noreferrer"` - override the rel attribute. false to clear.
-* `new_window: true` - set to false to remove `target='_blank'` attribute.
-* `scheme: false` - Set to true to link urls with schema `http://google.com`.
-* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`.
-* `strip_prefix: true` - Strip the scheme prefix.
-* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.).
+* `class` - specify the class to be added to the generated link (default: `false`)
+* `rel` - specify the rel attribute (default: `ugc`)
+* `new_window` - adds `target="_blank"` attribute (default: `false`)
+* `truncate` - Set to a number to truncate URLs longer then the number. Truncated URLs will end in `...` (default: `false`)
+* `strip_prefix` - Strip the scheme prefix (default: `false`)
+* `extra` - link URLs with rarely used schemes (magnet, ipfs, irc, etc.) (default: `true`)
+* `validate_tld` - Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for urls without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't) (default: `:no_scheme`)
Example:
```elixir
-config :auto_linker,
- opts: [
- scheme: true,
- extra: true,
- class: false,
- strip_prefix: false,
- new_window: false,
- rel: "ugc"
- ]
+config :pleroma, Pleroma.Formatter,
+ class: false,
+ rel: "ugc",
+ new_window: false,
+ truncate: false,
+ strip_prefix: false,
+ extra: true,
+ validate_tld: :no_scheme
```
## Custom Runtime Modules (`:modules`)
Control favicons for instances.
* `enabled`: Allow/disallow displaying and getting instances favicons
+
+## Frontend management
+
+Frontends in Pleroma are swappable - you can specify which one to use here.
+
+For now, you can set a frontend with the key `primary` and the options of `name` and `ref`. This will then make Pleroma serve the frontend from a folder constructed by concatenating the instance static path, `frontends` and the name and ref.
+
+The key `primary` refers to the frontend that will be served by default for general requests. In the future, other frontends like the admin frontend will also be configurable here.
+
+If you don't set anything here, the bundled frontend will be used.
+
+Example:
+
+```
+config :pleroma, :frontends,
+ primary: %{
+ "name" => "pleroma",
+ "ref" => "stable"
+ }
+```
+
+This would serve the frontend from the the folder at `$instance_static/frontends/pleroma/stable`. You have to copy the frontend into this folder yourself. You can choose the name and ref any way you like, but they will be used by mix tasks to automate installation in the future, the name referring to the project and the ref referring to a commit.
Application.put_env(:logger, :console, level: :debug)
end
+ adapter = Application.get_env(:tesla, :adapter)
+
apps =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
+ if adapter == Tesla.Adapter.Gun do
[:gun | @apps]
else
[:hackney | @apps]
Enum.each(apps, &Application.ensure_all_started/1)
- children = [
- Pleroma.Repo,
- {Pleroma.Config.TransferTask, false},
- Pleroma.Web.Endpoint
- ]
+ children =
+ [
+ Pleroma.Repo,
+ {Pleroma.Config.TransferTask, false},
+ Pleroma.Web.Endpoint,
+ {Oban, Pleroma.Config.get(Oban)}
+ ] ++
+ http_children(adapter)
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
def escape_sh_path(path) do
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
end
+
+ defp http_children(Tesla.Adapter.Gun) do
+ Pleroma.Gun.ConnectionPool.children() ++
+ [{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
+ end
+
+ defp http_children(_), do: []
end
@spec verify!() :: :ok | VerifyError.t()
def verify! do
:ok
+ |> check_confirmation_accounts!
|> check_migrations_applied!()
+ |> check_welcome_message_config!()
|> check_rum!()
|> handle_result()
end
defp handle_result(:ok), do: :ok
defp handle_result({:error, message}), do: raise(VerifyError, message: message)
+ defp check_welcome_message_config!(:ok) do
+ if Pleroma.Config.get([:welcome, :email, :enabled], false) and
+ not Pleroma.Emails.Mailer.enabled?() do
+ Logger.error("""
+ To send welcome email do you need to enable mail.
+ \nconfig :pleroma, Pleroma.Emails.Mailer, enabled: true
+ """)
+
+ {:error, "The mail disabled."}
+ else
+ :ok
+ end
+ end
+
+ defp check_welcome_message_config!(result), do: result
+
+ # Checks account confirmation email
+ #
+ def check_confirmation_accounts!(:ok) do
+ if Pleroma.Config.get([:instance, :account_activation_required]) &&
+ not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do
+ Logger.error(
+ "Account activation enabled, but no Mailer settings enabled.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise setup and enable Mailer."
+ )
+
+ {:error,
+ "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails."}
+ else
+ :ok
+ end
+ end
+
+ def check_confirmation_accounts!(result), do: result
+
# Checks for pending migrations.
#
def check_migrations_applied!(:ok) do
{:quack, :meta},
{:mime, :types},
{:cors_plug, [:max_age, :methods, :expose, :headers]},
- {:auto_linker, :opts},
{:swarm, :node_blacklist},
{:logger, :backends}
]
mrf_user_allowlist()
check_old_mrf_config()
check_media_proxy_whitelist_config()
+ check_welcome_message_config()
+ end
+
+ def check_welcome_message_config do
+ instance_config = Pleroma.Config.get([:instance])
+
+ use_old_config =
+ Keyword.has_key?(instance_config, :welcome_user_nickname) or
+ Keyword.has_key?(instance_config, :welcome_message)
+
+ if use_old_config do
+ Logger.error("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace:
+ \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname`
+ \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message`
+ """)
+ end
end
def check_old_mrf_config do
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Config.Helpers do
+ alias Pleroma.Config
+
+ def instance_name, do: Config.get([:instance, :name])
+
+ defp instance_notify_email do
+ Config.get([:instance, :notify_email]) || Config.get([:instance, :email])
+ end
+
+ def sender do
+ {instance_name(), instance_notify_email()}
+ end
+end
import Swoosh.Email
alias Pleroma.Config
+ alias Pleroma.HTML
alias Pleroma.Web.Router.Helpers
defp instance_config, do: Config.get(:instance)
|> subject("#{instance_name()} Report")
|> html_body(html_body)
end
+
+ def new_unapproved_registration(to, account) do
+ html_body = """
+ <p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
+ <blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
+ <a href="#{Pleroma.Web.base_url()}/pleroma/admin">Visit AdminFE</a>
+ """
+
+ new()
+ |> to({to.name, to.email})
+ |> from({instance_name(), instance_notify_email()})
+ |> subject("New account up for review on #{instance_name()} (@#{account.nickname})")
+ |> html_body(html_body)
+ end
end
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
- defp instance_name, do: Config.get([:instance, :name])
-
- defp sender do
- email = Config.get([:instance, :notify_email]) || Config.get([:instance, :email])
- {instance_name(), email}
- end
+ import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0]
defp recipient(email, nil), do: email
defp recipient(email, name), do: {name, email}
defp recipient(%User{} = user), do: recipient(user.email, user.name)
+ @spec welcome(User.t(), map()) :: Swoosh.Email.t()
+ def welcome(user, opts \\ %{}) do
+ new()
+ |> to(recipient(user))
+ |> from(Map.get(opts, :sender, sender()))
+ |> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!"))
+ |> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!"))
+ |> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!"))
+ end
+
def password_reset_email(user, token) when is_binary(token) do
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
- @auto_linker_config hashtag: true,
- hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
- mention: true,
- mention_handler: &Pleroma.Formatter.mention_handler/4,
- scheme: true
+ defp linkify_opts do
+ Pleroma.Config.get(Pleroma.Formatter) ++
+ [
+ hashtag: true,
+ hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
+ mention: true,
+ mention_handler: &Pleroma.Formatter.mention_handler/4
+ ]
+ end
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case User.get_cached_by_nickname(nickname) do
@spec linkify(String.t(), keyword()) ::
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
def linkify(text, options \\ []) do
- options = options ++ @auto_linker_config
+ options = linkify_opts() ++ options
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
- {text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options)
- {text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options)
+ {text_mentions, %{mentions: mentions}} = Linkify.link_map(mentions, acc, options)
+ {text_rest, %{tags: tags}} = Linkify.link_map(rest, acc, options)
{text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)}
else
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
- {text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
+ {text, %{mentions: mentions, tags: tags}} = Linkify.link_map(text, acc, options)
{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
end
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
- AutoLinker.link(mentions, options) <> AutoLinker.link(rest, options)
+ Linkify.link(mentions, options) <> Linkify.link(rest, options)
else
- AutoLinker.link(text, options)
+ Linkify.link(text, options)
end
end
get_gun_pid_from_worker(worker_pid, true)
[{worker_pid, {gun_pid, _used_by, _crf, _last_reference}}] ->
- GenServer.cast(worker_pid, {:add_client, self(), false})
+ GenServer.call(worker_pid, :add_client)
{:ok, gun_pid}
[] ->
# so instead we use cast + monitor
ref = Process.monitor(worker_pid)
- if register, do: GenServer.cast(worker_pid, {:add_client, self(), true})
+ if register, do: GenServer.cast(worker_pid, {:add_client, self()})
receive do
{:conn_pid, pid} ->
case query_result do
[worker_pid] ->
- GenServer.cast(worker_pid, {:remove_client, self()})
+ GenServer.call(worker_pid, :remove_client)
[] ->
:ok
end
@impl true
- def handle_cast({:add_client, client_pid, send_pid_back}, %{key: key} = state) do
+ def handle_cast({:add_client, client_pid}, state) do
+ case handle_call(:add_client, {client_pid, nil}, state) do
+ {:reply, conn_pid, state, :hibernate} ->
+ send(client_pid, {:conn_pid, conn_pid})
+ {:noreply, state, :hibernate}
+ end
+ end
+
+ @impl true
+ def handle_cast({:remove_client, client_pid}, state) do
+ case handle_call(:remove_client, {client_pid, nil}, state) do
+ {:reply, _, state, :hibernate} ->
+ {:noreply, state, :hibernate}
+ end
+ end
+
+ @impl true
+ def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do
time = :erlang.monotonic_time(:millisecond)
{{conn_pid, _, _, _}, _} =
{conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
end)
- if send_pid_back, do: send(client_pid, {:conn_pid, conn_pid})
-
state =
if state.timer != nil do
Process.cancel_timer(state[:timer])
ref = Process.monitor(client_pid)
state = put_in(state.client_monitors[client_pid], ref)
- {:noreply, state, :hibernate}
+ {:reply, conn_pid, state, :hibernate}
end
@impl true
- def handle_cast({:remove_client, client_pid}, %{key: key} = state) do
+ def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
{{_conn_pid, used_by, _crf, _last_reference}, _} =
Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} ->
{conn_pid, List.delete(used_by, client_pid), crf, last_reference}
nil
end
- {:noreply, %{state | timer: timer}, :hibernate}
+ {:reply, :ok, %{state | timer: timer}, :hibernate}
end
@impl true
@impl true
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
- # Sometimes the client is dead before we demonitor it in :remove_client, so the message
- # arrives anyway
+ :telemetry.execute(
+ [:pleroma, :connection_pool, :client_death],
+ %{client_pid: pid, reason: reason},
+ %{key: state.key}
+ )
- case state.client_monitors[pid] do
- nil ->
- {:noreply, state, :hibernate}
-
- _ref ->
- :telemetry.execute(
- [:pleroma, :connection_pool, :client_death],
- %{client_pid: pid, reason: reason},
- %{key: state.key}
- )
-
- handle_cast({:remove_client, pid}, state)
- end
+ handle_cast({:remove_client, pid}, state)
end
# LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478
"@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
end
+ @spec get_log_entry_message(ModerationLog) :: String.t()
+ def get_log_entry_message(%ModerationLog{
+ data: %{
+ "actor" => %{"nickname" => actor_nickname},
+ "action" => "approve",
+ "subject" => users
+ }
+ }) do
+ "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
+ end
+
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{
data: %{
{:error, "Object has been deleted"} ->
nil
+ {:reject, reason} ->
+ Logger.info("Rejected #{id} while fetching: #{inspect(reason)}")
+ nil
+
e ->
Logger.error("Error while fetching #{id}: #{inspect(e)}")
nil
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.FrontendStatic do
+ require Pleroma.Constants
+
+ @moduledoc """
+ This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
+ """
+ @behaviour Plug
+
+ def file_path(path, frontend_type \\ :primary) do
+ if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
+ instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+
+ Path.join([
+ instance_static_path,
+ "frontends",
+ configuration["name"],
+ configuration["ref"],
+ path
+ ])
+ else
+ nil
+ end
+ end
+
+ def init(opts) do
+ opts
+ |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
+ |> Plug.Static.init()
+ end
+
+ def call(conn, opts) do
+ frontend_type = Map.get(opts, :frontend_type, :primary)
+ path = file_path("", frontend_type)
+
+ if path do
+ conn
+ |> call_static(opts, path)
+ else
+ conn
+ end
+ end
+
+ defp call_static(conn, opts, from) do
+ opts =
+ opts
+ |> Map.put(:from, from)
+
+ Plug.Static.call(conn, opts)
+ end
+end
instance_path =
Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
- if File.exists?(instance_path) do
- instance_path
- else
+ frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary)
+
+ (File.exists?(instance_path) && instance_path) ||
+ (frontend_path && File.exists?(frontend_path) && frontend_path) ||
Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
- end
end
def init(opts) do
opts
|> Keyword.put(:from, "__unconfigured_instance_static_plug")
- |> Keyword.put(:at, "/__unconfigured_instance_static_plug")
|> Plug.Static.init()
end
for only <- Pleroma.Constants.static_only_files() do
- at = Plug.Router.Utils.split("/")
-
def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
call_static(
conn,
opts,
- unquote(at),
Pleroma.Config.get([:instance, :static_dir], "instance/static")
)
end
conn
end
- defp call_static(conn, opts, at, from) do
+ defp call_static(conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
- |> Map.put(:at, at)
Plug.Static.call(conn, opts)
end
defmodule Pleroma.ReverseProxy.Client.Tesla do
@behaviour Pleroma.ReverseProxy.Client
+ alias Pleroma.Gun.ConnectionPool
+
@type headers() :: [{String.t(), String.t()}]
@type status() :: pos_integer()
if is_map(response.body) and method != :head do
{:ok, response.status, response.headers, response.body}
else
+ conn_pid = response.opts[:adapter][:conn]
+ ConnectionPool.release_conn(conn_pid)
{:ok, response.status, response.headers}
end
else
@impl true
@spec stream_body(map()) ::
{:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return()
- def stream_body(%{pid: pid, opts: opts, fin: true}) do
- # if connection was reused, but in tesla were redirects,
- # tesla returns new opened connection, which must be closed manually
- if opts[:old_conn], do: Tesla.Adapter.Gun.close(pid)
- # if there were redirects we need to checkout old conn
- conn = opts[:old_conn] || opts[:conn]
-
- if conn, do: :ok = Pleroma.Gun.ConnectionPool.release_conn(conn)
-
+ def stream_body(%{pid: pid, fin: true}) do
+ ConnectionPool.release_conn(pid)
:done
end
@impl true
@spec close(map) :: :ok | no_return()
def close(%{pid: pid}) do
- adapter = check_adapter()
- adapter.close(pid)
+ ConnectionPool.release_conn(pid)
end
defp check_adapter do
{:ok, code, _, _} ->
{:error, {:invalid_http_response, code}}
+ {:ok, code, _} ->
+ {:error, {:invalid_http_response, code}}
+
{:error, error} ->
{:error, error}
end
require Logger
@type t :: %__MODULE__{}
- @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
+ @type account_status ::
+ :active
+ | :deactivated
+ | :password_reset_pending
+ | :confirmation_pending
+ | :approval_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false)
+ field(:approval_pending, :boolean, default: false)
+ field(:registration_reason, :string, default: nil)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:domain_blocks, {:array, :string}, default: [])
@spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
+ def account_status(%User{approval_pending: true}), do: :approval_pending
def account_status(%User{confirmation_pending: true}) do
if Config.get([:instance, :account_activation_required]) do
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
+ reason_limit = Config.get([:instance, :registration_reason_length], 500)
params = Map.put_new(params, :accepts_chat_messages, true)
need_confirmation? =
opts[:need_confirmation]
end
+ need_approval? =
+ if is_nil(opts[:need_approval]) do
+ Config.get([:instance, :account_approval_required])
+ else
+ opts[:need_approval]
+ end
+
struct
|> confirmation_changeset(need_confirmation: need_confirmation?)
+ |> approval_changeset(need_approval: need_approval?)
|> cast(params, [
:bio,
:raw_bio,
:password,
:password_confirmation,
:emoji,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :registration_reason
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
+ |> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external])
|> put_password_hash
|> put_ap_id()
def post_register_action(%User{} = user) do
with {:ok, user} <- autofollow_users(user),
{:ok, user} <- set_cache(user),
- {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
+ {:ok, _} <- send_welcome_email(user),
+ {:ok, _} <- send_welcome_message(user),
{:ok, _} <- try_send_confirmation_email(user) do
{:ok, user}
end
end
- def try_send_confirmation_email(%User{} = user) do
- if user.confirmation_pending &&
- Config.get([:instance, :account_activation_required]) do
- user
- |> Pleroma.Emails.UserEmail.account_confirmation_email()
- |> Pleroma.Emails.Mailer.deliver_async()
+ def send_welcome_message(user) do
+ if User.WelcomeMessage.enabled?() do
+ User.WelcomeMessage.post_message(user)
+ {:ok, :enqueued}
+ else
+ {:ok, :noop}
+ end
+ end
+
+ def send_welcome_email(%User{email: email} = user) when is_binary(email) do
+ if User.WelcomeEmail.enabled?() do
+ User.WelcomeEmail.send_email(user)
+ {:ok, :enqueued}
+ else
+ {:ok, :noop}
+ end
+ end
+
+ def send_welcome_email(_), do: {:ok, :noop}
+ @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
+ def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
+ if Config.get([:instance, :account_activation_required]) do
+ send_confirmation_email(user)
{:ok, :enqueued}
else
{:ok, :noop}
end
end
- def try_send_confirmation_email(users) do
- Enum.each(users, &try_send_confirmation_email/1)
+ def try_send_confirmation_email(_), do: {:ok, :noop}
+
+ @spec send_confirmation_email(Uset.t()) :: User.t()
+ def send_confirmation_email(%User{} = user) do
+ user
+ |> Pleroma.Emails.UserEmail.account_confirmation_email()
+ |> Pleroma.Emails.Mailer.deliver_async()
+
+ user
end
def needs_update?(%User{local: true}), do: false
end
end
+ def approve(users) when is_list(users) do
+ Repo.transaction(fn ->
+ Enum.map(users, fn user ->
+ with {:ok, user} <- approve(user), do: user
+ end)
+ end)
+ end
+
+ def approve(%User{} = user) do
+ change(user, approval_pending: false)
+ |> update_and_set_cache()
+ end
+
def update_notification_settings(%User{} = user, settings) do
user
|> cast(%{notification_settings: settings}, [])
defp delete_or_deactivate(%User{local: true} = user) do
status = account_status(user)
- if status == :confirmation_pending do
- delete_and_invalidate_cache(user)
- else
- user
- |> change(%{deactivated: true, email: nil})
- |> update_and_set_cache()
+ case status do
+ :confirmation_pending ->
+ delete_and_invalidate_cache(user)
+
+ :approval_pending ->
+ delete_and_invalidate_cache(user)
+
+ _ ->
+ user
+ |> change(%{deactivated: true, email: nil})
+ |> update_and_set_cache()
end
end
cast(user, params, [:confirmation_pending, :confirmation_token])
end
+ @spec approval_changeset(User.t(), keyword()) :: Changeset.t()
+ def approval_changeset(user, need_approval: need_approval?) do
+ params = if need_approval?, do: %{approval_pending: true}, else: %{approval_pending: false}
+ cast(user, params, [:approval_pending])
+ end
+
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
if id not in user.pinned_activities do
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
external: boolean(),
active: boolean(),
deactivated: boolean(),
+ need_approval: boolean(),
is_admin: boolean(),
is_moderator: boolean(),
super_users: boolean(),
|> where([u], not is_nil(u.nickname))
end
+ defp compose_query({:need_approval, _}, query) do
+ where(query, [u], u.approval_pending)
+ end
+
defp compose_query({:followers, %User{id: id}}, query) do
query
|> where([u], u.id != ^id)
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.WelcomeEmail do
+ @moduledoc """
+ The module represents the functions to send welcome email.
+ """
+
+ alias Pleroma.Config
+ alias Pleroma.Emails
+ alias Pleroma.User
+
+ import Pleroma.Config.Helpers, only: [instance_name: 0]
+
+ @spec enabled?() :: boolean()
+ def enabled?, do: Config.get([:welcome, :email, :enabled], false)
+
+ @spec send_email(User.t()) :: {:ok, Oban.Job.t()}
+ def send_email(%User{} = user) do
+ user
+ |> Emails.UserEmail.welcome(email_options(user))
+ |> Emails.Mailer.deliver_async()
+ end
+
+ defp email_options(user) do
+ bindings = [user: user, instance_name: instance_name()]
+
+ %{}
+ |> add_sender(Config.get([:welcome, :email, :sender], nil))
+ |> add_option(:subject, bindings)
+ |> add_option(:html, bindings)
+ |> add_option(:text, bindings)
+ end
+
+ defp add_option(opts, option, bindings) do
+ [:welcome, :email, option]
+ |> Config.get(nil)
+ |> eval_string(bindings)
+ |> merge_options(opts, option)
+ end
+
+ defp add_sender(opts, {_name, _email} = sender) do
+ merge_options(sender, opts, :sender)
+ end
+
+ defp add_sender(opts, sender) when is_binary(sender) do
+ add_sender(opts, {instance_name(), sender})
+ end
+
+ defp add_sender(opts, _), do: opts
+
+ defp merge_options(nil, options, _option), do: options
+
+ defp merge_options(value, options, option) do
+ Map.merge(options, %{option => value})
+ end
+
+ defp eval_string(nil, _), do: nil
+ defp eval_string("", _), do: nil
+ defp eval_string(str, bindings), do: EEx.eval_string(str, bindings)
+end
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.WelcomeMessage do
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.CommonAPI
- def post_welcome_message_to_user(user) do
- with %User{} = sender_user <- welcome_user(),
- message when is_binary(message) <- welcome_message() do
- CommonAPI.post(sender_user, %{
+ @spec enabled?() :: boolean()
+ def enabled?, do: Config.get([:welcome, :direct_message, :enabled], false)
+
+ @spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil}
+ def post_message(user) do
+ [:welcome, :direct_message, :sender_nickname]
+ |> Config.get(nil)
+ |> fetch_sender()
+ |> do_post(user, welcome_message())
+ end
+
+ defp do_post(%User{} = sender, %User{nickname: nickname}, message)
+ when is_binary(message) do
+ CommonAPI.post(
+ sender,
+ %{
visibility: "direct",
- status: "@#{user.nickname}\n#{message}"
- })
- else
- _ -> {:ok, nil}
- end
+ status: "@#{nickname}\n#{message}"
+ }
+ )
end
- defp welcome_user do
- with nickname when is_binary(nickname) <-
- Pleroma.Config.get([:instance, :welcome_user_nickname]),
- %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
+ defp do_post(_sender, _recipient, _message), do: {:ok, nil}
+
+ defp fetch_sender(nickname) when is_binary(nickname) do
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
user
else
_ -> nil
end
end
+ defp fetch_sender(_), do: nil
+
defp welcome_message do
- Pleroma.Config.get([:instance, :welcome_message])
+ Config.get([:welcome, :direct_message, :message], nil)
end
end
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
+ {:error, {:reject, reason} = e} ->
+ Logger.info("Rejected user #{ap_id}: #{inspect(reason)}")
+ {:error, e}
+
{:error, e} ->
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
def filter_by_summary(_in_reply_to, child), do: child
- def filter(%{"type" => "Create", "object" => child_object} = object) do
+ def filter(%{"type" => "Create", "object" => child_object} = object)
+ when is_map(child_object) do
child =
child_object["inReplyTo"]
|> Object.normalize(child_object["inReplyTo"])
the system.
"""
+ alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
|> UndoValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
+ undone_object = Activity.get_by_ap_id(object["object"])
+
+ meta =
+ meta
+ |> Keyword.put(:object_data, undone_object.data)
+
{:ok, object, meta}
end
end
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
if !do_not_federate && local do
+ activity =
+ if object = Keyword.get(meta, :object_data) do
+ %{activity | data: Map.put(activity.data, "object", object)}
+ else
+ activity
+ end
+
Federator.publish(activity)
{:ok, :federated}
else
|> Map.drop(["conversation"])
else
e ->
- Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+ Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
else
case Activity.get_by_ap_id_with_object(id) do
%Activity{} = activity ->
+ activity_actor = User.get_by_ap_id(activity.object.data["actor"])
+
%{
"type" => "Note",
"id" => activity.data["id"],
"content" => activity.object.data["content"],
"published" => activity.object.data["published"],
"actor" =>
- AccountView.render("show.json", %{
- user: User.get_by_ap_id(activity.object.data["actor"])
- })
+ AccountView.render(
+ "show.json",
+ %{user: activity_actor, skip_visibility_check: true}
+ )
}
_ ->
:user_toggle_activation,
:user_activate,
:user_deactivate,
+ :user_approve,
:tag_users,
:untag_users,
:right_add,
|> render("index.json", %{users: Keyword.values(updated_users)})
end
+ def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
+ {:ok, updated_users} = User.approve(users)
+
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "approve"
+ })
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", %{users: updated_users})
+ end
+
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
with {:ok, _} <- User.tag(nicknames, tags) do
ModerationLog.insert_log(%{
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
json(
conn,
- AccountView.render("index.json", users: users, count: count, page_size: page_size)
+ AccountView.render("index.json",
+ users: users,
+ count: count,
+ page_size: page_size
+ )
)
end
end
- @filters ~w(local external active deactivated is_admin is_moderator)
+ @filters ~w(local external active deactivated need_approval is_admin is_moderator)
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
end
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+ users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
User.toggle_confirmation(users)
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "confirm_email"
- })
+ ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
json(conn, "")
end
def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
- users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
-
- User.try_send_confirmation_email(users)
+ users =
+ Enum.map(nicknames, fn nickname ->
+ nickname
+ |> User.get_cached_by_nickname()
+ |> User.send_confirmation_email()
+ end)
- ModerationLog.insert_log(%{
- actor: admin,
- subject: users,
- action: "resend_confirmation_email"
- })
+ ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
json(conn, "")
end
"roles" => User.roles(user),
"tags" => user.tags || [],
"confirmation_pending" => user.confirmation_pending,
- "url" => user.uri || user.ap_id
+ "approval_pending" => user.approval_pending,
+ "url" => user.uri || user.ap_id,
+ "registration_reason" => user.registration_reason
}
end
end
def merge_account_views(%User{} = user) do
- MastodonAPI.AccountView.render("show.json", %{user: user})
+ MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true})
|> Map.merge(AdminAPI.AccountView.render("show.json", %{user: user}))
end
"content" => "Check this out :firefox:",
"id" => "13",
"chat_id" => "1",
- "actor_id" => "someflakeid",
+ "account_id" => "someflakeid",
"unread" => false
},
%{
- "actor_id" => "someflakeid",
+ "account_id" => "someflakeid",
"content" => "Whats' up?",
"id" => "12",
"chat_id" => "1",
}
end
+ # Supporting domain query parameter is deprecated in Mastodon API
def create_operation do
%Operation{
tags: ["domain_blocks"],
""",
operationId: "DomainBlockController.create",
requestBody: domain_block_request(),
+ parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")],
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{200 => empty_object_response()}
}
end
+ # Supporting domain query parameter is deprecated in Mastodon API
def delete_operation do
%Operation{
tags: ["domain_blocks"],
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
operationId: "DomainBlockController.delete",
requestBody: domain_block_request(),
+ parameters: [Operation.parameter(:domain, :query, %Schema{type: :string}, "Domain name")],
security: [%{"oAuth" => ["follow", "write:blocks"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
type: :object,
properties: %{
domain: %Schema{type: :string}
- },
- required: [:domain]
+ }
},
- required: true,
+ required: false,
example: %{
"domain" => "facebook.com"
}
defmodule Pleroma.Web.ChatChannel do
use Phoenix.Channel
+
alias Pleroma.User
alias Pleroma.Web.ChatChannel.ChatChannelState
+ alias Pleroma.Web.MastodonAPI.AccountView
def join("chat:public", _message, socket) do
send(self(), :after_join)
if String.length(text) in 1..Pleroma.Config.get([:instance, :chat_limit]) do
author = User.get_cached_by_nickname(user_name)
- author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author)
+ author_json = AccountView.render("show.json", user: author, skip_visibility_check: true)
- message = ChatChannelState.add_message(%{text: text, author: author})
+ message = ChatChannelState.add_message(%{text: text, author: author_json})
broadcast!(socket, "new_msg", message)
end
}
)
+ # Careful! No `only` restriction here, as we don't know what frontends contain.
+ plug(Pleroma.Plugs.FrontendStatic,
+ at: "/",
+ frontend_type: :primary,
+ gzip: true,
+ cache_control_for_etags: @static_cache_control,
+ headers: %{
+ "cache-control" => @static_cache_control
+ }
+ )
+
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
"atom"
end
- with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
activities =
%{
type: ["Create"],
render_error(conn, :not_found, "Not found")
end
+ def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do
json(conn, %{})
end
+ def create(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do
+ User.block_domain(blocker, domain)
+ json(conn, %{})
+ end
+
@doc "DELETE /api/v1/domain_blocks"
def delete(%{assigns: %{user: blocker}, body_params: %{domain: domain}} = conn, _params) do
User.unblock_domain(blocker, domain)
json(conn, %{})
end
+
+ def delete(%{assigns: %{user: blocker}} = conn, %{domain: domain}) do
+ User.unblock_domain(blocker, domain)
+ json(conn, %{})
+ end
end
AccountView.render("index.json",
users: accounts,
for: options[:for_user],
- as: :user,
embed_relationships: options[:embed_relationships]
)
end
UserRelationship.view_relationships_option(reading_user, users)
end
- opts = Map.put(opts, :relationships, relationships_opt)
+ opts =
+ opts
+ |> Map.merge(%{relationships: relationships_opt, as: :user})
+ |> Map.delete(:users)
users
|> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1)
end
- def render("show.json", %{user: user} = opts) do
- if User.visible_for(user, opts[:for]) == :visible do
+ @doc """
+ Renders specified user account.
+ :skip_visibility_check option skips visibility check and renders any user (local or remote)
+ regardless of [:pleroma, :restrict_unauthenticated] setting.
+ :for option specifies the requester and can be a User record or nil.
+ Only use `user: user, for: user` when `user` is the actual requester of own profile.
+ """
+ def render("show.json", %{user: _user, skip_visibility_check: true} = opts) do
+ do_render("show.json", opts)
+ end
+
+ def render("show.json", %{user: user, for: for_user_or_nil} = opts) do
+ if User.visible_for(user, for_user_or_nil) == :visible do
do_render("show.json", opts)
else
%{}
end
end
+ def render("show.json", _) do
+ raise "In order to prevent account accessibility issues, " <>
+ ":skip_visibility_check or :for option is required."
+ end
+
def render("mention.json", %{user: user}) do
%{
id: to_string(user.id),
%{
id: participation.id |> to_string(),
- accounts: render(AccountView, "index.json", users: users, as: :user),
+ accounts: render(AccountView, "index.json", users: users, for: user),
unread: !participation.read,
last_status:
render(StatusView, "show.json",
thumbnail: Keyword.get(instance, :instance_thumbnail),
languages: ["en"],
registrations: Keyword.get(instance, :registrations_open),
+ approval_required: Keyword.get(instance, :account_approval_required),
# Extra (not present in Mastodon):
max_toot_chars: Keyword.get(instance, :limit),
poll_limits: Keyword.get(instance, :poll_limits),
emoji_reactions =
with %{data: %{"reactions" => emoji_reactions}} <- object do
- Enum.map(emoji_reactions, fn [emoji, users] ->
- %{
- name: emoji,
- count: length(users),
- me: !!(opts[:for] && opts[:for].ap_id in users)
- }
+ Enum.map(emoji_reactions, fn
+ [emoji, users] when is_list(users) ->
+ build_emoji_map(emoji, users, opts[:for])
+
+ {emoji, users} when is_list(users) ->
+ build_emoji_map(emoji, users, opts[:for])
+
+ _ ->
+ nil
end)
+ |> Enum.reject(&is_nil/1)
else
_ -> []
end
defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),
do: id in pinned_activities
+
+ defp build_emoji_map(emoji, users, current_user) do
+ %{
+ name: emoji,
+ count: length(users),
+ me: !!(current_user && current_user.ap_id in users)
+ }
+ end
end
)
end
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do
+ render_error(
+ conn,
+ :forbidden,
+ "Your account is awaiting approval.",
+ %{},
+ "awaiting_approval"
+ )
+ end
+
defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
render_invalid_credentials_error(conn)
end
cm_ref <- MessageReference.for_chat_and_object(chat, message) do
conn
|> put_view(MessageReferenceView)
- |> render("show.json", for: user, chat_message_reference: cm_ref)
+ |> render("show.json", chat_message_reference: cm_ref)
end
end
- def mark_message_as_read(%{assigns: %{user: %{id: user_id} = user}} = conn, %{
+ def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{
id: chat_id,
message_id: message_id
}) do
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
conn
|> put_view(MessageReferenceView)
- |> render("show.json", for: user, chat_message_reference: cm_ref)
+ |> render("show.json", chat_message_reference: cm_ref)
end
end
def mark_as_read(
- %{body_params: %{last_read_id: last_read_id}, assigns: %{user: %{id: user_id}}} = conn,
+ %{
+ body_params: %{last_read_id: last_read_id},
+ assigns: %{user: %{id: user_id}}
+ } = conn,
%{id: id}
) do
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
end
end
- def messages(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: id} = params) do
+ def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do
with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do
cm_refs =
chat
conn
|> put_view(MessageReferenceView)
- |> render("index.json", for: user, chat_message_references: cm_refs)
+ |> render("index.json", chat_message_references: cm_refs)
else
_ ->
conn
]
)
- @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
- plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list])
+ @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
def render("show.json", %{chat: %Chat{} = chat} = opts) do
recipient = User.get_cached_by_ap_id(chat.recipient)
last_message = opts[:last_message] || MessageReference.last_message_for_chat(chat)
+ account_view_opts = account_view_opts(opts, recipient)
%{
id: chat.id |> to_string(),
- account: AccountView.render("show.json", Map.put(opts, :user, recipient)),
+ account: AccountView.render("show.json", account_view_opts),
unread: MessageReference.unread_count_for_chat(chat),
last_message:
last_message &&
}
end
- def render("index.json", %{chats: chats}) do
- render_many(chats, __MODULE__, "show.json")
+ def render("index.json", %{chats: chats} = opts) do
+ render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
+ end
+
+ defp account_view_opts(opts, recipient) do
+ account_view_opts = Map.put(opts, :user, recipient)
+
+ if Map.has_key?(account_view_opts, :for) do
+ account_view_opts
+ else
+ Map.put(account_view_opts, :skip_visibility_check, true)
+ end
end
end
%{
name: emoji,
count: length(users),
- accounts: render(AccountView, "index.json", users: users, for: user, as: :user),
+ accounts: render(AccountView, "index.json", users: users, for: user),
me: !!(user && user.ap_id in user_ap_ids)
}
end
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
- validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
+ validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld])
page_url
- |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld)
+ |> Linkify.Parser.url?(validate_tld: validate_tld)
|> parse_uri(page_url)
end
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
patch("/users/activate", AdminAPIController, :user_activate)
patch("/users/deactivate", AdminAPIController, :user_deactivate)
+ patch("/users/approve", AdminAPIController, :user_approve)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
|> Map.put(:nickname, params[:username])
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|> Map.put(:password_confirmation, params[:password])
+ |> Map.put(:registration_reason, params[:reason])
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)
case User.register(changeset) do
{:ok, user} ->
+ maybe_notify_admins(user)
{:ok, user}
{:error, changeset} ->
end
end
+ defp maybe_notify_admins(%User{} = account) do
+ if Pleroma.Config.get([:instance, :account_approval_required]) do
+ User.all_superusers()
+ |> Enum.filter(fn user -> not is_nil(user.email) end)
+ |> Enum.each(fn superuser ->
+ superuser
+ |> Pleroma.Emails.AdminEmail.new_unapproved_registration(account)
+ |> Pleroma.Emails.Mailer.deliver_async()
+ end)
+ end
+ end
+
def password_reset(nickname_or_email) do
with true <- is_binary(nickname_or_email),
%User{local: true, email: email} = user when is_binary(email) <-
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.CustomEmojiView
- @default_settings %{
- onboarded: true,
- home: %{
- shows: %{
- reblog: true,
- reply: true
- }
- },
- notifications: %{
- alerts: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- },
- shows: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- },
- sounds: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- }
- }
- }
-
def initial_state(token, user, custom_emojis) do
limit = Config.get([:instance, :limit])
"video\/mp4"
]
},
- settings: user.mastofe_settings || @default_settings,
+ settings: user.mastofe_settings || %{},
push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
{:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
- {:earmark, "~> 1.3"},
+ {:earmark, "1.4.3"},
{:bbcode_pleroma, "~> 0.2.0"},
{:ex_machina, "~> 2.3", only: :test},
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.3", only: :test},
{:crypt,
- git: "https://github.com/msantos/crypt", ref: "f63a705f92c26955977ee62a313012e309a4d77a"},
+ git: "https://github.com/msantos/crypt.git",
+ ref: "f63a705f92c26955977ee62a313012e309a4d77a"},
{:cors_plug, "~> 1.5"},
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"},
{:swoosh,
- git: "https://github.com/swoosh/swoosh",
+ git: "https://github.com/swoosh/swoosh.git",
ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5",
override: true},
{:phoenix_swoosh, "~> 0.2"},
{:floki, "~> 0.25"},
{:timex, "~> 3.5"},
{:ueberauth, "~> 0.4"},
- {:auto_linker,
- git: "https://git.pleroma.social/pleroma/auto_linker.git",
- ref: "95e8188490e97505c56636c1379ffdf036c1fdde"},
+ {:linkify, "~> 0.2.0"},
{:http_signatures,
git: "https://git.pleroma.social/pleroma/http_signatures.git",
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
{:excoveralls, "~> 0.12.1", only: :test},
{:flake_id, "~> 0.1.0"},
{:concurrent_limiter,
- git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter",
+ git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git",
ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"},
{:remote_ip,
git: "https://git.pleroma.social/pleroma/remote_ip.git",
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
- "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]},
"base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
- "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter", "8eee96c6ba39b9286ec44c51c52d9f2758951365", [ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"]},
+ "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "8eee96c6ba39b9286ec44c51c52d9f2758951365", [ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"]},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9af027d20dc12dd0c4345a6b87247e0c62965871feea0bfecf9764648b02cc69"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
"credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"},
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
- "crypt": {:git, "https://github.com/msantos/crypt", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
+ "crypt": {:git, "https://github.com/msantos/crypt.git", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
+ "linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
- "swoosh": {:git, "https://github.com/swoosh/swoosh", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]},
+ "swoosh": {:git, "https://github.com/swoosh/swoosh.git", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"tesla": {:git, "https://github.com/teamon/tesla.git", "af3707078b10793f6a534938e56b963aff82fe3c", [ref: "af3707078b10793f6a534938e56b963aff82fe3c"]},
--- /dev/null
+defmodule Pleroma.Repo.Migrations.AddApprovalFieldsToUsers do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add(:approval_pending, :boolean)
+ add(:registration_reason, :text)
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.AutolinkerToLinkify do
+ use Ecto.Migration
+ alias Pleroma.ConfigDB
+
+ @autolinker_path %{group: :auto_linker, key: :opts}
+ @linkify_path %{group: :pleroma, key: Pleroma.Formatter}
+
+ @compat_opts [:class, :rel, :new_window, :truncate, :strip_prefix, :extra]
+
+ def change do
+ with {:ok, {old, new}} <- maybe_get_params() do
+ move_config(old, new)
+ end
+ end
+
+ defp move_config(%{} = old, %{} = new) do
+ {:ok, _} = ConfigDB.update_or_create(new)
+ {:ok, _} = ConfigDB.delete(old)
+ :ok
+ end
+
+ defp maybe_get_params() do
+ with %ConfigDB{value: opts} <- ConfigDB.get_by_params(@autolinker_path),
+ opts <- transform_opts(opts),
+ %{} = linkify_params <- Map.put(@linkify_path, :value, opts) do
+ {:ok, {@autolinker_path, linkify_params}}
+ end
+ end
+
+ def transform_opts(opts) when is_list(opts) do
+ opts
+ |> Enum.into(%{})
+ |> Map.take(@compat_opts)
+ |> Map.to_list()
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.FixMalformedFormatterConfig do
+ use Ecto.Migration
+ alias Pleroma.ConfigDB
+
+ @config_path %{group: :pleroma, key: Pleroma.Formatter}
+
+ def change do
+ with %ConfigDB{value: %{} = opts} <- ConfigDB.get_by_params(@config_path),
+ fixed_opts <- Map.to_list(opts) do
+ fix_config(fixed_opts)
+ else
+ _ -> :skipped
+ end
+ end
+
+ defp fix_config(fixed_opts) when is_list(fixed_opts) do
+ {:ok, _} =
+ ConfigDB.update_or_create(%{
+ group: :pleroma,
+ key: Pleroma.Formatter,
+ value: fixed_opts
+ })
+
+ :ok
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.MoveWelcomeSettings do
+ use Ecto.Migration
+
+ alias Pleroma.ConfigDB
+
+ @old_keys [:welcome_user_nickname, :welcome_message]
+
+ def up do
+ with {:ok, config, {keep_values, move_values}} <- get_old_values() do
+ insert_welcome_settings(move_values)
+ update_instance_config(config, keep_values)
+ end
+ end
+
+ def down do
+ with {:ok, welcome_config, revert_values} <- get_revert_values() do
+ revert_instance_config(revert_values)
+ Pleroma.Repo.delete(welcome_config)
+ end
+ end
+
+ defp insert_welcome_settings([_ | _] = values) do
+ unless String.trim(values[:welcome_message]) == "" do
+ config_values = [
+ direct_message: %{
+ enabled: true,
+ sender_nickname: values[:welcome_user_nickname],
+ message: values[:welcome_message]
+ },
+ email: %{
+ enabled: false,
+ sender: nil,
+ subject: "Welcome to <%= instance_name %>",
+ html: "Welcome to <%= instance_name %>",
+ text: "Welcome to <%= instance_name %>"
+ }
+ ]
+
+ {:ok, _} =
+ %ConfigDB{}
+ |> ConfigDB.changeset(%{group: :pleroma, key: :welcome, value: config_values})
+ |> Pleroma.Repo.insert()
+ end
+
+ :ok
+ end
+
+ defp insert_welcome_settings(_), do: :noop
+
+ defp revert_instance_config(%{} = revert_values) do
+ values = [
+ welcome_user_nickname: revert_values[:sender_nickname],
+ welcome_message: revert_values[:message]
+ ]
+
+ ConfigDB.update_or_create(%{group: :pleroma, key: :instance, value: values})
+ end
+
+ defp revert_instance_config(_), do: :noop
+
+ defp update_instance_config(config, values) do
+ {:ok, _} =
+ config
+ |> ConfigDB.changeset(%{value: values})
+ |> Pleroma.Repo.update()
+
+ :ok
+ end
+
+ defp get_revert_values do
+ config = ConfigDB.get_by_params(%{group: :pleroma, key: :welcome})
+
+ cond do
+ is_nil(config) -> {:noop, nil, nil}
+ true -> {:ok, config, config.value[:direct_message]}
+ end
+ end
+
+ defp get_old_values do
+ config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
+
+ cond do
+ is_nil(config) ->
+ {:noop, config, {}}
+
+ is_binary(config.value[:welcome_message]) ->
+ {:ok, config,
+ {Keyword.drop(config.value, @old_keys), Keyword.take(config.value, @old_keys)}}
+
+ true ->
+ {:ok, config, {Keyword.drop(config.value, @old_keys), []}}
+ end
+ end
+end
alias Pleroma.Repo
+ describe "check_welcome_message_config!/1" do
+ setup do: clear_config([:welcome])
+ setup do: clear_config([Pleroma.Emails.Mailer])
+
+ test "raises if welcome email enabled but mail disabled" do
+ Pleroma.Config.put([:welcome, :email, :enabled], true)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+
+ assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+ end
+
+ describe "check_confirmation_accounts!" do
+ setup_with_mocks([
+ {Pleroma.ApplicationRequirements, [:passthrough],
+ [
+ check_migrations_applied!: fn _ -> :ok end
+ ]}
+ ]) do
+ :ok
+ end
+
+ setup do: clear_config([:instance, :account_activation_required])
+
+ test "raises if account confirmation is required but mailer isn't enable" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+
+ assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
+ fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+
+ test "doesn't do anything if account confirmation is disabled" do
+ Pleroma.Config.put([:instance, :account_activation_required], false)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+ assert Pleroma.ApplicationRequirements.verify!() == :ok
+ end
+
+ test "doesn't do anything if account confirmation is required and mailer is enabled" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], true)
+ assert Pleroma.ApplicationRequirements.verify!() == :ok
+ end
+ end
+
describe "check_rum!" do
setup_with_mocks([
{Pleroma.ApplicationRequirements, [:passthrough],
assert res.to == [{to_user.name, to_user.email}]
assert res.from == {config[:name], config[:notify_email]}
end
+
+ test "new unapproved registration email" do
+ config = Pleroma.Config.get(:instance)
+ to_user = insert(:user)
+ account = insert(:user, registration_reason: "Plz let me in")
+
+ res = AdminEmail.new_unapproved_registration(to_user, account)
+
+ account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id)
+
+ assert res.to == [{to_user.name, to_user.email}]
+ assert res.from == {config[:name], config[:notify_email]}
+ assert res.subject == "New account up for review on #{config[:name]} (@#{account.nickname})"
+
+ assert res.html_body == """
+ <p>New account for review: <a href="#{account_url}">@#{account.nickname}</a></p>
+ <blockquote>Plz let me in</blockquote>
+ <a href="http://localhost:4001/pleroma/admin">Visit AdminFE</a>
+ """
+ end
end
import Pleroma.Factory
setup_all do
+ clear_config(Pleroma.Formatter)
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
assert {_text, ^expected_mentions, []} = Formatter.linkify(text)
end
+
+ test "it parses URL containing local mention" do
+ _user = insert(:user, %{nickname: "lain"})
+
+ text = "https://example.com/@lain"
+
+ expected = ~S(<a href="https://example.com/@lain" rel="ugc">https://example.com/@lain</a>)
+
+ assert {^expected, [], []} = Formatter.linkify(text)
+ end
+
+ test "it correctly parses angry face D:< with mention" do
+ lain =
+ insert(:user, %{
+ nickname: "lain@lain.com",
+ ap_id: "https://lain.com/users/lain",
+ id: "9qrWmR0cKniB0YU0TA"
+ })
+
+ text = "@lain@lain.com D:<"
+
+ expected_text =
+ ~S(<span class="h-card"><a class="u-url mention" data-user="9qrWmR0cKniB0YU0TA" href="https://lain.com/users/lain" rel="ugc">@<span>lain</span></a></span> D:<)
+
+ expected_mentions = [
+ {"@lain@lain.com", lain}
+ ]
+
+ assert {^expected_text, ^expected_mentions, []} = Formatter.linkify(text)
+ end
end
describe ".parse_tags" do
--- /dev/null
+defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ import Pleroma.Tests.Helpers
+ alias Pleroma.ConfigDB
+
+ setup do: clear_config(Pleroma.Formatter)
+ setup_all do: require_migration("20200716195806_autolinker_to_linkify")
+
+ test "change/0 converts auto_linker opts for Pleroma.Formatter", %{migration: migration} do
+ autolinker_opts = [
+ extra: true,
+ validate_tld: true,
+ class: false,
+ strip_prefix: false,
+ new_window: false,
+ rel: "testing"
+ ]
+
+ insert(:config, group: :auto_linker, key: :opts, value: autolinker_opts)
+
+ migration.change()
+
+ assert nil == ConfigDB.get_by_params(%{group: :auto_linker, key: :opts})
+
+ %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter})
+
+ assert new_opts == [
+ class: false,
+ extra: true,
+ new_window: false,
+ rel: "testing",
+ strip_prefix: false
+ ]
+
+ Pleroma.Config.put(Pleroma.Formatter, new_opts)
+ assert new_opts == Pleroma.Config.get(Pleroma.Formatter)
+
+ {text, _mentions, []} =
+ Pleroma.Formatter.linkify(
+ "https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\n\nOmg will COVID finally end Black Friday???"
+ )
+
+ assert text ==
+ "<a href=\"https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\" rel=\"testing\">https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7</a>\n\nOmg will COVID finally end Black Friday???"
+ end
+
+ test "transform_opts/1 returns a list of compatible opts", %{migration: migration} do
+ old_opts = [
+ extra: true,
+ validate_tld: true,
+ class: false,
+ strip_prefix: false,
+ new_window: false,
+ rel: "qqq"
+ ]
+
+ expected_opts = [
+ class: false,
+ extra: true,
+ new_window: false,
+ rel: "qqq",
+ strip_prefix: false
+ ]
+
+ assert migration.transform_opts(old_opts) == expected_opts
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.FixMalformedFormatterConfigTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ import Pleroma.Tests.Helpers
+ alias Pleroma.ConfigDB
+
+ setup do: clear_config(Pleroma.Formatter)
+ setup_all do: require_migration("20200722185515_fix_malformed_formatter_config")
+
+ test "change/0 converts a map into a list", %{migration: migration} do
+ incorrect_opts = %{
+ class: false,
+ extra: true,
+ new_window: false,
+ rel: "F",
+ strip_prefix: false
+ }
+
+ insert(:config, group: :pleroma, key: Pleroma.Formatter, value: incorrect_opts)
+
+ assert :ok == migration.change()
+
+ %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter})
+
+ assert new_opts == [
+ class: false,
+ extra: true,
+ new_window: false,
+ rel: "F",
+ strip_prefix: false
+ ]
+
+ Pleroma.Config.put(Pleroma.Formatter, new_opts)
+ assert new_opts == Pleroma.Config.get(Pleroma.Formatter)
+
+ {text, _mentions, []} =
+ Pleroma.Formatter.linkify(
+ "https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\n\nOmg will COVID finally end Black Friday???"
+ )
+
+ assert text ==
+ "<a href=\"https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7\" rel=\"F\">https://www.businessinsider.com/walmart-will-close-stores-on-thanksgiving-ending-black-friday-tradition-2020-7</a>\n\nOmg will COVID finally end Black Friday???"
+ end
+
+ test "change/0 skips if Pleroma.Formatter config is already a list", %{migration: migration} do
+ opts = [
+ class: false,
+ extra: true,
+ new_window: false,
+ rel: "ugc",
+ strip_prefix: false
+ ]
+
+ insert(:config, group: :pleroma, key: Pleroma.Formatter, value: opts)
+
+ assert :skipped == migration.change()
+
+ %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter})
+
+ assert new_opts == opts
+ end
+
+ test "change/0 skips if Pleroma.Formatter is empty", %{migration: migration} do
+ assert :skipped == migration.change()
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.MoveWelcomeSettingsTest do
+ use Pleroma.DataCase
+ import Pleroma.Factory
+ import Pleroma.Tests.Helpers
+ alias Pleroma.ConfigDB
+
+ setup_all do: require_migration("20200724133313_move_welcome_settings")
+
+ describe "up/0" do
+ test "converts welcome settings", %{migration: migration} do
+ insert(:config,
+ group: :pleroma,
+ key: :instance,
+ value: [
+ welcome_message: "Test message",
+ welcome_user_nickname: "jimm",
+ name: "Pleroma"
+ ]
+ )
+
+ migration.up()
+ instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
+ welcome_config = ConfigDB.get_by_params(%{group: :pleroma, key: :welcome})
+
+ assert instance_config.value == [name: "Pleroma"]
+
+ assert welcome_config.value == [
+ direct_message: %{
+ enabled: true,
+ message: "Test message",
+ sender_nickname: "jimm"
+ },
+ email: %{
+ enabled: false,
+ html: "Welcome to <%= instance_name %>",
+ sender: nil,
+ subject: "Welcome to <%= instance_name %>",
+ text: "Welcome to <%= instance_name %>"
+ }
+ ]
+ end
+
+ test "does nothing when message empty", %{migration: migration} do
+ insert(:config,
+ group: :pleroma,
+ key: :instance,
+ value: [
+ welcome_message: "",
+ welcome_user_nickname: "jimm",
+ name: "Pleroma"
+ ]
+ )
+
+ migration.up()
+ instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
+ refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome})
+ assert instance_config.value == [name: "Pleroma"]
+ end
+
+ test "does nothing when welcome_message not set", %{migration: migration} do
+ insert(:config,
+ group: :pleroma,
+ key: :instance,
+ value: [welcome_user_nickname: "jimm", name: "Pleroma"]
+ )
+
+ migration.up()
+ instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
+ refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome})
+ assert instance_config.value == [name: "Pleroma"]
+ end
+ end
+
+ describe "down/0" do
+ test "revert new settings to old when instance setting not exists", %{migration: migration} do
+ insert(:config,
+ group: :pleroma,
+ key: :welcome,
+ value: [
+ direct_message: %{
+ enabled: true,
+ message: "Test message",
+ sender_nickname: "jimm"
+ },
+ email: %{
+ enabled: false,
+ html: "Welcome to <%= instance_name %>",
+ sender: nil,
+ subject: "Welcome to <%= instance_name %>",
+ text: "Welcome to <%= instance_name %>"
+ }
+ ]
+ )
+
+ migration.down()
+
+ refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome})
+ instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
+
+ assert instance_config.value == [
+ welcome_user_nickname: "jimm",
+ welcome_message: "Test message"
+ ]
+ end
+
+ test "revert new settings to old when instance setting exists", %{migration: migration} do
+ insert(:config, group: :pleroma, key: :instance, value: [name: "Pleroma App"])
+
+ insert(:config,
+ group: :pleroma,
+ key: :welcome,
+ value: [
+ direct_message: %{
+ enabled: true,
+ message: "Test message",
+ sender_nickname: "jimm"
+ },
+ email: %{
+ enabled: false,
+ html: "Welcome to <%= instance_name %>",
+ sender: nil,
+ subject: "Welcome to <%= instance_name %>",
+ text: "Welcome to <%= instance_name %>"
+ }
+ ]
+ )
+
+ migration.down()
+
+ refute ConfigDB.get_by_params(%{group: :pleroma, key: :welcome})
+ instance_config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
+
+ assert instance_config.value == [
+ name: "Pleroma App",
+ welcome_user_nickname: "jimm",
+ welcome_message: "Test message"
+ ]
+ end
+ end
+end
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FrontendStaticPlugTest do
+ use Pleroma.Web.ConnCase
+
+ @dir "test/tmp/instance_static"
+
+ setup do
+ File.mkdir_p!(@dir)
+ on_exit(fn -> File.rm_rf(@dir) end)
+ end
+
+ setup do: clear_config([:instance, :static_dir], @dir)
+
+ test "overrides existing static files", %{conn: conn} do
+ name = "pelmora"
+ ref = "uguu"
+
+ clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+ path = "#{@dir}/frontends/#{name}/#{ref}"
+
+ File.mkdir_p!(path)
+ File.write!("#{path}/index.html", "from frontend plug")
+
+ index = get(conn, "/")
+ assert html_response(index, 200) == "from frontend plug"
+ end
+end
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.RuntimeStaticPlugTest do
+defmodule Pleroma.Web.InstanceStaticPlugTest do
use Pleroma.Web.ConnCase
@dir "test/tmp/instance_static"
assert html_response(index, 200) == "hello world"
end
+ test "also overrides frontend files", %{conn: conn} do
+ name = "pelmora"
+ ref = "uguu"
+
+ clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+
+ bundled_index = get(conn, "/")
+ refute html_response(bundled_index, 200) == "from frontend plug"
+
+ path = "#{@dir}/frontends/#{name}/#{ref}"
+ File.mkdir_p!(path)
+ File.write!("#{path}/index.html", "from frontend plug")
+
+ index = get(conn, "/")
+ assert html_response(index, 200) == "from frontend plug"
+
+ File.write!(@dir <> "/index.html", "from instance static")
+
+ index = get(conn, "/")
+ assert html_response(index, 200) == "from instance static"
+ end
+
test "overrides any file in static/static" do
bundled_index = get(build_conn(), "/static/terms-of-service.html")
end
end
+ def require_migration(migration_name) do
+ [{module, _}] = Code.require_file("#{migration_name}.exs", "priv/repo/migrations")
+ {:ok, %{migration: module}}
+ end
+
defmacro __using__(_opts) do
quote do
import Pleroma.Tests.Helpers,
autofollowed_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
- welcome_user_nickname: nil,
- welcome_message: nil,
max_report_comment_size: 1000,
safe_dm_mentions: false,
healthcheck: false,
end
assert file ==
- "#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n welcome_user_nickname: nil,\n welcome_message: nil,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n"
+ "#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n"
end
end
end
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.WelcomeEmailTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Config
+ alias Pleroma.Tests.ObanHelpers
+ alias Pleroma.User.WelcomeEmail
+
+ import Pleroma.Factory
+ import Swoosh.TestAssertions
+
+ setup do: clear_config([:welcome])
+
+ describe "send_email/1" do
+ test "send a welcome email" do
+ user = insert(:user, name: "Jimm")
+
+ Config.put([:welcome, :email, :enabled], true)
+ Config.put([:welcome, :email, :sender], "welcome@pleroma.app")
+
+ Config.put(
+ [:welcome, :email, :subject],
+ "Hello, welcome to pleroma: <%= instance_name %>"
+ )
+
+ Config.put(
+ [:welcome, :email, :html],
+ "<h1>Hello <%= user.name %>.</h1> <p>Welcome to <%= instance_name %></p>"
+ )
+
+ instance_name = Config.get([:instance, :name])
+
+ {:ok, _job} = WelcomeEmail.send_email(user)
+
+ ObanHelpers.perform_all()
+
+ assert_email_sent(
+ from: {instance_name, "welcome@pleroma.app"},
+ to: {user.name, user.email},
+ subject: "Hello, welcome to pleroma: #{instance_name}",
+ html_body: "<h1>Hello #{user.name}.</h1> <p>Welcome to #{instance_name}</p>"
+ )
+
+ Config.put([:welcome, :email, :sender], {"Pleroma App", "welcome@pleroma.app"})
+
+ {:ok, _job} = WelcomeEmail.send_email(user)
+
+ ObanHelpers.perform_all()
+
+ assert_email_sent(
+ from: {"Pleroma App", "welcome@pleroma.app"},
+ to: {user.name, user.email},
+ subject: "Hello, welcome to pleroma: #{instance_name}",
+ html_body: "<h1>Hello #{user.name}.</h1> <p>Welcome to #{instance_name}</p>"
+ )
+ end
+ end
+end
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.WelcomeMessageTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Config
+ alias Pleroma.User.WelcomeMessage
+
+ import Pleroma.Factory
+
+ setup do: clear_config([:welcome])
+
+ describe "post_message/1" do
+ test "send a direct welcome message" do
+ welcome_user = insert(:user)
+ user = insert(:user, name: "Jimm")
+
+ Config.put([:welcome, :direct_message, :enabled], true)
+ Config.put([:welcome, :direct_message, :sender_nickname], welcome_user.nickname)
+
+ Config.put(
+ [:welcome, :direct_message, :message],
+ "Hello. Welcome to Pleroma"
+ )
+
+ {:ok, %Pleroma.Activity{} = activity} = WelcomeMessage.post_message(user)
+ assert user.ap_id in activity.recipients
+ assert activity.data["directMessage"] == true
+ assert Pleroma.Object.normalize(activity).data["content"] =~ "Hello. Welcome to Pleroma"
+ end
+ end
+end
import Pleroma.Factory
import ExUnit.CaptureLog
+ import Swoosh.TestAssertions
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
password_confirmation: "test",
email: "email@example.com"
}
+
setup do: clear_config([:instance, :autofollowed_nicknames])
- setup do: clear_config([:instance, :welcome_message])
- setup do: clear_config([:instance, :welcome_user_nickname])
+ setup do: clear_config([:welcome])
+ setup do: clear_config([:instance, :account_activation_required])
test "it autofollows accounts that are set for it" do
user = insert(:user)
test "it sends a welcome message if it is set" do
welcome_user = insert(:user)
+ Pleroma.Config.put([:welcome, :direct_message, :enabled], true)
+ Pleroma.Config.put([:welcome, :direct_message, :sender_nickname], welcome_user.nickname)
+ Pleroma.Config.put([:welcome, :direct_message, :message], "Hello, this is a cool site")
+
+ Pleroma.Config.put([:welcome, :email, :enabled], true)
+ Pleroma.Config.put([:welcome, :email, :sender], welcome_user.email)
+
+ Pleroma.Config.put(
+ [:welcome, :email, :subject],
+ "Hello, welcome to cool site: <%= instance_name %>"
+ )
- Pleroma.Config.put([:instance, :welcome_user_nickname], welcome_user.nickname)
- Pleroma.Config.put([:instance, :welcome_message], "Hello, this is a cool site")
+ instance_name = Pleroma.Config.get([:instance, :name])
cng = User.register_changeset(%User{}, @full_user_data)
{:ok, registered_user} = User.register(cng)
+ ObanHelpers.perform_all()
activity = Repo.one(Pleroma.Activity)
assert registered_user.ap_id in activity.recipients
assert Object.normalize(activity).data["content"] =~ "cool site"
assert activity.actor == welcome_user.ap_id
+
+ assert_email_sent(
+ from: {instance_name, welcome_user.email},
+ to: {registered_user.name, registered_user.email},
+ subject: "Hello, welcome to cool site: #{instance_name}",
+ html_body: "Welcome to #{instance_name}"
+ )
end
- setup do: clear_config([:instance, :account_activation_required])
+ test "it sends a confirm email" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+
+ cng = User.register_changeset(%User{}, @full_user_data)
+ {:ok, registered_user} = User.register(cng)
+ ObanHelpers.perform_all()
+ assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(registered_user))
+ end
test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
Pleroma.Config.put([:instance, :account_activation_required], true)
end
end
+ describe "user registration, with :account_approval_required" do
+ @full_user_data %{
+ bio: "A guy",
+ name: "my name",
+ nickname: "nick",
+ password: "test",
+ password_confirmation: "test",
+ email: "email@example.com",
+ registration_reason: "I'm a cool guy :)"
+ }
+ setup do: clear_config([:instance, :account_approval_required], true)
+
+ test "it creates unapproved user" do
+ changeset = User.register_changeset(%User{}, @full_user_data)
+ assert changeset.valid?
+
+ {:ok, user} = Repo.insert(changeset)
+
+ assert user.approval_pending
+ assert user.registration_reason == "I'm a cool guy :)"
+ end
+
+ test "it restricts length of registration reason" do
+ reason_limit = Pleroma.Config.get([:instance, :registration_reason_length])
+
+ assert is_integer(reason_limit)
+
+ params =
+ @full_user_data
+ |> Map.put(
+ :registration_reason,
+ "Quia et nesciunt dolores numquam ipsam nisi sapiente soluta. Ullam repudiandae nisi quam porro officiis officiis ad. Consequatur animi velit ex quia. Odit voluptatem perferendis quia ut nisi. Dignissimos sit soluta atque aliquid dolorem ut dolorum ut. Labore voluptates iste iusto amet voluptatum earum. Ad fugit illum nam eos ut nemo. Pariatur ea fuga non aspernatur. Dignissimos debitis officia corporis est nisi ab et. Atque itaque alias eius voluptas minus. Accusamus numquam tempore occaecati in."
+ )
+
+ changeset = User.register_changeset(%User{}, params)
+
+ refute changeset.valid?
+ end
+ end
+
describe "get_or_fetch/1" do
test "gets an existing user by nickname" do
user = insert(:user)
end
end
+ describe "approve" do
+ test "approves a user" do
+ user = insert(:user, approval_pending: true)
+ assert true == user.approval_pending
+ {:ok, user} = User.approve(user)
+ assert false == user.approval_pending
+ end
+
+ test "approves a list of users" do
+ unapproved_users = [
+ insert(:user, approval_pending: true),
+ insert(:user, approval_pending: true),
+ insert(:user, approval_pending: true)
+ ]
+
+ {:ok, users} = User.approve(unapproved_users)
+
+ assert Enum.count(users) == 3
+
+ Enum.each(users, fn user ->
+ assert false == user.approval_pending
+ end)
+ end
+ end
+
describe "delete" do
setup do
{:ok, user} = insert(:user) |> User.set_cache()
end
end
+ test "delete/1 when approval is pending deletes the user" do
+ user = insert(:user, approval_pending: true)
+ {:ok, user: user}
+
+ {:ok, job} = User.delete(user)
+ {:ok, _} = ObanHelpers.perform(job)
+
+ refute User.get_cached_by_id(user.id)
+ refute User.get_by_id(user.id)
+ end
+
test "get_public_key_for_ap_id fetches a user that's not in the db" do
assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
end
user = insert(:user, local: true, confirmation_pending: false, deactivated: true)
assert User.account_status(user) == :deactivated
end
+
+ test "returns :approval_pending for unapproved user" do
+ user = insert(:user, local: true, approval_pending: true)
+ assert User.account_status(user) == :approval_pending
+
+ user = insert(:user, local: true, confirmation_pending: true, approval_pending: true)
+ assert User.account_status(user) == :approval_pending
+ end
end
describe "superuser?/1" do
"id" => activity_ap_id,
"content" => content,
"published" => activity_with_object.object.data["published"],
- "actor" => AccountView.render("show.json", %{user: target_account})
+ "actor" =>
+ AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
}
assert %Activity{
assert {:ok, res} = EnsureRePrepended.filter(message)
assert res == message
end
+
+ test "it skips if the object is only a reference" do
+ message = %{
+ "type" => "Create",
+ "object" => "somereference"
+ }
+
+ assert {:ok, res} = EnsureRePrepended.filter(message)
+ assert res == message
+ end
end
end
:ok
end
+ test "when given an `object_data` in meta, Federation will receive a the original activity with the `object` field set to this embedded object" do
+ activity = insert(:note_activity)
+ object = %{"id" => "1", "type" => "Love"}
+ meta = [local: true, object_data: object]
+
+ activity_with_object = %{activity | data: Map.put(activity.data, "object", object)}
+
+ with_mocks([
+ {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
+ {
+ Pleroma.Web.ActivityPub.MRF,
+ [],
+ [filter: fn o -> {:ok, o} end]
+ },
+ {
+ Pleroma.Web.ActivityPub.ActivityPub,
+ [],
+ [persist: fn o, m -> {:ok, o, m} end]
+ },
+ {
+ Pleroma.Web.ActivityPub.SideEffects,
+ [],
+ [
+ handle: fn o, m -> {:ok, o, m} end,
+ handle_after_transaction: fn m -> m end
+ ]
+ },
+ {
+ Pleroma.Web.Federator,
+ [],
+ [publish: fn _o -> :ok end]
+ }
+ ]) do
+ assert {:ok, ^activity, ^meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
+
+ assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
+ assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
+ assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
+ assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
+ refute called(Pleroma.Web.Federator.publish(activity))
+ assert_called(Pleroma.Web.Federator.publish(activity_with_object))
+ end
+ end
+
test "it goes through validation, filtering, persisting, side effects and federation for local activities" do
activity = insert(:note_activity)
meta = [local: true]
}
end
- test "deletes the original block", %{block_undo: block_undo, block: block} do
- {:ok, _block_undo, _} = SideEffects.handle(block_undo)
+ test "deletes the original block", %{
+ block_undo: block_undo,
+ block: block
+ } do
+ {:ok, _block_undo, _meta} = SideEffects.handle(block_undo)
+
refute Activity.get_by_id(block.id)
end
assert capture_log(fn ->
{:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
- end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
+ end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
end
test "it works for incoming notices" do
"id" => activity.data["id"],
"content" => "test post",
"published" => object.data["published"],
- "actor" => AccountView.render("show.json", %{user: user})
+ "actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true})
}
message = %{
"id" => activity_ap_id,
"content" => content,
"published" => activity.object.data["published"],
- "actor" => AccountView.render("show.json", %{user: target_account})
+ "actor" =>
+ AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
}
assert %{
import ExUnit.CaptureLog
import Mock
import Pleroma.Factory
+ import Swoosh.TestAssertions
alias Pleroma.Activity
alias Pleroma.Config
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
assert expected == json_response(conn, 200)
describe "GET /api/pleroma/admin/users" do
test "renders users array for the first page", %{conn: conn, admin: admin} do
user = insert(:user, local: false, tags: ["foo", "bar"])
+ user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude")
+
conn = get(conn, "/api/pleroma/admin/users?page=1")
users =
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false,
- "url" => admin.ap_id
+ "approval_pending" => false,
+ "url" => admin.ap_id,
+ "registration_reason" => nil
},
%{
"deactivated" => user.deactivated,
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
+ },
+ %{
+ "deactivated" => user2.deactivated,
+ "id" => user2.id,
+ "nickname" => user2.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user2.name || user2.nickname),
+ "confirmation_pending" => false,
+ "approval_pending" => true,
+ "url" => user2.ap_id,
+ "registration_reason" => "I'm a chill dude"
}
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{
- "count" => 2,
+ "count" => 3,
"page_size" => 50,
"users" => users
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user2.name || user2.nickname),
"confirmation_pending" => false,
- "url" => user2.ap_id
+ "approval_pending" => false,
+ "url" => user2.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
},
%{
"deactivated" => admin.deactivated,
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false,
- "url" => admin.ap_id
+ "approval_pending" => false,
+ "url" => admin.ap_id,
+ "registration_reason" => nil
},
%{
"deactivated" => false,
"avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
"confirmation_pending" => false,
- "url" => old_admin.ap_id
+ "approval_pending" => false,
+ "url" => old_admin.ap_id,
+ "registration_reason" => nil
}
]
|> Enum.sort_by(& &1["nickname"])
}
end
+ test "only unapproved users", %{conn: conn} do
+ user =
+ insert(:user,
+ nickname: "sadboy",
+ approval_pending: true,
+ registration_reason: "Plz let me in!"
+ )
+
+ insert(:user, nickname: "happyboy", approval_pending: false)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=need_approval")
+
+ users =
+ [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false,
+ "approval_pending" => true,
+ "url" => user.ap_id,
+ "registration_reason" => "Plz let me in!"
+ }
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
test "load only admins", %{conn: conn, admin: admin} do
second_admin = insert(:user, is_admin: true)
insert(:user)
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false,
- "url" => admin.ap_id
+ "approval_pending" => false,
+ "url" => admin.ap_id,
+ "registration_reason" => nil
},
%{
"deactivated" => false,
"avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
"confirmation_pending" => false,
- "url" => second_admin.ap_id
+ "approval_pending" => false,
+ "url" => second_admin.ap_id,
+ "registration_reason" => nil
}
]
|> Enum.sort_by(& &1["nickname"])
"avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
"confirmation_pending" => false,
- "url" => moderator.ap_id
+ "approval_pending" => false,
+ "url" => moderator.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(user1) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user1.name || user1.nickname),
"confirmation_pending" => false,
- "url" => user1.ap_id
+ "approval_pending" => false,
+ "url" => user1.ap_id,
+ "registration_reason" => nil
},
%{
"deactivated" => false,
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user2.name || user2.nickname),
"confirmation_pending" => false,
- "url" => user2.ap_id
+ "approval_pending" => false,
+ "url" => user2.ap_id,
+ "registration_reason" => nil
}
]
|> Enum.sort_by(& &1["nickname"])
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
]
}
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false,
- "url" => admin.ap_id
+ "approval_pending" => false,
+ "url" => admin.ap_id,
+ "registration_reason" => nil
}
]
}
"@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
end
+ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
+ user_one = insert(:user, approval_pending: true)
+ user_two = insert(:user, approval_pending: true)
+
+ conn =
+ patch(
+ conn,
+ "/api/pleroma/admin/users/approve",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
user = insert(:user)
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false,
- "url" => user.ap_id
+ "approval_pending" => false,
+ "url" => user.ap_id,
+ "registration_reason" => nil
}
log_entry = Repo.one(ModerationLog)
"@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
second_user.nickname
}"
+
+ ObanHelpers.perform_all()
+ assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(first_user))
end
end
assert total == 3
assert count == 1
end
+
+ test "it returns unapproved user" do
+ unapproved = insert(:user, approval_pending: true)
+ insert(:user)
+ insert(:user)
+
+ {:ok, _results, total} = Search.user()
+ {:ok, [^unapproved], count} = Search.user(%{need_approval: true})
+ assert total == 3
+ assert count == 1
+ end
end
end
defmodule Pleroma.Web.AdminAPI.ReportViewTest do
use Pleroma.DataCase
+
import Pleroma.Factory
+
+ alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI
alias Pleroma.Web.MastodonAPI.StatusView
test "renders a report" do
content: nil,
actor:
Map.merge(
- AccountView.render("show.json", %{user: user}),
- Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})
+ MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
+ AdminAPI.AccountView.render("show.json", %{user: user})
),
account:
Map.merge(
- AccountView.render("show.json", %{user: other_user}),
- Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})
+ MastodonAPI.AccountView.render("show.json", %{
+ user: other_user,
+ skip_visibility_check: true
+ }),
+ AdminAPI.AccountView.render("show.json", %{user: other_user})
),
statuses: [],
notes: [],
content: nil,
actor:
Map.merge(
- AccountView.render("show.json", %{user: user}),
- Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})
+ MastodonAPI.AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
+ AdminAPI.AccountView.render("show.json", %{user: user})
),
account:
Map.merge(
- AccountView.render("show.json", %{user: other_user}),
- Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})
+ MastodonAPI.AccountView.render("show.json", %{
+ user: other_user,
+ skip_visibility_check: true
+ }),
+ AdminAPI.AccountView.render("show.json", %{user: other_user})
),
statuses: [StatusView.render("show.json", %{activity: activity})],
state: "open",
user = insert(:user)
other_user = insert(:user)
- {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
- {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+ clear_config([:instance, :federating], true)
+
+ with_mock Pleroma.Web.Federator,
+ publish: fn _ -> nil end do
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
+ {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
+
+ {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
- {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
+ assert unreaction.data["type"] == "Undo"
+ assert unreaction.data["object"] == reaction.data["id"]
+ assert unreaction.local
- assert unreaction.data["type"] == "Undo"
- assert unreaction.data["object"] == reaction.data["id"]
- assert unreaction.local
+ # On federation, it contains the undone (and deleted) object
+ unreaction_with_object = %{
+ unreaction
+ | data: Map.put(unreaction.data, "object", reaction.data)
+ }
+
+ assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
+ end
end
test "repeating a status" do
assert activity_titles == ['public', 'unlisted']
end
+
+ test "returns 404 when the user is remote", %{conn: conn} do
+ user = insert(:user, local: false)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "test"})
+
+ assert conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, user.nickname))
+ |> response(404)
+ end
end
# Note: see ActivityPubControllerTest for JSON format tests
end
setup do: clear_config([:instance, :account_activation_required])
+ setup do: clear_config([:instance, :account_approval_required])
test "Account registration via Application", %{conn: conn} do
conn =
assert token_from_db.user.confirmation_pending
end
+ test "Account registration via app with account_approval_required", %{conn: conn} do
+ Pleroma.Config.put([:instance, :account_approval_required], true)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/apps", %{
+ client_name: "client_name",
+ redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
+ scopes: "read, write, follow"
+ })
+
+ assert %{
+ "client_id" => client_id,
+ "client_secret" => client_secret,
+ "id" => _,
+ "name" => "client_name",
+ "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+ "vapid_key" => _,
+ "website" => nil
+ } = json_response_and_validate_schema(conn, 200)
+
+ conn =
+ post(conn, "/oauth/token", %{
+ grant_type: "client_credentials",
+ client_id: client_id,
+ client_secret: client_secret
+ })
+
+ assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
+ json_response(conn, 200)
+
+ assert token
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ assert refresh
+ assert scope == "read write follow"
+
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_header("authorization", "Bearer " <> token)
+ |> post("/api/v1/accounts", %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ bio: "Test Bio",
+ agreement: true,
+ reason: "I'm a cool dude, bro"
+ })
+
+ %{
+ "access_token" => token,
+ "created_at" => _created_at,
+ "scope" => ^scope,
+ "token_type" => "Bearer"
+ } = json_response_and_validate_schema(conn, 200)
+
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ token_from_db = Repo.preload(token_from_db, :user)
+ assert token_from_db.user
+
+ assert token_from_db.user.confirmation_pending
+ assert token_from_db.user.approval_pending
+
+ assert token_from_db.user.registration_reason == "I'm a cool dude, bro"
+ end
+
test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
_user = insert(:user, email: "lain@example.org")
app_token = insert(:oauth_token, user: nil)
refute User.blocks?(user, other_user)
end
+ test "blocking a domain via query params" do
+ %{user: user, conn: conn} = oauth_access(["write:blocks"])
+ other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
+
+ ret_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/domain_blocks?domain=dogwhistle.zone")
+
+ assert %{} == json_response_and_validate_schema(ret_conn, 200)
+ user = User.get_cached_by_ap_id(user.ap_id)
+ assert User.blocks?(user, other_user)
+ end
+
+ test "unblocking a domain via query params" do
+ %{user: user, conn: conn} = oauth_access(["write:blocks"])
+ other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
+
+ User.block_domain(user, "dogwhistle.zone")
+ user = refresh_record(user)
+ assert User.blocks?(user, other_user)
+
+ ret_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> delete("/api/v1/domain_blocks?domain=dogwhistle.zone")
+
+ assert %{} == json_response_and_validate_schema(ret_conn, 200)
+ user = User.get_cached_by_ap_id(user.ap_id)
+ refute User.blocks?(user, other_user)
+ end
+
test "getting a list of domain blocks" do
%{user: user, conn: conn} = oauth_access(["read:blocks"])
"thumbnail" => _,
"languages" => _,
"registrations" => _,
+ "approval_required" => _,
"poll_limits" => _,
"upload_limit" => _,
"avatar_upload_limit" => _,
}
}
- assert expected == AccountView.render("show.json", %{user: user})
+ assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true})
end
test "Favicon is nil when :instances_favicons is disabled" do
favicon:
"https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png"
}
- } = AccountView.render("show.json", %{user: user})
+ } = AccountView.render("show.json", %{user: user, skip_visibility_check: true})
Config.put([:instances_favicons, :enabled], false)
- assert %{pleroma: %{favicon: nil}} = AccountView.render("show.json", %{user: user})
+ assert %{pleroma: %{favicon: nil}} =
+ AccountView.render("show.json", %{user: user, skip_visibility_check: true})
end
test "Represent the user account for the account owner" do
}
}
- assert expected == AccountView.render("show.json", %{user: user})
+ assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true})
end
test "Represent a Funkwhale channel" do
"https://channels.tests.funkwhale.audio/federation/actors/compositions"
)
- assert represented = AccountView.render("show.json", %{user: user})
+ assert represented =
+ AccountView.render("show.json", %{user: user, skip_visibility_check: true})
+
assert represented.acct == "compositions@channels.tests.funkwhale.audio"
assert represented.url == "https://channels.tests.funkwhale.audio/channels/compositions"
end
assert expected == AccountView.render("mention.json", %{user: user})
end
+ test "demands :for or :skip_visibility_check option for account rendering" do
+ clear_config([:restrict_unauthenticated, :profiles, :local], false)
+
+ user = insert(:user)
+ user_id = user.id
+
+ assert %{id: ^user_id} = AccountView.render("show.json", %{user: user, for: nil})
+ assert %{id: ^user_id} = AccountView.render("show.json", %{user: user, for: user})
+
+ assert %{id: ^user_id} =
+ AccountView.render("show.json", %{user: user, skip_visibility_check: true})
+
+ assert_raise RuntimeError, ~r/:skip_visibility_check or :for option is required/, fn ->
+ AccountView.render("show.json", %{user: user})
+ end
+ end
+
describe "relationship" do
defp test_relationship_rendering(user, other_user, expected_result) do
opts = %{user: user, target: other_user, relationships: nil}
assert result.pleroma.settings_store == %{:fe => "test"}
- result = AccountView.render("show.json", %{user: user, with_pleroma_settings: true})
+ result = AccountView.render("show.json", %{user: user, for: nil, with_pleroma_settings: true})
assert result.pleroma[:settings_store] == nil
result = AccountView.render("show.json", %{user: user, for: user})
test "doesn't sanitize display names" do
user = insert(:user, name: "<marquee> username </marquee>")
- result = AccountView.render("show.json", %{user: user})
+ result = AccountView.render("show.json", %{user: user, skip_visibility_check: true})
assert result.display_name == "<marquee> username </marquee>"
end
test "never display nil user follow counts" do
user = insert(:user, following_count: 0, follower_count: 0)
- result = AccountView.render("show.json", %{user: user})
+ result = AccountView.render("show.json", %{user: user, skip_visibility_check: true})
assert result.following_count == 0
assert result.followers_count == 0
followers_count: 0,
following_count: 0,
pleroma: %{hide_follows_count: true, hide_followers_count: true}
- } = AccountView.render("show.json", %{user: user})
+ } = AccountView.render("show.json", %{user: user, skip_visibility_check: true})
end
test "shows when follows/followers are hidden" do
followers_count: 1,
following_count: 1,
pleroma: %{hide_follows: true, hide_followers: true}
- } = AccountView.render("show.json", %{user: user})
+ } = AccountView.render("show.json", %{user: user, skip_visibility_check: true})
end
test "shows actual follower/following count to the account owner" do
emoji: %{"joker_smile" => "https://evil.website/society.png"}
)
- AccountView.render("show.json", %{user: user})
+ AccountView.render("show.json", %{user: user, skip_visibility_check: true})
|> Enum.all?(fn
{key, url} when key in [:avatar, :avatar_static, :header, :header_static] ->
String.starts_with?(url, Pleroma.Web.base_url())
]
end
+ test "works correctly with badly formatted emojis" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "yo"})
+
+ activity
+ |> Object.normalize(false)
+ |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}})
+
+ activity = Activity.get_by_id(activity.id)
+
+ status = StatusView.render("show.json", activity: activity, for: user)
+
+ assert status[:pleroma][:emoji_reactions] == [
+ %{name: "☕", count: 1, me: true}
+ ]
+ end
+
test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
user = insert(:user)
id: to_string(note.id),
uri: object_data["id"],
url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
- account: AccountView.render("show.json", %{user: user}),
+ account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
card: nil,
key: "_test",
signing_salt: "cooldude"
]
- setup do: clear_config([:instance, :account_activation_required])
+ setup do
+ clear_config([:instance, :account_activation_required])
+ clear_config([:instance, :account_approval_required])
+ end
describe "in OAuth consumer mode, " do
setup do
}
end
+ test "rejects token exchange for valid credentials belonging to an unapproved user" do
+ password = "testpassword"
+
+ user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt(password), approval_pending: true)
+
+ refute Pleroma.User.account_status(user) == :active
+
+ app = insert(:oauth_app)
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert resp = json_response(conn, 403)
+ assert %{"error" => _} = resp
+ refute Map.has_key?(resp, "access_token")
+ end
+
test "rejects an invalid authorization code" do
app = insert(:oauth_app)
chat_1.id |> to_string()
]
end
+
+ test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
+ conn: conn,
+ user: user
+ } do
+ clear_config([:restrict_unauthenticated, :profiles, :local], true)
+ clear_config([:restrict_unauthenticated, :profiles, :remote], true)
+
+ user2 = insert(:user)
+ user3 = insert(:user, local: false)
+
+ {:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
+ {:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
+
+ result =
+ conn
+ |> get("/api/v1/pleroma/chats")
+ |> json_response_and_validate_schema(200)
+
+ account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
+ assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])
+ end
end
end
)
setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
+ setup do: clear_config([:instance, :public], true)
+
setup do
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
{:ok, %{admin_conn: admin_conn}}
end
+ test "GET /api/pleroma/emoji/packs when :public: false", %{conn: conn} do
+ Config.put([:instance, :public], false)
+ conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
+ end
+
test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert represented_chat == %{
id: "#{chat.id}",
- account: AccountView.render("show.json", user: recipient),
+ account:
+ AccountView.render("show.json", user: recipient, skip_visibility_check: true),
unread: 0,
last_message: nil,
updated_at: Utils.to_masto_date(chat.updated_at)
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
use Pleroma.DataCase
+ import Pleroma.Factory
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.UserInviteToken
- alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.TwitterAPI.TwitterAPI
setup_all do
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("lain")
-
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
+ assert user == User.get_cached_by_nickname("lain")
end
- test "it registers a new user with empty string in bio and returns the user." do
+ test "it registers a new user with empty string in bio and returns the user" do
data = %{
:username => "lain",
:email => "lain@wired.jp",
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("lain")
-
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
+ assert user == User.get_cached_by_nickname("lain")
end
test "it sends confirmation email if :account_activation_required is specified in instance config" do
)
end
+ test "it sends an admin email if :account_approval_required is specified in instance config" do
+ admin = insert(:user, is_admin: true)
+ setting = Pleroma.Config.get([:instance, :account_approval_required])
+
+ unless setting do
+ Pleroma.Config.put([:instance, :account_approval_required], true)
+ on_exit(fn -> Pleroma.Config.put([:instance, :account_approval_required], setting) end)
+ end
+
+ data = %{
+ :username => "lain",
+ :email => "lain@wired.jp",
+ :fullname => "lain iwakura",
+ :bio => "",
+ :password => "bear",
+ :confirm => "bear",
+ :reason => "I love anime"
+ }
+
+ {:ok, user} = TwitterAPI.register_user(data)
+ ObanHelpers.perform_all()
+
+ assert user.approval_pending
+
+ email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user)
+
+ notify_email = Pleroma.Config.get([:instance, :notify_email])
+ instance_name = Pleroma.Config.get([:instance, :name])
+
+ Swoosh.TestAssertions.assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {admin.name, admin.email},
+ html_body: email.html_body
+ )
+ end
+
test "it registers a new user and parses mentions in the bio" do
data1 = %{
:username => "john",
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("vinny")
- invite = Repo.get_by(UserInviteToken, token: invite.token)
+ assert user == User.get_cached_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true
-
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
end
test "returns error on invalid token" do
check_fn = fn invite ->
data = Map.put(data, :token, invite.token)
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("vinny")
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
+ assert user == User.get_cached_by_nickname("vinny")
end
{:ok, data: data, check_fn: check_fn}
}
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("vinny")
- invite = Repo.get_by(UserInviteToken, token: invite.token)
+ assert user == User.get_cached_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
-
data = %{
:username => "GrimReaper",
:email => "death@reapers.afterlife",
}
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("vinny")
- invite = Repo.get_by(UserInviteToken, token: invite.token)
+ assert user == User.get_cached_by_nickname("vinny")
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
refute invite.used
-
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
end
test "error after max uses" do
}
{:ok, user} = TwitterAPI.register_user(data)
- fetched_user = User.get_cached_by_nickname("vinny")
+ assert user == User.get_cached_by_nickname("vinny")
+
invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true
- assert AccountView.render("show.json", %{user: user}) ==
- AccountView.render("show.json", %{user: fetched_user})
-
data = %{
:username => "GrimReaper",
:email => "death@reapers.afterlife",