mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2025-04-24 05:47:41 -04:00
Merge branch 'multitenancy' into 'develop'
Allow using multiple domains for WebFinger See merge request pleroma/pleroma!3965
This commit is contained in:
commit
1325c34c9b
44 changed files with 1247 additions and 40 deletions
1
changelog.d/multitenancy.add
Normal file
1
changelog.d/multitenancy.add
Normal file
|
@ -0,0 +1 @@
|
|||
Allow using multiple domains for WebFinger
|
|
@ -261,7 +261,10 @@ config :pleroma, :instance,
|
|||
max_endorsed_users: 20,
|
||||
birthday_required: false,
|
||||
birthday_min_age: 0,
|
||||
max_media_attachments: 1_000
|
||||
max_media_attachments: 1_000,
|
||||
multitenancy: %{
|
||||
enabled: false
|
||||
}
|
||||
|
||||
config :pleroma, :welcome,
|
||||
direct_message: [
|
||||
|
@ -601,6 +604,7 @@ config :pleroma, Oban,
|
|||
],
|
||||
plugins: [{Oban.Plugins.Pruner, max_age: 900}],
|
||||
crontab: [
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.CheckDomainsResolveWorker},
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
|
||||
{"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}
|
||||
|
|
|
@ -1131,6 +1131,23 @@ config :pleroma, :config_description, [
|
|||
suggestions: [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :multitenancy,
|
||||
type: :map,
|
||||
description: "Multitenancy support",
|
||||
children: [
|
||||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "Enables allowing multiple Webfinger domains"
|
||||
},
|
||||
%{
|
||||
key: :separate_timelines,
|
||||
type: :boolean,
|
||||
description: "Only display posts from own domain on local timeline"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# How to serve multiple domains for Pleroma user identifiers
|
||||
|
||||
It is possible to use multiple domains for WebFinger identifiers. If configured, users can select from the available domains during registration. Domains can be set by instance administrator and can be marked as either public (everyone can choose it) or private (only available when admin creates a user)
|
||||
|
||||
## Configuring
|
||||
|
||||
### Configuring Pleroma
|
||||
|
||||
To enable using multiple domains, append the following to your `prod.secret.exs` or `dev.secret.exs`:
|
||||
```elixir
|
||||
config :pleroma, :instance, :multitenancy, enabled: true
|
||||
```
|
||||
|
||||
Creating, updating and deleting domains is available from the admin API.
|
||||
|
||||
### Configuring WebFinger domains
|
||||
|
||||
If you recall how webfinger queries work, the first step is to query `https://example.org/.well-known/host-meta`, which will contain an URL template.
|
||||
|
||||
Therefore, the easiest way to configure the additional domains is to redirect `/.well-known/host-meta` to the domain used by Pleroma.
|
||||
|
||||
With nginx, it would be as simple as adding:
|
||||
|
||||
```nginx
|
||||
location = /.well-known/host-meta {
|
||||
return 301 https://pleroma.example.org$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
in the additional domain's server block.
|
|
@ -1755,6 +1755,73 @@ Note that this differs from the Mastodon API variant: Mastodon API only returns
|
|||
{}
|
||||
```
|
||||
|
||||
## `GET /api/v1/pleroma/admin/domains`
|
||||
|
||||
### List of domains
|
||||
|
||||
- Response: JSON, list of domains
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"domain": "example.org",
|
||||
"public": false,
|
||||
"resolves": true,
|
||||
"last_checked_at": "2023-11-17T12:13:05"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## `POST /api/v1/pleroma/admin/domains`
|
||||
|
||||
### Create a domain
|
||||
|
||||
- Params:
|
||||
- `domain`: string, required, domain name
|
||||
- `public`: boolean, optional, defaults to false, whether it is possible to register an account under the domain by everyone
|
||||
|
||||
- Response: JSON, created announcement
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"domain": "example.org",
|
||||
"public": true,
|
||||
"resolves": false,
|
||||
"last_checked_at": null
|
||||
}
|
||||
```
|
||||
|
||||
## `POST /api/v1/pleroma/admin/domains/:id`
|
||||
|
||||
### Change domain publicity
|
||||
|
||||
- Params:
|
||||
- `public`: boolean, whether it is possible to register an account under the domain by everyone
|
||||
|
||||
- Response: JSON, updated domain
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"domain": "example.org",
|
||||
"public": false,
|
||||
"resolves": true,
|
||||
"last_checked_at": "2023-11-17T12:13:05"
|
||||
}
|
||||
```
|
||||
|
||||
## `DELETE /api/v1/pleroma/admin/domains/:id`
|
||||
|
||||
### Delete a domain
|
||||
|
||||
- Response: JSON, empty object
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
|
||||
## `GET /api/v1/pleroma/admin/rules`
|
||||
|
||||
|
|
|
@ -312,6 +312,7 @@ Has these additional parameters (which are the same as in Pleroma-API):
|
|||
- `captcha_answer_data`: optional, contains provider-specific captcha data
|
||||
- `token`: invite token required when the registrations aren't public.
|
||||
- `language`: optional, user's preferred language for receiving emails (digest, confirmation, etc.), default to the language set in the `userLanguage` cookies or `Accept-Language` header.
|
||||
- `domain`: optional, domain id, if multitenancy is enabled.
|
||||
|
||||
## Instance
|
||||
|
||||
|
|
|
@ -173,7 +173,8 @@ defmodule Pleroma.Application do
|
|||
),
|
||||
build_cachex("rel_me", limit: 2500),
|
||||
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5_000),
|
||||
build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000)
|
||||
build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000),
|
||||
build_cachex("domain", limit: 2500)
|
||||
]
|
||||
end
|
||||
|
||||
|
|
81
lib/pleroma/domain.ex
Normal file
81
lib/pleroma/domain.ex
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
defmodule Pleroma.Domain do
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Repo
|
||||
|
||||
schema "domains" do
|
||||
field(:domain, :string, default: "")
|
||||
field(:public, :boolean, default: false)
|
||||
field(:resolves, :boolean, default: false)
|
||||
field(:last_checked_at, :naive_datetime)
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
def changeset(%__MODULE__{} = domain, params \\ %{}) do
|
||||
domain
|
||||
|> cast(params, [:domain, :public])
|
||||
|> validate_required([:domain])
|
||||
|> update_change(:domain, &String.downcase/1)
|
||||
|> unique_constraint(:domain)
|
||||
end
|
||||
|
||||
def update_changeset(%__MODULE__{} = domain, params \\ %{}) do
|
||||
domain
|
||||
|> cast(params, [:public])
|
||||
end
|
||||
|
||||
def update_state_changeset(%__MODULE__{} = domain, resolves) do
|
||||
domain
|
||||
|> cast(
|
||||
%{
|
||||
resolves: resolves,
|
||||
last_checked_at: NaiveDateTime.utc_now()
|
||||
},
|
||||
[:resolves, :last_checked_at]
|
||||
)
|
||||
end
|
||||
|
||||
def list do
|
||||
__MODULE__
|
||||
|> order_by(asc: :id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get(id), do: Repo.get(__MODULE__, id)
|
||||
|
||||
def get_by_name(domain) do
|
||||
__MODULE__
|
||||
|> where(domain: ^domain)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def create(params) do
|
||||
%__MODULE__{}
|
||||
|> changeset(params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def update(params, id) do
|
||||
get(id)
|
||||
|> update_changeset(params)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def delete(id) do
|
||||
get(id)
|
||||
|> Repo.delete()
|
||||
end
|
||||
|
||||
def cached_list do
|
||||
@cachex.fetch!(:domain_cache, "domains_list", fn _ -> list() end)
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.FollowingRelationship
|
||||
|
@ -163,6 +164,8 @@ defmodule Pleroma.User do
|
|||
field(:show_birthday, :boolean, default: false)
|
||||
field(:language, :string)
|
||||
|
||||
belongs_to(:domain, Domain)
|
||||
|
||||
embeds_one(
|
||||
:notification_settings,
|
||||
Pleroma.User.NotificationSetting,
|
||||
|
@ -860,16 +863,18 @@ defmodule Pleroma.User do
|
|||
:accepts_chat_messages,
|
||||
:registration_reason,
|
||||
:birthday,
|
||||
:language
|
||||
:language,
|
||||
:domain_id
|
||||
])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_email_not_in_blacklisted_domain(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_not_restricted_nickname(:nickname)
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> fix_nickname(Map.get(params, :domain_id), opts[:from_admin])
|
||||
|> validate_not_restricted_nickname(:nickname)
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> validate_length(:registration_reason, max: reason_limit)
|
||||
|
@ -883,6 +888,26 @@ defmodule Pleroma.User do
|
|||
|> put_private_key()
|
||||
end
|
||||
|
||||
defp fix_nickname(changeset, domain_id, from_admin) when not is_nil(domain_id) do
|
||||
with {:domain, domain} <- {:domain, Domain.get(domain_id)},
|
||||
{:domain_allowed, true} <- {:domain_allowed, from_admin || domain.public} do
|
||||
nickname = get_field(changeset, :nickname)
|
||||
|
||||
changeset
|
||||
|> put_change(:nickname, nickname <> "@" <> domain.domain)
|
||||
|> put_change(:domain, domain)
|
||||
else
|
||||
{:domain_allowed, false} ->
|
||||
changeset
|
||||
|> add_error(:domain, "not allowed to use this domain")
|
||||
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp fix_nickname(changeset, _, _), do: changeset
|
||||
|
||||
def validate_not_restricted_nickname(changeset, field) do
|
||||
validate_change(changeset, field, fn _, value ->
|
||||
valid? =
|
||||
|
@ -943,7 +968,9 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defp put_ap_id(changeset) do
|
||||
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
|
||||
nickname = get_field(changeset, :nickname)
|
||||
ap_id = ap_id(%User{nickname: nickname})
|
||||
|
||||
put_change(changeset, :ap_id, ap_id)
|
||||
end
|
||||
|
||||
|
@ -1356,6 +1383,13 @@ defmodule Pleroma.User do
|
|||
restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
|
||||
get_cached_by_nickname(nickname_or_id)
|
||||
|
||||
String.contains?(nickname_or_id, "@") ->
|
||||
with %User{local: true} = user <- get_cached_by_nickname(nickname_or_id) do
|
||||
user
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -955,6 +955,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_local(query, _), do: query
|
||||
|
||||
defp restrict_domain(query, %{domain_id: 0}) do
|
||||
query
|
||||
|> join(:inner, [activity], u in User,
|
||||
as: :domain_user,
|
||||
on: activity.actor == u.ap_id and is_nil(u.domain_id)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_domain(query, %{domain_id: domain_id}) do
|
||||
query
|
||||
|> join(:inner, [activity], u in User,
|
||||
as: :domain_user,
|
||||
on: activity.actor == u.ap_id and u.domain_id == ^domain_id
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_domain(query, _), do: query
|
||||
|
||||
defp restrict_remote(query, %{remote: true}) do
|
||||
from(activity in query, where: activity.local == false)
|
||||
end
|
||||
|
@ -1443,6 +1461,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_replies(opts)
|
||||
|> restrict_since(opts)
|
||||
|> restrict_local(opts)
|
||||
|> restrict_domain(opts)
|
||||
|> restrict_remote(opts)
|
||||
|> restrict_actor(opts)
|
||||
|> restrict_type(opts)
|
||||
|
|
|
@ -52,6 +52,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
when action in [:activity, :object]
|
||||
)
|
||||
|
||||
plug(
|
||||
Pleroma.Web.Plugs.SetDomainPlug
|
||||
when action in [:following, :followers, :pinned, :inbox, :outbox, :update_outbox]
|
||||
)
|
||||
|
||||
plug(
|
||||
Pleroma.Web.Plugs.SetNicknameWithDomainPlug
|
||||
when action in [:following, :followers, :pinned, :inbox, :outbox, :update_outbox]
|
||||
)
|
||||
|
||||
plug(:log_inbox_metadata when action in [:inbox])
|
||||
plug(:set_requester_reachable when action in [:inbox])
|
||||
plug(:relay_active? when action in [:relay])
|
||||
|
@ -540,7 +550,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
with {:ok, object} <-
|
||||
ActivityPub.upload(
|
||||
file,
|
||||
actor: User.ap_id(user),
|
||||
actor: user.ap_id,
|
||||
description: Map.get(data, "description")
|
||||
) do
|
||||
Logger.debug(inspect(object))
|
||||
|
|
78
lib/pleroma/web/admin_api/controllers/domain_controller.ex
Normal file
78
lib/pleroma/web/admin_api/controllers/domain_controller.ex
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.DomainController do
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.Web.AdminAPI
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [
|
||||
json_response: 3
|
||||
]
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["admin:write"]}
|
||||
when action in [:create, :update, :delete]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index)
|
||||
|
||||
action_fallback(AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.DomainOperation
|
||||
|
||||
def index(conn, _) do
|
||||
domains = Domain.list()
|
||||
|
||||
render(conn, "index.json", domains: domains)
|
||||
end
|
||||
|
||||
def create(%{body_params: params} = conn, _) do
|
||||
with {:domain_not_used, true} <-
|
||||
{:domain_not_used, params[:domain] !== Pleroma.Web.WebFinger.host()},
|
||||
{:ok, domain} <- Domain.create(params),
|
||||
_ <- @cachex.del(:domain_cache, "domains_list") do
|
||||
Pleroma.Workers.CheckDomainResolveWorker.enqueue("check_domain_resolve", %{
|
||||
"id" => domain.id
|
||||
})
|
||||
|
||||
render(conn, "show.json", domain: domain)
|
||||
else
|
||||
{:domain_not_used, false} ->
|
||||
{:error, :invalid_domain}
|
||||
|
||||
{:error, changeset} ->
|
||||
errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
|
||||
|
||||
{:errors, errors}
|
||||
end
|
||||
end
|
||||
|
||||
def update(%{body_params: params} = conn, %{id: id}) do
|
||||
{:ok, domain} =
|
||||
params
|
||||
|> Domain.update(id)
|
||||
|
||||
@cachex.del(:domain_cache, "domains_list")
|
||||
|
||||
render(conn, "show.json", domain: domain)
|
||||
end
|
||||
|
||||
def delete(conn, %{id: id}) do
|
||||
with {:ok, _} <- Domain.delete(id),
|
||||
_ <- @cachex.del(:domain_cache, :domains_list) do
|
||||
json(conn, %{})
|
||||
else
|
||||
_ -> json_response(conn, :bad_request, "")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
|||
import Pleroma.Web.ControllerHelper,
|
||||
only: [fetch_integer_param: 3]
|
||||
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.ModerationLog
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
|
@ -150,17 +151,25 @@ defmodule Pleroma.Web.AdminAPI.UserController do
|
|||
) do
|
||||
changesets =
|
||||
users
|
||||
|> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
|
||||
|> Enum.map(fn %{nickname: nickname, email: email, password: password} = user ->
|
||||
domain_id = Map.get(user, :domain)
|
||||
|
||||
domain =
|
||||
if domain_id do
|
||||
Domain.get(domain_id)
|
||||
end
|
||||
|
||||
user_data = %{
|
||||
nickname: nickname,
|
||||
name: nickname,
|
||||
email: email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
bio: "."
|
||||
bio: ".",
|
||||
domain: domain
|
||||
}
|
||||
|
||||
User.register_changeset(%User{}, user_data, need_confirmation: false)
|
||||
User.register_changeset(%User{}, user_data, need_confirmation: false, from_admin: true)
|
||||
end)
|
||||
|> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
|
||||
Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
|
||||
|
|
35
lib/pleroma/web/admin_api/views/domain_view.ex
Normal file
35
lib/pleroma/web/admin_api/views/domain_view.ex
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.DomainView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
def render("index.json", %{domains: domains} = assigns) do
|
||||
render_many(domains, __MODULE__, "show.json", assigns |> Map.delete("domains"))
|
||||
end
|
||||
|
||||
def render("show.json", %{domain: %Domain{} = domain} = assigns) do
|
||||
%{
|
||||
id: domain.id |> to_string(),
|
||||
domain: domain.domain,
|
||||
public: domain.public
|
||||
}
|
||||
|> maybe_put_resolve_information(domain, assigns)
|
||||
end
|
||||
|
||||
defp maybe_put_resolve_information(map, _domain, %{admin: false}) do
|
||||
map
|
||||
end
|
||||
|
||||
defp maybe_put_resolve_information(map, domain, _assigns) do
|
||||
map
|
||||
|> Map.merge(%{
|
||||
resolves: domain.resolves,
|
||||
last_checked_at: Utils.to_masto_date(domain.last_checked_at)
|
||||
})
|
||||
end
|
||||
end
|
|
@ -105,7 +105,8 @@ defmodule Pleroma.Web.ApiSpec do
|
|||
"Report management",
|
||||
"Status administration",
|
||||
"User administration",
|
||||
"Announcement management"
|
||||
"Announcement management",
|
||||
"Domain managment"
|
||||
]
|
||||
},
|
||||
%{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
|
||||
|
|
|
@ -617,7 +617,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
type: :string,
|
||||
nullable: true,
|
||||
description: "User's preferred language for emails"
|
||||
}
|
||||
},
|
||||
domain: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"username" => "cofe",
|
||||
|
|
113
lib/pleroma/web/api_spec/operations/admin/domain_operation.ex
Normal file
113
lib/pleroma/web/api_spec/operations/admin/domain_operation.ex
Normal file
|
@ -0,0 +1,113 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Admin.DomainOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Domain managment"],
|
||||
summary: "Retrieve list of domains",
|
||||
operationId: "AdminAPI.DomainController.index",
|
||||
security: [%{"oAuth" => ["admin:read"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Response", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: domain()
|
||||
}),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Domain managment"],
|
||||
summary: "Create new domain",
|
||||
operationId: "AdminAPI.DomainController.create",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
parameters: admin_api_params(),
|
||||
requestBody: request_body("Parameters", create_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", domain()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Domain managment"],
|
||||
summary: "Modify existing domain",
|
||||
operationId: "AdminAPI.DomainController.update",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
parameters: [Operation.parameter(:id, :path, :string, "Domain ID")],
|
||||
requestBody: request_body("Parameters", update_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("Response", "application/json", domain()),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def delete_operation do
|
||||
%Operation{
|
||||
tags: ["Domain managment"],
|
||||
summary: "Delete domain",
|
||||
operationId: "AdminAPI.DomainController.delete",
|
||||
parameters: [Operation.parameter(:id, :path, :string, "Domain ID")],
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
responses: %{
|
||||
200 => empty_object_response(),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:domain],
|
||||
properties: %{
|
||||
domain: %Schema{type: :string},
|
||||
public: %Schema{type: :boolean, nullable: true}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp update_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
public: %Schema{type: :boolean, nullable: true}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp domain do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
domain: %Schema{type: :string},
|
||||
public: %Schema{type: :boolean},
|
||||
resolves: %Schema{type: :boolean},
|
||||
last_checked_at: %Schema{type: :string, format: "date-time", nullable: true}
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -82,7 +82,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.UserOperation do
|
|||
properties: %{
|
||||
nickname: %Schema{type: :string},
|
||||
email: %Schema{type: :string},
|
||||
password: %Schema{type: :string}
|
||||
password: %Schema{type: :string},
|
||||
domain: %Schema{type: :string, nullable: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,10 @@ defmodule Pleroma.Web.Endpoint do
|
|||
plug(Phoenix.CodeReloader)
|
||||
end
|
||||
|
||||
plug(Pleroma.Web.Plugs.TrailingFormatPlug)
|
||||
plug(Pleroma.Web.Plugs.TrailingFormatPlug,
|
||||
supported_formats: ["html", "xml", "rss", "atom", "activity+json", "json"]
|
||||
)
|
||||
|
||||
plug(Plug.RequestId)
|
||||
plug(Plug.Logger, log: :debug)
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ defmodule Pleroma.Web.Feed.UserController do
|
|||
alias Pleroma.Web.Feed.FeedView
|
||||
|
||||
plug(Pleroma.Web.Plugs.SetFormatPlug when action in [:feed_redirect])
|
||||
plug(Pleroma.Web.Plugs.SetDomainPlug when action in [:feed_redirect, :feed])
|
||||
plug(Pleroma.Web.Plugs.SetNicknameWithDomainPlug when action in [:feed_redirect, :feed])
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
|
@ -28,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
|
|||
with {:ok, object} <-
|
||||
ActivityPub.upload(
|
||||
file,
|
||||
actor: User.ap_id(user),
|
||||
actor: user.ap_id,
|
||||
description: Map.get(data, :description)
|
||||
) do
|
||||
attachment_data = Map.put(object.data, "id", object.id)
|
||||
|
@ -48,7 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
|
|||
with {:ok, object} <-
|
||||
ActivityPub.upload(
|
||||
file,
|
||||
actor: User.ap_id(user),
|
||||
actor: user.ap_id,
|
||||
description: Map.get(data, :description)
|
||||
) do
|
||||
attachment_data = Map.put(object.data, "id", object.id)
|
||||
|
|
|
@ -36,6 +36,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
when action in [:public, :hashtag]
|
||||
)
|
||||
|
||||
plug(Pleroma.Web.Plugs.SetDomainPlug when action in [:public, :hashtag])
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation
|
||||
|
||||
# GET /api/v1/timelines/home
|
||||
|
@ -120,6 +122,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> Map.put(:instance, params[:instance])
|
||||
# Restricts unfederated content to authenticated users
|
||||
|> Map.put(:includes_local_public, not is_nil(user))
|
||||
|> maybe_put_domain_id(conn)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|
||||
conn
|
||||
|
@ -137,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
render_error(conn, :unauthorized, "authorization required for timeline view")
|
||||
end
|
||||
|
||||
defp hashtag_fetching(params, user, local_only) do
|
||||
defp hashtag_fetching(conn, params, user, local_only) do
|
||||
# Note: not sanitizing tag options at this stage (may be mix-cased, have duplicates etc.)
|
||||
tags_any =
|
||||
[params[:tag], params[:any]]
|
||||
|
@ -156,6 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> Map.put(:tag, tags_any)
|
||||
|> Map.put(:tag_all, tag_all)
|
||||
|> Map.put(:tag_reject, tag_reject)
|
||||
|> maybe_put_domain_id(conn)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
end
|
||||
|
||||
|
@ -166,7 +170,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
if is_nil(user) and restrict_unauthenticated?(local_only) do
|
||||
fail_on_bad_auth(conn)
|
||||
else
|
||||
activities = hashtag_fetching(params, user, local_only)
|
||||
activities = hashtag_fetching(conn, params, user, local_only)
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|
@ -189,6 +193,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
|> Map.put(:user, user)
|
||||
|> Map.put(:muting_user, user)
|
||||
|> Map.put(:local_only, params[:local])
|
||||
|> maybe_put_domain_id(conn)
|
||||
|
||||
# we must filter the following list for the user to avoid leaking statuses the user
|
||||
# does not actually have permission to see (for more info, peruse security issue #270).
|
||||
|
@ -213,4 +218,19 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
|||
_e -> render_error(conn, :forbidden, "Error.")
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_domain_id(%{local_only: true} = params, conn) do
|
||||
separate_timelines = Config.get([:instance, :multitenancy, :separate_timelines])
|
||||
|
||||
if separate_timelines do
|
||||
domain = Map.get(conn, :domain, %{id: 0})
|
||||
|
||||
params
|
||||
|> Map.put(:domain_id, domain.id)
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_domain_id(params, _conn), do: params
|
||||
end
|
||||
|
|
|
@ -325,7 +325,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
accepts_chat_messages: user.accepts_chat_messages,
|
||||
favicon: favicon,
|
||||
avatar_description: avatar_description,
|
||||
header_description: header_description
|
||||
header_description: header_description,
|
||||
is_local: user.local
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|
|
|
@ -6,7 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.AdminAPI.DomainView
|
||||
|
||||
@mastodon_api_level "2.7.2"
|
||||
|
||||
|
@ -157,7 +159,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
"pleroma:bookmark_folders",
|
||||
if Pleroma.Language.LanguageDetector.configured?() do
|
||||
"pleroma:language_detection"
|
||||
end
|
||||
end,
|
||||
"pleroma:multitenancy"
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
@ -286,7 +289,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
birthday_required: Config.get([:instance, :birthday_required]),
|
||||
birthday_min_age: Config.get([:instance, :birthday_min_age]),
|
||||
translation: supported_languages(),
|
||||
base_urls: base_urls
|
||||
base_urls: base_urls,
|
||||
multitenancy: multitenancy()
|
||||
},
|
||||
stats: %{mau: Pleroma.User.active_user_count()},
|
||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
|
@ -337,4 +341,21 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
target_languages: target_languages
|
||||
}
|
||||
end
|
||||
|
||||
defp multitenancy do
|
||||
enabled = Config.get([:instance, :multitenancy, :enabled])
|
||||
|
||||
if enabled do
|
||||
domains =
|
||||
[%Domain{id: "", domain: Pleroma.Web.WebFinger.host(), public: true}] ++
|
||||
Domain.cached_list()
|
||||
|
||||
%{
|
||||
enabled: true,
|
||||
domains: DomainView.render("index.json", domains: domains, admin: false)
|
||||
}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
28
lib/pleroma/web/plugs/set_domain_plug.ex
Normal file
28
lib/pleroma/web/plugs/set_domain_plug.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.SetDomainPlug do
|
||||
use Pleroma.Web, :plug
|
||||
|
||||
alias Pleroma.Domain
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
@impl true
|
||||
def perform(%{host: domain} = conn, _opts) do
|
||||
with true <- Pleroma.Config.get([:instance, :multitenancy, :enabled], false),
|
||||
false <-
|
||||
domain in [
|
||||
Pleroma.Config.get([__MODULE__, :domain]),
|
||||
Pleroma.Web.Endpoint.host()
|
||||
],
|
||||
%Domain{} = domain <- Domain.get_by_name(domain) do
|
||||
Map.put(conn, :domain, domain)
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
def perform(conn, _), do: conn
|
||||
end
|
24
lib/pleroma/web/plugs/set_nickname_with_domain_plug.ex
Normal file
24
lib/pleroma/web/plugs/set_nickname_with_domain_plug.ex
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.SetNicknameWithDomainPlug do
|
||||
use Pleroma.Web, :plug
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
@impl true
|
||||
def perform(%{domain: domain, params: params} = conn, opts) do
|
||||
with key <- Keyword.get(opts, :key, "nickname"),
|
||||
nickname <- Map.get(params, key),
|
||||
false <- String.contains?(nickname, "@"),
|
||||
nickname <- nickname <> "@" <> domain.domain,
|
||||
params <- Map.put(params, "nickname", nickname) do
|
||||
Map.put(conn, :params, params)
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
def perform(conn, _), do: conn
|
||||
end
|
|
@ -1,9 +1,12 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.TrailingFormatPlug do
|
||||
@moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."
|
||||
@moduledoc """
|
||||
This plug is adapted from [`TrailingFormatPlug`](https://github.com/mschae/trailing_format_plug/blob/master/lib/trailing_format_plug.ex).
|
||||
Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."
|
||||
"""
|
||||
|
||||
@behaviour Plug
|
||||
@paths [
|
||||
|
@ -29,14 +32,53 @@ defmodule Pleroma.Web.Plugs.TrailingFormatPlug do
|
|||
]
|
||||
|
||||
def init(opts) do
|
||||
TrailingFormatPlug.init(opts)
|
||||
opts
|
||||
end
|
||||
|
||||
for path <- @paths do
|
||||
def call(%{request_path: unquote(path) <> _} = conn, opts) do
|
||||
TrailingFormatPlug.call(conn, opts)
|
||||
path = conn.path_info |> List.last() |> String.split(".") |> Enum.reverse()
|
||||
|
||||
supported_formats = Keyword.get(opts, :supported_formats, nil)
|
||||
|
||||
case path do
|
||||
[_] ->
|
||||
conn
|
||||
|
||||
[format | fragments] ->
|
||||
if supported_formats == nil || format in supported_formats do
|
||||
new_path = fragments |> Enum.reverse() |> Enum.join(".")
|
||||
path_fragments = List.replace_at(conn.path_info, -1, new_path)
|
||||
|
||||
params =
|
||||
Plug.Conn.fetch_query_params(conn).params
|
||||
|> update_params(new_path, format)
|
||||
|> Map.put("_format", format)
|
||||
|
||||
%{
|
||||
conn
|
||||
| path_info: path_fragments,
|
||||
query_params: params,
|
||||
params: params
|
||||
}
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def call(conn, _opts), do: conn
|
||||
|
||||
defp update_params(params, new_path, format) do
|
||||
wildcard = Enum.find(params, fn {_, v} -> v == "#{new_path}.#{format}" end)
|
||||
|
||||
case wildcard do
|
||||
{key, _} ->
|
||||
Map.put(params, key, new_path)
|
||||
|
||||
_ ->
|
||||
params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -309,6 +309,11 @@ defmodule Pleroma.Web.Router do
|
|||
post("/rules", RuleController, :create)
|
||||
patch("/rules/:id", RuleController, :update)
|
||||
delete("/rules/:id", RuleController, :delete)
|
||||
|
||||
get("/domains", DomainController, :index)
|
||||
post("/domains", DomainController, :create)
|
||||
patch("/domains/:id", DomainController, :update)
|
||||
delete("/domains/:id", DomainController, :delete)
|
||||
end
|
||||
|
||||
# AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
|
||||
|
|
|
@ -27,6 +27,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
:language,
|
||||
Pleroma.Web.Gettext.normalize_locale(params[:language]) || fallback_language
|
||||
)
|
||||
|> maybe_put_domain_id(params[:domain])
|
||||
|
||||
if Pleroma.Config.get([:instance, :registrations_open]) do
|
||||
create_user(params, opts)
|
||||
|
@ -64,6 +65,14 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
end
|
||||
end
|
||||
|
||||
defp maybe_put_domain_id(params, domain) do
|
||||
if Pleroma.Config.get([:instance, :multitenancy, :enabled]) do
|
||||
Map.put(params, :domain_id, domain)
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(nickname_or_email) do
|
||||
with true <- is_binary(nickname_or_email),
|
||||
%User{local: true, email: email, is_active: true} = user when is_binary(email) <-
|
||||
|
|
|
@ -33,15 +33,13 @@ defmodule Pleroma.Web.WebFinger do
|
|||
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
|
||||
regex =
|
||||
if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/
|
||||
else
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
|
||||
end
|
||||
regex = ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(?<domain>[a-z0-9A-Z_\.-]+)/
|
||||
webfinger_domain = Pleroma.Config.get([__MODULE__, :domain])
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
||||
%User{} = user <- User.get_cached_by_nickname(username) do
|
||||
with %{"username" => username, "domain" => domain} <- Regex.named_captures(regex, resource),
|
||||
nickname <-
|
||||
if(domain in [host, webfinger_domain], do: username, else: username <> "@" <> domain),
|
||||
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, represent_user(user, fmt)}
|
||||
else
|
||||
_e ->
|
||||
|
@ -70,7 +68,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
def represent_user(user, "JSON") do
|
||||
%{
|
||||
"subject" => "acct:#{user.nickname}@#{host()}",
|
||||
"subject" => get_subject(user),
|
||||
"aliases" => gather_aliases(user),
|
||||
"links" => gather_links(user)
|
||||
}
|
||||
|
@ -90,12 +88,20 @@ defmodule Pleroma.Web.WebFinger do
|
|||
:XRD,
|
||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
[
|
||||
{:Subject, "acct:#{user.nickname}@#{host()}"}
|
||||
{:Subject, get_subject(user)}
|
||||
] ++ aliases ++ links
|
||||
}
|
||||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
||||
defp get_subject(%User{nickname: nickname}) do
|
||||
if String.contains?(nickname, "@") do
|
||||
"acct:#{nickname}"
|
||||
else
|
||||
"acct:#{nickname}@#{host()}"
|
||||
end
|
||||
end
|
||||
|
||||
def host do
|
||||
Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host()
|
||||
end
|
||||
|
|
36
lib/pleroma/workers/check_domain_resolve_worker.ex
Normal file
36
lib/pleroma/workers/check_domain_resolve_worker.ex
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.CheckDomainResolveWorker do
|
||||
use Oban.Worker, queue: :background
|
||||
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.HTTP
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Job{args: %{"id" => domain_id}}) do
|
||||
domain = Domain.get(domain_id)
|
||||
|
||||
resolves =
|
||||
with {:ok, %Tesla.Env{status: status, body: hostmeta_body}} when status in 200..299 <-
|
||||
HTTP.get("https://" <> domain.domain <> "/.well-known/host-meta"),
|
||||
{:ok, template} <- WebFinger.get_template_from_xml(hostmeta_body),
|
||||
base_url <- Endpoint.url(),
|
||||
true <- template == "#{base_url}/.well-known/webfinger?resource={uri}" do
|
||||
true
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
|
||||
domain
|
||||
|> Domain.update_state_changeset(resolves)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@impl Oban.Worker
|
||||
def timeout(_job), do: :timer.seconds(5)
|
||||
end
|
34
lib/pleroma/workers/cron/check_domains_resolve_worker.ex
Normal file
34
lib/pleroma/workers/cron/check_domains_resolve_worker.ex
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.Cron.CheckDomainsResolveWorker do
|
||||
@moduledoc """
|
||||
The worker to check if alternative domains resolve correctly.
|
||||
"""
|
||||
|
||||
use Oban.Worker, queue: "check_domain_resolve"
|
||||
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_job) do
|
||||
domains =
|
||||
Domain
|
||||
|> select([d], d.id)
|
||||
|> Repo.all()
|
||||
|
||||
Enum.each(domains, fn domain_id ->
|
||||
Pleroma.Workers.CheckDomainResolveWorker.enqueue("check_domain_resolve", %{
|
||||
"id" => domain_id
|
||||
})
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
1
mix.exs
1
mix.exs
|
@ -148,7 +148,6 @@ defmodule Pleroma.Mixfile do
|
|||
{:oban, "~> 2.18.0"},
|
||||
{:gettext, "~> 0.20"},
|
||||
{:bcrypt_elixir, "~> 2.2"},
|
||||
{:trailing_format_plug, "~> 0.0.7"},
|
||||
{:fast_sanitize, "~> 0.2.0"},
|
||||
{:html_entities, "~> 0.5", override: true},
|
||||
{:calendar, "~> 1.0"},
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -143,7 +143,6 @@
|
|||
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
|
||||
"timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"},
|
||||
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
|
|
22
priv/repo/migrations/20230618190919_create_domains.exs
Normal file
22
priv/repo/migrations/20230618190919_create_domains.exs
Normal file
|
@ -0,0 +1,22 @@
|
|||
defmodule Pleroma.Repo.Migrations.CreateDomains do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists table(:domains) do
|
||||
add(:domain, :citext)
|
||||
add(:public, :boolean)
|
||||
add(:resolves, :boolean)
|
||||
add(:last_checked_at, :naive_datetime)
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create_if_not_exists(unique_index(:domains, [:domain]))
|
||||
|
||||
alter table(:users) do
|
||||
add(:domain_id, references(:domains))
|
||||
end
|
||||
|
||||
create_if_not_exists(index(:users, [:domain_id]))
|
||||
end
|
||||
end
|
|
@ -387,6 +387,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
|||
["data_migration_failed_ids"],
|
||||
["data_migrations"],
|
||||
["deliveries"],
|
||||
["domains"],
|
||||
["filters"],
|
||||
["following_relationships"],
|
||||
["hashtags"],
|
||||
|
|
|
@ -841,6 +841,55 @@ defmodule Pleroma.UserTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "user registration, with custom domain" do
|
||||
@full_user_data %{
|
||||
bio: "A guy",
|
||||
name: "my name",
|
||||
nickname: "nick",
|
||||
password: "test",
|
||||
password_confirmation: "test",
|
||||
email: "email@example.com"
|
||||
}
|
||||
|
||||
setup do
|
||||
clear_config([:instance, :multitenancy], %{enabled: true})
|
||||
end
|
||||
|
||||
test "it registers on a given domain" do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Pleroma.Domain.create(%{
|
||||
domain: "pleroma.example.org",
|
||||
public: true
|
||||
})
|
||||
|
||||
params =
|
||||
@full_user_data
|
||||
|> Map.put(:domain_id, to_string(domain_id))
|
||||
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
assert changeset.valid?
|
||||
|
||||
{:ok, user} = Repo.insert(changeset)
|
||||
|
||||
assert user.nickname == "nick@pleroma.example.org"
|
||||
end
|
||||
|
||||
test "it fails when domain is private" do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Pleroma.Domain.create(%{
|
||||
domain: "private.example.org",
|
||||
public: false
|
||||
})
|
||||
|
||||
params =
|
||||
@full_user_data
|
||||
|> Map.put(:domain_id, to_string(domain_id))
|
||||
|
||||
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)
|
||||
|
|
|
@ -138,6 +138,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||
end
|
||||
|
||||
test "it returns a json representation of a local user domain different from host", %{
|
||||
conn: conn
|
||||
} do
|
||||
user =
|
||||
insert(:user, %{
|
||||
nickname: "nick@example.org"
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> get("/users/#{user.nickname}.json")
|
||||
|
||||
user = User.get_cached_by_id(user.id)
|
||||
|
||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||
end
|
||||
|
||||
test "it returns 404 for remote users", %{
|
||||
conn: conn
|
||||
} do
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.DomainControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Domain
|
||||
|
||||
setup do
|
||||
clear_config([Pleroma.Web.WebFinger, :domain], "example.com")
|
||||
|
||||
admin = insert(:user, is_admin: true)
|
||||
token = insert(:oauth_admin_token, user: admin)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> assign(:token, token)
|
||||
|
||||
{:ok, %{admin: admin, token: token, conn: conn}}
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/domains" do
|
||||
test "list created domains", %{conn: conn} do
|
||||
_domain =
|
||||
Domain.create(%{
|
||||
domain: "pleroma.mkljczk.pl",
|
||||
public: true
|
||||
})
|
||||
|
||||
_domain =
|
||||
Domain.create(%{
|
||||
domain: "pleroma2.mkljczk.pl"
|
||||
})
|
||||
|
||||
conn = get(conn, "/api/pleroma/admin/domains")
|
||||
|
||||
[
|
||||
%{
|
||||
"id" => _id,
|
||||
"domain" => "pleroma.mkljczk.pl",
|
||||
"public" => true
|
||||
},
|
||||
%{
|
||||
"id" => _id2,
|
||||
"domain" => "pleroma2.mkljczk.pl",
|
||||
"public" => false
|
||||
}
|
||||
] = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/domains" do
|
||||
test "create a valid domain", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/domains", %{
|
||||
domain: "pleroma.mkljczk.pl",
|
||||
public: true
|
||||
})
|
||||
|
||||
%{
|
||||
"id" => _id,
|
||||
"domain" => "pleroma.mkljczk.pl",
|
||||
"public" => true
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "create a domain the same as host", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/domains", %{
|
||||
domain: "example.com",
|
||||
public: false
|
||||
})
|
||||
|
||||
%{"error" => "invalid_domain"} = json_response_and_validate_schema(conn, 400)
|
||||
end
|
||||
|
||||
test "create duplicate domains", %{conn: conn} do
|
||||
Domain.create(%{
|
||||
domain: "pleroma.mkljczk.pl",
|
||||
public: true
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/domains", %{
|
||||
domain: "pleroma.mkljczk.pl",
|
||||
public: false
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 400)
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH /api/pleroma/admin/domains/:id" do
|
||||
test "update domain privacy", %{conn: conn} do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "pleroma.mkljczk.pl",
|
||||
public: true
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> patch("/api/pleroma/admin/domains/#{domain_id}", %{
|
||||
public: false
|
||||
})
|
||||
|
||||
%{
|
||||
"id" => _id,
|
||||
"domain" => "pleroma.mkljczk.pl",
|
||||
"public" => false
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "doesn't update domain name", %{conn: conn} do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "plemora.mkljczk.pl",
|
||||
public: true
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> patch("/api/pleroma/admin/domains/#{domain_id}", %{
|
||||
domain: "pleroma.mkljczk.pl"
|
||||
})
|
||||
|
||||
%{
|
||||
"id" => _id,
|
||||
"domain" => "plemora.mkljczk.pl",
|
||||
"public" => true
|
||||
} = json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /api/pleroma/admin/domains/:id" do
|
||||
test "delete a domain", %{conn: conn} do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "pleroma.mkljczk.pl",
|
||||
public: true
|
||||
})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> delete("/api/pleroma/admin/domains/#{domain_id}")
|
||||
|
||||
%{} = json_response_and_validate_schema(conn, 200)
|
||||
|
||||
domains = Domain.list()
|
||||
|
||||
assert length(domains) == 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -194,4 +194,53 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
|
|||
refute Map.has_key?(result["pleroma"]["metadata"]["base_urls"], "media_proxy")
|
||||
refute Map.has_key?(result["pleroma"]["metadata"]["base_urls"], "upload")
|
||||
end
|
||||
|
||||
test "instance domains", %{conn: conn} do
|
||||
clear_config([:instance, :multitenancy], %{enabled: true})
|
||||
|
||||
{:ok, %{id: domain_id}} =
|
||||
Pleroma.Domain.create(%{
|
||||
domain: "pleroma.example.org"
|
||||
})
|
||||
|
||||
domain_id = to_string(domain_id)
|
||||
|
||||
assert %{
|
||||
"pleroma" => %{
|
||||
"metadata" => %{
|
||||
"multitenancy" => %{
|
||||
"enabled" => true,
|
||||
"domains" => [
|
||||
%{
|
||||
"id" => "",
|
||||
"domain" => _,
|
||||
"public" => true
|
||||
},
|
||||
%{
|
||||
"id" => ^domain_id,
|
||||
"domain" => "pleroma.example.org",
|
||||
"public" => false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
} =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
clear_config([:instance, :multitenancy, :enabled], false)
|
||||
|
||||
assert %{
|
||||
"pleroma" => %{
|
||||
"metadata" => %{
|
||||
"multitenancy" => nil
|
||||
}
|
||||
}
|
||||
} =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -407,6 +407,43 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
|||
|
||||
assert [] = result
|
||||
end
|
||||
|
||||
test "filtering local posts basing on domain", %{conn: conn} do
|
||||
clear_config([:instance, :multitenancy], %{enabled: true, separate_timelines: false})
|
||||
|
||||
{:ok, domain} = Pleroma.Domain.create(%{domain: "pleroma.example.org"})
|
||||
|
||||
user1 = insert(:user)
|
||||
user2 = insert(:user, %{domain_id: domain.id})
|
||||
|
||||
%{id: note1} = insert(:note_activity, user: user1)
|
||||
%{id: note2} = insert(:note_activity, user: user2)
|
||||
|
||||
assert [
|
||||
%{"id" => ^note2},
|
||||
%{"id" => ^note1}
|
||||
] =
|
||||
conn
|
||||
|> get("/api/v1/timelines/public?local=true")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
clear_config([:instance, :multitenancy, :separate_timelines], true)
|
||||
|
||||
assert [%{"id" => ^note1}] =
|
||||
conn
|
||||
|> get("/api/v1/timelines/public?local=true")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^note1}] =
|
||||
conn
|
||||
|> get("/api/v1/timelines/public?local=true")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert [%{"id" => ^note2}] =
|
||||
conn
|
||||
|> get("http://pleroma.example.org/api/v1/timelines/public?local=true")
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
end
|
||||
|
||||
defp local_and_remote_activities do
|
||||
|
|
|
@ -98,7 +98,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
skip_thread_containment: false,
|
||||
accepts_chat_messages: nil,
|
||||
avatar_description: "",
|
||||
header_description: ""
|
||||
header_description: "",
|
||||
is_local: true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,7 +345,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
skip_thread_containment: false,
|
||||
accepts_chat_messages: nil,
|
||||
avatar_description: "",
|
||||
header_description: ""
|
||||
header_description: "",
|
||||
is_local: true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,20 @@ defmodule Pleroma.Web.WebFingerTest do
|
|||
{:ok, result} = WebFinger.webfinger(user.ap_id, "XML")
|
||||
assert is_binary(result)
|
||||
end
|
||||
|
||||
test "works for fqns with domains other than host" do
|
||||
user = insert(:user, %{nickname: "nick@example.org"})
|
||||
|
||||
{:ok, result} = WebFinger.webfinger("#{user.nickname})}", "XML")
|
||||
|
||||
assert is_binary(result)
|
||||
end
|
||||
|
||||
test "doesn't work for remote users" do
|
||||
user = insert(:user, %{local: false})
|
||||
|
||||
assert {:error, _} = WebFinger.webfinger("#{user.nickname})}", "XML")
|
||||
end
|
||||
end
|
||||
|
||||
describe "fingering" do
|
||||
|
|
118
test/pleroma/workers/check_domains_resolve_worker_test.exs
Normal file
118
test/pleroma/workers/check_domains_resolve_worker_test.exs
Normal file
|
@ -0,0 +1,118 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.CheckDomainsResolveWorkerTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Domain
|
||||
alias Pleroma.Workers.CheckDomainResolveWorker
|
||||
|
||||
setup do
|
||||
Pleroma.Web.Endpoint.config_change(
|
||||
[{Pleroma.Web.Endpoint, url: [host: "pleroma.example.org", scheme: "https", port: 443]}],
|
||||
[]
|
||||
)
|
||||
|
||||
clear_config([Pleroma.Web.Endpoint, :url, :host], "pleroma.example.org")
|
||||
|
||||
Tesla.Mock.mock_global(fn
|
||||
%{url: "https://pleroma.example.org/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/pleroma-host-meta.xml"
|
||||
|> File.read!()
|
||||
|> String.replace("{{domain}}", "pleroma.example.org")
|
||||
}
|
||||
|
||||
%{url: "https://example.org/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/pleroma-host-meta.xml"
|
||||
|> File.read!()
|
||||
|> String.replace("{{domain}}", "pleroma.example.org")
|
||||
}
|
||||
|
||||
%{url: "https://social.example.org/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 302,
|
||||
headers: [{"location", "https://pleroma.example.org/.well-known/host-meta"}]
|
||||
}
|
||||
|
||||
%{url: "https://notpleroma.example.org/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body:
|
||||
"test/fixtures/webfinger/pleroma-host-meta.xml"
|
||||
|> File.read!()
|
||||
|> String.replace("{{domain}}", "notpleroma.example.org")
|
||||
}
|
||||
|
||||
%{url: "https://wrong.example.org/.well-known/host-meta"} ->
|
||||
%Tesla.Env{
|
||||
status: 302,
|
||||
headers: [{"location", "https://notpleroma.example.org/.well-known/host-meta"}]
|
||||
}
|
||||
|
||||
%{url: "https://bad.example.org/.well-known/host-meta"} ->
|
||||
%Tesla.Env{status: 404}
|
||||
end)
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Web.Endpoint.config_change(
|
||||
[{Pleroma.Web.Endpoint, url: [host: "localhost"]}],
|
||||
[]
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
test "verifies domain state" do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "example.org"
|
||||
})
|
||||
|
||||
{:ok, domain} = CheckDomainResolveWorker.perform(%Oban.Job{args: %{"id" => domain_id}})
|
||||
|
||||
assert domain.resolves == true
|
||||
assert domain.last_checked_at != nil
|
||||
end
|
||||
|
||||
test "verifies domain state for a redirect" do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "social.example.org"
|
||||
})
|
||||
|
||||
{:ok, domain} = CheckDomainResolveWorker.perform(%Oban.Job{args: %{"id" => domain_id}})
|
||||
|
||||
assert domain.resolves == true
|
||||
assert domain.last_checked_at != nil
|
||||
end
|
||||
|
||||
test "doesn't verify state for an incorrect redirect" do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "wrong.example.org"
|
||||
})
|
||||
|
||||
{:ok, domain} = CheckDomainResolveWorker.perform(%Oban.Job{args: %{"id" => domain_id}})
|
||||
|
||||
assert domain.resolves == false
|
||||
assert domain.last_checked_at != nil
|
||||
end
|
||||
|
||||
test "doesn't verify state for unimplemented redirect" do
|
||||
{:ok, %{id: domain_id}} =
|
||||
Domain.create(%{
|
||||
domain: "bad.example.org"
|
||||
})
|
||||
|
||||
{:ok, domain} = CheckDomainResolveWorker.perform(%Oban.Job{args: %{"id" => domain_id}})
|
||||
|
||||
assert domain.resolves == false
|
||||
assert domain.last_checked_at != nil
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue