End-to-end sso domain verification (#5513)
* wip
* Fix up typespcs
* Extract domain status enum to module macros
* Cancel verification jobs when integration is removed
* Revisit verification interface
* Supply local DNS server for domain ownership testing
* Rename a test
* 👾
* Use identifier when submitting domain verification
* Disallow re-verification of already verified domains
This commit is contained in:
parent
9256f4ff9c
commit
37e718db40
8
Makefile
8
Makefile
|
|
@ -93,6 +93,14 @@ sso-stop:
|
|||
docker stop idp
|
||||
docker remove idp
|
||||
|
||||
generate-corefile:
|
||||
$(call require, integration_id)
|
||||
integration_id=$(integration_id) envsubst < $(PWD)/extra/fixture/Corefile.template > $(PWD)/extra/fixture/Corefile.gen.$(integration_id)
|
||||
|
||||
mock-dns: generate-corefile
|
||||
$(call require, integration_id)
|
||||
docker run --rm -p 5353:53/udp -v $(PWD)/extra/fixture/Corefile.gen.$(integration_id):/Corefile coredns/coredns:latest -conf Corefile
|
||||
|
||||
loadtest-server:
|
||||
@echo "Ensure your OTP installation is built with --enable-lock-counter"
|
||||
MIX_ENV=load ERL_FLAGS="-emu_type lcnt +Mdai max" iex -S mix do phx.digest + phx.server
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ SHOW_CITIES=true
|
|||
PADDLE_VENDOR_AUTH_CODE=895e20d4efaec0575bb857f44b183217b332d9592e76e69b8a
|
||||
PADDLE_VENDOR_ID=3942
|
||||
SSO_ENABLED=true
|
||||
SSO_VERIFICATION_NAMESERVERS=0.0.0.0:5353
|
||||
|
||||
GOOGLE_CLIENT_ID=875387135161-l8tp53dpt7fdhdg9m1pc3vl42si95rh0.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=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
|
||||
|
|
|
|||
|
|
@ -312,6 +312,23 @@ sso_saml_adapter =
|
|||
"real" -> PlausibleWeb.SSO.RealSAMLAdapter
|
||||
end
|
||||
|
||||
sso_verification_nameservers =
|
||||
case get_var_from_path_or_env(config_dir, "SSO_VERIFICATION_NAMESERVERS") do
|
||||
nil ->
|
||||
[]
|
||||
|
||||
some ->
|
||||
some
|
||||
|> String.split(",")
|
||||
|> Enum.map(fn addr ->
|
||||
uri = URI.parse("dns://#{addr}")
|
||||
host = uri.host
|
||||
port = uri.port || 53
|
||||
{:ok, addr} = :inet.parse_address(to_charlist(host))
|
||||
{addr, port}
|
||||
end)
|
||||
end
|
||||
|
||||
config :plausible,
|
||||
environment: env,
|
||||
mailer_email: mailer_email,
|
||||
|
|
@ -323,7 +340,8 @@ config :plausible,
|
|||
data_dir: data_dir,
|
||||
session_transfer_dir: session_transfer_dir,
|
||||
sso_enabled: sso_enabled,
|
||||
sso_saml_adapter: sso_saml_adapter
|
||||
sso_saml_adapter: sso_saml_adapter,
|
||||
sso_verification_nameservers: sso_verification_nameservers
|
||||
|
||||
config :plausible, :selfhost,
|
||||
enable_email_verification: enable_email_verification,
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Corefile.gen.*
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
. {
|
||||
bind 0.0.0.0
|
||||
template IN TXT plausible.test {
|
||||
answer "{{ .Name }} 60 IN TXT \"plausible-sso-verification=${integration_id}\""
|
||||
fallthrough
|
||||
}
|
||||
log
|
||||
errors
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ defmodule Plausible.Auth.SSO do
|
|||
alias Plausible.Repo
|
||||
alias Plausible.Teams
|
||||
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
|
||||
@type policy_attr() ::
|
||||
{:sso_default_role, Teams.Policy.sso_member_role()}
|
||||
| {:sso_session_timeout_minutes, non_neg_integer()}
|
||||
|
|
@ -184,15 +186,23 @@ defmodule Plausible.Auth.SSO do
|
|||
|
||||
case {check, force_deprovision?} do
|
||||
{:ok, _} ->
|
||||
Repo.delete!(integration)
|
||||
{:ok, :ok} =
|
||||
Repo.transaction(fn ->
|
||||
integration = Repo.preload(integration, :sso_domains)
|
||||
Enum.each(integration.sso_domains, &SSO.Domains.cancel_verification(&1.domain))
|
||||
Repo.delete!(integration)
|
||||
:ok
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
||||
{{:error, :sso_users_present}, true} ->
|
||||
users = Repo.preload(integration, :users).users
|
||||
|
||||
{:ok, :ok} =
|
||||
Repo.transaction(fn ->
|
||||
users = Repo.preload(integration, :users).users
|
||||
integration = Repo.preload(integration, :sso_domains)
|
||||
Enum.each(users, &deprovision_user!/1)
|
||||
Enum.each(integration.sso_domains, &SSO.Domains.cancel_verification(&1.domain))
|
||||
Repo.delete!(integration)
|
||||
:ok
|
||||
end)
|
||||
|
|
@ -216,7 +226,7 @@ defmodule Plausible.Auth.SSO do
|
|||
)
|
||||
|
||||
domains = Enum.flat_map(integrations, & &1.sso_domains)
|
||||
no_verified_domains? = Enum.all?(domains, &(&1.status != :verified))
|
||||
no_verified_domains? = Enum.all?(domains, &(&1.status != Status.verified()))
|
||||
|
||||
cond do
|
||||
integrations == [] -> {:error, :no_integration}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ defmodule Plausible.Auth.SSO.Domain do
|
|||
@spec verification_methods() :: list(verification_method())
|
||||
def verification_methods(), do: @verification_methods
|
||||
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
|
||||
schema "sso_domains" do
|
||||
field :identifier, Ecto.UUID
|
||||
field :domain, :string
|
||||
|
|
@ -32,8 +34,8 @@ defmodule Plausible.Auth.SSO.Domain do
|
|||
field :last_verified_at, :naive_datetime
|
||||
|
||||
field :status, Ecto.Enum,
|
||||
values: [:pending, :in_progress, :verified, :unverified],
|
||||
default: :pending
|
||||
values: Status.all(),
|
||||
default: Status.pending()
|
||||
|
||||
belongs_to :sso_integration, Plausible.Auth.SSO.Integration
|
||||
|
||||
|
|
@ -59,11 +61,11 @@ defmodule Plausible.Auth.SSO.Domain do
|
|||
|> change()
|
||||
|> put_change(:verified_via, method)
|
||||
|> put_change(:last_verified_at, now)
|
||||
|> put_change(:status, :verified)
|
||||
|> put_change(:status, Status.verified())
|
||||
end
|
||||
|
||||
@spec unverified_changeset(t(), NaiveDateTime.t(), atom()) :: Ecto.Changeset.t()
|
||||
def unverified_changeset(sso_domain, now, status \\ :in_progress) do
|
||||
def unverified_changeset(sso_domain, now, status \\ Status.in_progress()) do
|
||||
sso_domain
|
||||
|> change()
|
||||
|> put_change(:verified_via, nil)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
defmodule Plausible.Auth.SSO.Domain.Status do
|
||||
@moduledoc false
|
||||
|
||||
defmacro __using__(opts) do
|
||||
as = Keyword.get(opts, :as, Status)
|
||||
|
||||
quote do
|
||||
require Plausible.Auth.SSO.Domain.Status
|
||||
alias Plausible.Auth.SSO.Domain.Status, as: unquote(as)
|
||||
end
|
||||
end
|
||||
|
||||
defmacro pending(), do: :pending
|
||||
defmacro in_progress(), do: :in_progress
|
||||
defmacro verified(), do: :verified
|
||||
defmacro unverified(), do: :unverified
|
||||
|
||||
defmacro all() do
|
||||
[pending(), in_progress(), verified(), unverified()]
|
||||
end
|
||||
end
|
||||
|
|
@ -9,29 +9,33 @@ defmodule Plausible.Auth.SSO.Domain.Verification.Worker do
|
|||
queue: :sso_domain_ownership_verification,
|
||||
unique: true
|
||||
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
|
||||
alias Plausible.Auth.SSO
|
||||
alias Plausible.Repo
|
||||
|
||||
# roughly around 34h, given the snooze back-off
|
||||
@max_snoozes 14
|
||||
|
||||
@spec cancel(String.t()) :: :ok
|
||||
def cancel(domain) do
|
||||
{:ok, job} =
|
||||
%{domain: domain}
|
||||
|> new()
|
||||
|> Oban.insert()
|
||||
|
||||
Oban.cancel_job(job)
|
||||
end
|
||||
|
||||
@spec enqueue(String.t()) :: {:ok, Oban.Job.t()}
|
||||
def enqueue(domain) do
|
||||
{:ok, result} =
|
||||
Repo.transaction(fn ->
|
||||
with {:ok, sso_domain} <- SSO.Domains.get(domain) do
|
||||
SSO.Domains.mark_unverified!(sso_domain, :in_progress)
|
||||
end
|
||||
{:ok, job} =
|
||||
%{domain: domain}
|
||||
|> new()
|
||||
|> Oban.insert()
|
||||
|
||||
{:ok, job} =
|
||||
%{domain: domain}
|
||||
|> new()
|
||||
|> Oban.insert()
|
||||
|
||||
:ok = Oban.retry_job(job)
|
||||
{:ok, job}
|
||||
end)
|
||||
|
||||
result
|
||||
:ok = Oban.retry_job(job)
|
||||
{:ok, job}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -42,7 +46,10 @@ defmodule Plausible.Auth.SSO.Domain.Verification.Worker do
|
|||
})
|
||||
when attempt <= @max_snoozes do
|
||||
service_opts = [
|
||||
skip_checks?: meta["skip_checks"] == true
|
||||
skip_checks?: meta["skip_checks"] == true,
|
||||
verification_opts: [
|
||||
nameservers: Application.get_env(:plausible, :sso_verification_nameservers) || []
|
||||
]
|
||||
]
|
||||
|
||||
service_opts =
|
||||
|
|
@ -55,7 +62,7 @@ defmodule Plausible.Auth.SSO.Domain.Verification.Worker do
|
|||
case SSO.Domains.get(domain) do
|
||||
{:ok, sso_domain} ->
|
||||
case SSO.Domains.verify(sso_domain, service_opts) do
|
||||
%SSO.Domain{status: :verified} = verified ->
|
||||
%SSO.Domain{status: Status.verified()} = verified ->
|
||||
verification_complete(sso_domain)
|
||||
{:ok, verified}
|
||||
|
||||
|
|
@ -82,7 +89,7 @@ defmodule Plausible.Auth.SSO.Domain.Verification.Worker do
|
|||
defp verification_failure(domain) do
|
||||
with {:ok, sso_domain} <- SSO.Domains.get(domain) do
|
||||
sso_domain
|
||||
|> SSO.Domains.mark_unverified!(:unverified)
|
||||
|> SSO.Domains.mark_unverified!(Status.unverified())
|
||||
|> send_failure_notification()
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ defmodule Plausible.Auth.SSO.Domains do
|
|||
|
||||
alias Plausible.Auth
|
||||
alias Plausible.Auth.SSO
|
||||
alias Plausible.Auth.SSO.Domain.Verification
|
||||
alias Plausible.Repo
|
||||
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
|
||||
@spec add(SSO.Integration.t(), String.t()) ::
|
||||
{:ok, SSO.Domain.t()} | {:error, Ecto.Changeset.t()}
|
||||
def add(integration, domain) do
|
||||
|
|
@ -17,6 +20,34 @@ defmodule Plausible.Auth.SSO.Domains do
|
|||
Repo.insert(changeset)
|
||||
end
|
||||
|
||||
@spec start_verification(String.t()) :: SSO.Domain.t()
|
||||
def start_verification(domain) when is_binary(domain) do
|
||||
{:ok, result} =
|
||||
Repo.transaction(fn ->
|
||||
with {:ok, sso_domain} <- get(domain) do
|
||||
sso_domain = mark_unverified!(sso_domain, Status.in_progress())
|
||||
{:ok, _} = Verification.Worker.enqueue(domain)
|
||||
{:ok, sso_domain}
|
||||
end
|
||||
end)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
@spec cancel_verification(String.t()) :: :ok
|
||||
def cancel_verification(domain) when is_binary(domain) do
|
||||
{:ok, :ok} =
|
||||
Repo.transaction(fn ->
|
||||
with {:ok, sso_domain} <- get(domain) do
|
||||
mark_unverified!(sso_domain, Status.unverified())
|
||||
end
|
||||
|
||||
:ok = Verification.Worker.cancel(domain)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec verify(SSO.Domain.t(), Keyword.t()) :: SSO.Domain.t()
|
||||
def verify(%SSO.Domain{} = sso_domain, opts \\ []) do
|
||||
skip_checks? = Keyword.get(opts, :skip_checks?, false)
|
||||
|
|
@ -69,7 +100,7 @@ defmodule Plausible.Auth.SSO.Domains do
|
|||
inner_join: i in assoc(d, :sso_integration),
|
||||
inner_join: t in assoc(i, :team),
|
||||
where: d.domain == ^search,
|
||||
where: d.status == :verified,
|
||||
where: d.status == ^Status.verified(),
|
||||
preload: [sso_integration: {i, team: t}]
|
||||
)
|
||||
|> Repo.one()
|
||||
|
|
@ -90,17 +121,21 @@ defmodule Plausible.Auth.SSO.Domains do
|
|||
|
||||
case {check, force_deprovision?} do
|
||||
{:ok, _} ->
|
||||
Repo.delete!(sso_domain)
|
||||
{:ok, :ok} =
|
||||
Repo.transaction(fn ->
|
||||
Repo.delete!(sso_domain)
|
||||
:ok = cancel_verification(sso_domain.domain)
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
||||
{{:error, :sso_users_present}, true} ->
|
||||
domain_users = users_by_domain(sso_domain)
|
||||
|
||||
{:ok, :ok} =
|
||||
Repo.transaction(fn ->
|
||||
domain_users = users_by_domain(sso_domain)
|
||||
Enum.each(domain_users, &SSO.deprovision_user!/1)
|
||||
Repo.delete!(sso_domain)
|
||||
:ok
|
||||
cancel_verification(sso_domain.domain)
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
alias Plausible.Teams
|
||||
|
||||
alias PlausibleWeb.Router.Helpers, as: Routes
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
|
||||
@fake_verify_interval :timer.seconds(10)
|
||||
@refresh_integration_interval :timer.seconds(5)
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
socket = load_integration(socket, socket.assigns.current_team)
|
||||
|
||||
Process.send_after(self(), :fake_domain_verify, @fake_verify_interval)
|
||||
if connected?(socket) do
|
||||
Process.send_after(self(), :refresh_integration, @refresh_integration_interval)
|
||||
end
|
||||
|
||||
{:ok, route_mode(socket)}
|
||||
end
|
||||
|
|
@ -172,7 +175,7 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
<div class="flex-col space-y-6">
|
||||
<p class="text-sm">Verifying domain {@domain.domain}</p>
|
||||
|
||||
<p class="text-sm">You can verify the domain using one of 3 methods:</p>
|
||||
<p class="text-sm">You can verify ownership of the domain using one of 3 methods:</p>
|
||||
|
||||
<ul class="list-disc ml-4 space-y-6">
|
||||
<li>
|
||||
|
|
@ -201,8 +204,20 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<form id="show-manage" for={} phx-submit="show-manage">
|
||||
<.button type="submit">Continue</.button>
|
||||
<.notice>
|
||||
We'll keep checking your domain ownership. Once any of the above verification methods succeeds, we'll send you an e-mail. Thank you for your patience.
|
||||
</.notice>
|
||||
|
||||
<form id="verify-domain-submit" for={} phx-submit="verify-domain-submit">
|
||||
<.input type="hidden" name="identifier" value={@domain.identifier} />
|
||||
<.button
|
||||
:if={@domain.status in [Status.in_progress(), Status.unverified(), Status.verified()]}
|
||||
type="submit"
|
||||
>
|
||||
Run verification now
|
||||
</.button>
|
||||
|
||||
<.button :if={@domain.status == Status.pending()} type="submit">Continue</.button>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
|
|
@ -296,16 +311,31 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
<.table rows={@integration.sso_domains}>
|
||||
<:thead>
|
||||
<.th>Domain</.th>
|
||||
<.th>Added at</.th>
|
||||
<.th hide_on_mobile>Added at</.th>
|
||||
<.th>Status</.th>
|
||||
<.th invisible>Actions</.th>
|
||||
</:thead>
|
||||
<:tbody :let={domain}>
|
||||
<.td>{domain.domain}</.td>
|
||||
<.td>{Calendar.strftime(domain.inserted_at, "%b %-d, %Y at %H:%m UTC")}</.td>
|
||||
<.td>{domain.status}</.td>
|
||||
<.td hide_on_mobile>
|
||||
{Calendar.strftime(domain.inserted_at, "%b %-d, %Y at %H:%m UTC")}
|
||||
</.td>
|
||||
<.td :if={domain.status != Status.in_progress()}>{domain.status}</.td>
|
||||
<.td :if={domain.status == Status.in_progress()}>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<.spinner class="w-4 h-4" />
|
||||
<.styled_link
|
||||
id={"cancel-verify-domain-#{domain.identifier}"}
|
||||
phx-click="cancel-verify-domain"
|
||||
phx-value-identifier={domain.identifier}
|
||||
>
|
||||
Cancel
|
||||
</.styled_link>
|
||||
</div>
|
||||
</.td>
|
||||
<.td actions>
|
||||
<.styled_link
|
||||
:if={domain.status not in [Status.in_progress(), Status.verified()]}
|
||||
id={"verify-domain-#{domain.identifier}"}
|
||||
phx-click="verify-domain"
|
||||
phx-value-identifier={domain.identifier}
|
||||
|
|
@ -458,8 +488,16 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("show-manage", _params, socket) do
|
||||
{:noreply, route_mode(socket, :manage)}
|
||||
def handle_event("verify-domain-submit", params, socket) do
|
||||
integration = socket.assigns.integration
|
||||
sso_domain = Enum.find(integration.sso_domains, &(&1.identifier == params["identifier"]))
|
||||
|
||||
if sso_domain do
|
||||
SSO.Domains.start_verification(sso_domain.domain)
|
||||
{:noreply, route_mode(load_integration(socket, socket.assigns.current_team), :manage)}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("show-domain-setup", _params, socket) do
|
||||
|
|
@ -483,6 +521,21 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_event("cancel-verify-domain", params, socket) do
|
||||
integration = socket.assigns.integration
|
||||
domain = Enum.find(integration.sso_domains, &(&1.identifier == params["identifier"]))
|
||||
|
||||
socket =
|
||||
if domain do
|
||||
:ok = SSO.Domains.cancel_verification(domain.domain)
|
||||
load_integration(socket, socket.assigns.current_team)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("remove-domain", params, socket) do
|
||||
integration = socket.assigns.integration
|
||||
domain = Enum.find(integration.sso_domains, &(&1.identifier == params["identifier"]))
|
||||
|
|
@ -551,29 +604,9 @@ defmodule PlausibleWeb.Live.SSOManagement do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_info(:fake_domain_verify, %{assigns: %{integration: integration}} = socket)
|
||||
when not is_nil(integration) do
|
||||
sso_domains =
|
||||
integration.sso_domains
|
||||
|> Enum.map(fn domain ->
|
||||
if domain.status == :pending do
|
||||
SSO.Domains.verify(domain, skip_checks?: true)
|
||||
else
|
||||
domain
|
||||
end
|
||||
end)
|
||||
|
||||
Process.send_after(self(), :fake_domain_verify, @fake_verify_interval)
|
||||
|
||||
integration = %{integration | sso_domains: sso_domains}
|
||||
|
||||
{:noreply, assign(socket, :integration, integration)}
|
||||
end
|
||||
|
||||
def handle_info(:fake_domain_verify, socket) do
|
||||
Process.send_after(self(), :fake_domain_verify, @fake_verify_interval)
|
||||
|
||||
{:noreply, socket}
|
||||
def handle_info(:refresh_integration, socket) do
|
||||
Process.send_after(self(), :refresh_integration, @refresh_integration_interval)
|
||||
{:noreply, load_integration(socket, socket.assigns.current_team)}
|
||||
end
|
||||
|
||||
defp load_integration(socket, team) do
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ defmodule Plausible.Auth.SSO.DomainsTest do
|
|||
|
||||
on_ee do
|
||||
use Plausible.Teams.Test
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
use Oban.Testing, repo: Plausible.Repo
|
||||
|
||||
alias Plausible.Auth.SSO
|
||||
alias Plausible.Teams
|
||||
|
|
@ -29,7 +31,7 @@ defmodule Plausible.Auth.SSO.DomainsTest do
|
|||
assert is_binary(sso_domain.identifier)
|
||||
refute sso_domain.verified_via
|
||||
refute sso_domain.last_verified_at
|
||||
assert sso_domain.status == :pending
|
||||
assert sso_domain.status == Status.pending()
|
||||
end
|
||||
|
||||
test "normalizes domain before adding", %{integration: integration} do
|
||||
|
|
@ -110,7 +112,7 @@ defmodule Plausible.Auth.SSO.DomainsTest do
|
|||
|
||||
assert verified_domain.id == sso_domain.id
|
||||
assert verified_domain.verified_via == :dns_txt
|
||||
assert verified_domain.status == :verified
|
||||
assert verified_domain.status == Status.verified()
|
||||
assert verified_domain.last_verified_at
|
||||
end
|
||||
|
||||
|
|
@ -124,11 +126,49 @@ defmodule Plausible.Auth.SSO.DomainsTest do
|
|||
|
||||
assert unverified_domain.id == sso_domain.id
|
||||
refute unverified_domain.verified_via
|
||||
assert unverified_domain.status == :in_progress
|
||||
assert unverified_domain.status == Status.in_progress()
|
||||
assert unverified_domain.last_verified_at
|
||||
end
|
||||
end
|
||||
|
||||
describe "start_verification/1" do
|
||||
test "no domain" do
|
||||
assert {:error, :not_found} = SSO.Domains.start_verification("example.com")
|
||||
end
|
||||
|
||||
test "sets domain status to in progress", %{integration: integration} do
|
||||
domain = generate_domain()
|
||||
{:ok, _} = SSO.Domains.add(integration, domain)
|
||||
assert {:ok, sso_domain} = SSO.Domains.start_verification(domain)
|
||||
assert sso_domain.status == Status.in_progress()
|
||||
end
|
||||
|
||||
test "enqueues background work", %{integration: integration} do
|
||||
domain = generate_domain()
|
||||
{:ok, _} = SSO.Domains.add(integration, domain)
|
||||
assert {:ok, _} = SSO.Domains.start_verification(domain)
|
||||
|
||||
assert_enqueued(
|
||||
worker: Plausible.Auth.SSO.Domain.Verification.Worker,
|
||||
args: %{domain: domain}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "cancel_verification/1" do
|
||||
test "no domain" do
|
||||
assert :ok = SSO.Domains.cancel_verification("example.com")
|
||||
end
|
||||
|
||||
test "sets domain status to unverified", %{integration: integration} do
|
||||
domain = generate_domain()
|
||||
{:ok, _} = SSO.Domains.add(integration, domain)
|
||||
assert {:ok, sso_domain} = SSO.Domains.start_verification(domain)
|
||||
assert :ok = SSO.Domains.cancel_verification(domain)
|
||||
assert Repo.reload!(sso_domain).status == Status.unverified()
|
||||
end
|
||||
end
|
||||
|
||||
describe "lookup/1" do
|
||||
test "looks up domain by email", %{integration: integration} do
|
||||
domain = generate_domain()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ defmodule Plausible.Auth.SSOTest do
|
|||
use Plausible
|
||||
|
||||
on_ee do
|
||||
use Oban.Testing, repo: Plausible.Repo
|
||||
use Plausible.Teams.Test
|
||||
|
||||
alias Plausible.Auth
|
||||
|
|
@ -746,6 +747,50 @@ defmodule Plausible.Auth.SSOTest do
|
|||
refute sso_user.sso_identity_id
|
||||
refute sso_user.sso_integration_id
|
||||
end
|
||||
|
||||
test "cancels verification jobs for all domains when integration is removed" do
|
||||
team = new_site().team
|
||||
|
||||
integration = SSO.initiate_saml_integration(team)
|
||||
domain1 = "example-#{Enum.random(1..10_000)}.com"
|
||||
domain2 = "test-#{Enum.random(1..10_000)}.com"
|
||||
|
||||
{:ok, _} = SSO.Domains.add(integration, domain1)
|
||||
{:ok, _} = SSO.Domains.add(integration, domain2)
|
||||
|
||||
{:ok, _} = SSO.Domains.start_verification(domain1)
|
||||
{:ok, _} = SSO.Domains.start_verification(domain2)
|
||||
|
||||
assert_enqueued(worker: SSO.Domain.Verification.Worker, args: %{domain: domain1})
|
||||
assert_enqueued(worker: SSO.Domain.Verification.Worker, args: %{domain: domain2})
|
||||
|
||||
assert :ok = SSO.remove_integration(integration)
|
||||
|
||||
refute Repo.reload(integration)
|
||||
refute_enqueued(worker: SSO.Domain.Verification.Worker, args: %{domain: domain1})
|
||||
refute_enqueued(worker: SSO.Domain.Verification.Worker, args: %{domain: domain2})
|
||||
end
|
||||
|
||||
test "cancels verification jobs when integration is force removed with SSO users" do
|
||||
team = new_site().team
|
||||
|
||||
integration = SSO.initiate_saml_integration(team)
|
||||
domain = "example-#{Enum.random(1..10_000)}.com"
|
||||
|
||||
{:ok, sso_domain} = SSO.Domains.add(integration, domain)
|
||||
SSO.Domains.verify(sso_domain, skip_checks?: true)
|
||||
|
||||
identity = new_identity("Test User", "test@" <> domain)
|
||||
{:ok, _, _, _} = SSO.provision_user(identity)
|
||||
|
||||
{:ok, _} = SSO.Domains.start_verification(domain)
|
||||
assert_enqueued(worker: SSO.Domain.Verification.Worker, args: %{domain: domain})
|
||||
|
||||
assert :ok = SSO.remove_integration(integration, force_deprovision?: true)
|
||||
|
||||
refute Repo.reload(integration)
|
||||
refute_enqueued(worker: SSO.Domain.Verification.Worker, args: %{domain: domain})
|
||||
end
|
||||
end
|
||||
|
||||
defp new_identity(name, email, id \\ Ecto.UUID.generate()) do
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
defmodule PlausibleWeb.Live.SSOMangementTest do
|
||||
use PlausibleWeb.ConnCase, async: false
|
||||
use Oban.Testing, repo: Plausible.Repo
|
||||
|
||||
@moduletag :ee_only
|
||||
|
||||
|
|
@ -108,7 +109,12 @@ defmodule PlausibleWeb.Live.SSOMangementTest do
|
|||
text = render(lv) |> text()
|
||||
assert text =~ "Verifying domain"
|
||||
|
||||
lv |> element("form#show-manage") |> render_submit()
|
||||
lv |> element("form#verify-domain-submit") |> render_submit()
|
||||
|
||||
assert_enqueued(
|
||||
worker: SSO.Domain.Verification.Worker,
|
||||
args: %{domain: "example.com"}
|
||||
)
|
||||
|
||||
html = render(lv)
|
||||
text = text(html)
|
||||
|
|
@ -185,7 +191,12 @@ defmodule PlausibleWeb.Live.SSOMangementTest do
|
|||
html = render(lv)
|
||||
assert text(html) =~ "Verifying domain new.example.com"
|
||||
|
||||
lv |> element("form#show-manage") |> render_submit()
|
||||
lv |> element("form#verify-domain-submit") |> render_submit()
|
||||
|
||||
assert_enqueued(
|
||||
worker: SSO.Domain.Verification.Worker,
|
||||
args: %{domain: "new.example.com"}
|
||||
)
|
||||
|
||||
text = text(render(lv))
|
||||
assert text =~ "org.example.com"
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@ defmodule Plausible.Auth.SSO.Domain.Verification.WorkerTest do
|
|||
use Plausible
|
||||
|
||||
on_ee do
|
||||
use Oban.Testing, repo: Plausible.Repo
|
||||
|
||||
use Bamboo.Test, shared: true
|
||||
|
||||
alias Plausible.Auth.SSO.Domain.Verification.Worker
|
||||
use Oban.Testing, repo: Plausible.Repo
|
||||
use Plausible.Auth.SSO.Domain.Status
|
||||
use Plausible.Teams.Test
|
||||
|
||||
alias Plausible.Auth.SSO
|
||||
|
||||
use Plausible.Teams.Test
|
||||
alias Plausible.Auth.SSO.Domain.Verification.Worker
|
||||
|
||||
test "no sso domain cancels the job" do
|
||||
assert {:cancel, :domain_not_found} =
|
||||
|
|
@ -29,6 +27,13 @@ defmodule Plausible.Auth.SSO.Domain.Verification.WorkerTest do
|
|||
assert_enqueued(worker: Worker, args: %{domain: "example.com"})
|
||||
end
|
||||
|
||||
test "enqueue then cancel" do
|
||||
{:ok, _} = Worker.enqueue("example.com")
|
||||
assert_enqueued(worker: Worker, args: %{domain: "example.com"})
|
||||
:ok = Worker.cancel("example.com")
|
||||
refute_enqueued(worker: Worker, args: %{domain: "example.com"})
|
||||
end
|
||||
|
||||
describe "integration set up" do
|
||||
setup do
|
||||
owner = new_user()
|
||||
|
|
@ -45,19 +50,13 @@ defmodule Plausible.Auth.SSO.Domain.Verification.WorkerTest do
|
|||
sso_domain: sso_domain}
|
||||
end
|
||||
|
||||
test "enqueue resets domain status", %{sso_domain: sso_domain} do
|
||||
%{status: :unverified} = SSO.Domains.mark_unverified!(sso_domain, :unverified)
|
||||
{:ok, _} = Worker.enqueue(sso_domain.domain)
|
||||
assert Plausible.Repo.reload!(sso_domain).status == :in_progress
|
||||
end
|
||||
|
||||
test "domain is marked as in progress and job is snoozed", %{domain: domain} do
|
||||
assert {:ok, %{status: :pending}} = SSO.Domains.get(domain)
|
||||
assert {:ok, %{status: Status.pending()}} = SSO.Domains.get(domain)
|
||||
|
||||
assert {:snooze, 15} =
|
||||
perform_job(Worker, %{"domain" => domain}, meta: %{bypass_checks: true})
|
||||
|
||||
assert {:ok, %{status: :in_progress}} = SSO.Domains.get(domain)
|
||||
assert {:ok, %{status: Status.in_progress()}} = SSO.Domains.get(domain)
|
||||
|
||||
assert {:snooze, 7680} =
|
||||
perform_job(Worker, %{"domain" => domain},
|
||||
|
|
@ -65,7 +64,7 @@ defmodule Plausible.Auth.SSO.Domain.Verification.WorkerTest do
|
|||
meta: %{bypass_checks: true}
|
||||
)
|
||||
|
||||
assert {:ok, %{status: :in_progress}} = SSO.Domains.get(domain)
|
||||
assert {:ok, %{status: Status.in_progress()}} = SSO.Domains.get(domain)
|
||||
end
|
||||
|
||||
test "domain is marked as verified and emails are sent", %{
|
||||
|
|
@ -75,7 +74,7 @@ defmodule Plausible.Auth.SSO.Domain.Verification.WorkerTest do
|
|||
} do
|
||||
owner2 = add_member(team, role: :owner)
|
||||
|
||||
assert {:ok, %{status: :verified}} =
|
||||
assert {:ok, %{status: Status.verified()}} =
|
||||
perform_job(Worker, %{"domain" => domain}, meta: %{skip_checks: true})
|
||||
|
||||
assert_email_delivered_with(
|
||||
|
|
|
|||
Loading…
Reference in New Issue