283 lines
9.3 KiB
Elixir
283 lines
9.3 KiB
Elixir
defmodule PlausibleWeb.Live.SSOMangementTest do
|
|
use PlausibleWeb.ConnCase, async: false
|
|
use Oban.Testing, repo: Plausible.Repo
|
|
|
|
@moduletag :ee_only
|
|
|
|
on_ee do
|
|
use Bamboo.Test, shared: true
|
|
use Plausible.Teams.Test
|
|
|
|
import Phoenix.LiveViewTest
|
|
import Plausible.Test.Support.HTML
|
|
|
|
alias Plausible.Auth
|
|
alias Plausible.Auth.SSO
|
|
|
|
@cert_pem """
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIFdTCCA12gAwIBAgIUNcATm3CidmlEMMsZa9KBZpWYCVcwDQYJKoZIhvcNAQEL
|
|
BQAwYzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEcMBoGA1UEAwwTc29tZWlkcC5leGFt
|
|
cGxlLmNvbTAeFw0yNTA1MjExMjI5MzVaFw0yNjA1MjExMjI5MzVaMGMxCzAJBgNV
|
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
|
aWRnaXRzIFB0eSBMdGQxHDAaBgNVBAMME3NvbWVpZHAuZXhhbXBsZS5jb20wggIi
|
|
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1N6Drbjed+lFXpOYvE6Efgndy
|
|
W7kYiO8LqQTr4UwVrp9ArxgYuK4TrcNRh2rhS08xAzNTo+NqnJOm95baG97ADYk1
|
|
TqVIKxzaFurv+L/Na0wVXyeNUtxIVKF59uElsg2YLm5eQhL9fmN8jVINCvwDPzxc
|
|
Ihm6mQOaL/i/0DGINOqwHG9MGMZ11AeOM0wKMuXJ2+aKjHOCedhMYVuOaHZgLkcX
|
|
Zzgiv7itm3+JpCjL474MMfibiqKHR0e3QRNcsEC13f/LD8BAGOwsKLznFC8Uctms
|
|
48EDNbxxLG01jVbnJSxRrcDN3RUDjtCdHyaTCCFJAgmldHKKua3VQEynOwJIkFMC
|
|
fpL1LpLvATzIt0cT1ESb1RHIlgacmESVn/TW2QjO5tp4FAu7GJK+5xY7jPvI6saG
|
|
oUHsk0zo9obLK8WYneF19ln+Ea5ZCl9PcTi559AKGpYzpL/9uxoPT1zxxTn6c2lt
|
|
4xkxkuHtYqi/ENHGdo4CLBL93GDZEilSVmZjD/9N9990yWbPXXQ0eNoFckYSZuls
|
|
HaWz8W5c046/ob8mASI6wzAUCkO9Zz4WbIj9A+mNZB32hMZbMA02gU//ffvNkFjL
|
|
DGlNbROCg2DX64rvGs/RuqhuDVCnVfid9B36Cgs76GWI8dCInEfyZMtiqUb7E8Oe
|
|
BPVwtTscz1StlF/0cQIDAQABoyEwHzAdBgNVHQ4EFgQU9lvXH4X04v99rrwKNzsw
|
|
pNQP/dUwDQYJKoZIhvcNAQELBQADggIBAJD0MD+OK58vlP2HEoKLQYAKYM/4NsBz
|
|
vSK1PtZsEj0fqiuu66ceH0wlKlGquRad4Z+LXMptu1DzMNmJsf0zSQfleGFks3xI
|
|
86hgkQ7f0qjs+YJzjJUxF9H8zX4jJk5poOqOJwStHBCDLsUmIxnT7/il3jlT0Nj4
|
|
cVs4946pCg7rP1kR9jojFD5yvzKoRBJG3/qvFnzAi8cDv9CRjSgoDTZyzZmwdCgu
|
|
NioW7YeFCtvYxvY7HDXinwq/w8Gn3n8zdISoAqSpYrt5Y5ygJGiEYVDWdA50a6PC
|
|
gq5xt8RCizz1L7a5BUJFMCQ0pyAUuODTndPUGLT8i7jFgzhamFPD72zFMk2+IabE
|
|
Dutyt2GFeTQ75wL8QvfsKm29Vd5EjAsdfmup3hCpLGqF3g8Sh0aXDrj8KPqIecuS
|
|
gkL69M9iXfnwZhTo23zUuFjBNoAIPXkNKXiJS7p9IEpYRVnlPYLToSEnnzptoPPQ
|
|
zMBb8x/UMMtNYkyehSLhuIPrRLvv3eth7Hq3hA7tOCRyyf78tReVm+VoRx6AK68v
|
|
5ufxMKBFRTNoLIN3sD+DmSUNY+CaHxRMDhSESy0Ac/95J2yKi+Y1Kml2GV53pSlT
|
|
6FPm8B0R9YXM7lHhTLyL7DYqnvklkLh2bUqCLyBowynPyGqdYV4DbFSiST14fGXR
|
|
mNEYF78kg0IA
|
|
-----END CERTIFICATE-----
|
|
"""
|
|
|
|
describe "/settings/sso/general" do
|
|
setup [:create_user, :log_in, :create_team, :setup_team]
|
|
|
|
test "renders", %{conn: conn} do
|
|
resp =
|
|
conn
|
|
|> get(Routes.sso_path(conn, :sso_settings))
|
|
|> html_response(200)
|
|
|> text()
|
|
|
|
assert resp =~ "Start Configuring SSO"
|
|
end
|
|
end
|
|
|
|
describe "live" do
|
|
setup [:create_user, :log_in, :create_team, :setup_team]
|
|
|
|
test "init setup - basic walk through", %{conn: conn} do
|
|
{lv, _html} = get_lv(conn)
|
|
lv |> element("form#sso-init") |> render_submit()
|
|
html = render(lv)
|
|
|
|
assert element_exists?(html, "form#sso-sp-config")
|
|
|
|
lv |> element("form#sso-idp-form") |> render_submit()
|
|
lv |> element("form#sso-idp-config") |> render_submit()
|
|
|
|
text = text(render(lv))
|
|
|
|
assert text =~ "Sign-in URL can't be blank"
|
|
assert text =~ "Entity ID can't be blank"
|
|
assert text =~ "Certificate in PEM format can't be blank"
|
|
|
|
lv
|
|
|> element("form#sso-idp-config")
|
|
|> render_submit(%{
|
|
saml_config: %{
|
|
idp_signin_url: "http://signin.example.com",
|
|
idp_entity_id: "abc123",
|
|
idp_cert_pem: @cert_pem
|
|
}
|
|
})
|
|
|
|
lv
|
|
|> element("form#sso-add-domain")
|
|
|> render_submit()
|
|
|
|
text = text(render(lv))
|
|
assert text =~ "Domain can't be blank"
|
|
|
|
lv
|
|
|> element("form#sso-add-domain")
|
|
|> render_submit(%{
|
|
domain: %{
|
|
domain: "example.com"
|
|
}
|
|
})
|
|
|
|
text = render(lv) |> text()
|
|
assert text =~ "Verifying domain"
|
|
|
|
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)
|
|
|
|
assert text =~ "example.com"
|
|
assert text =~ "-BEGIN CERTIFICATE-"
|
|
|
|
sp_entity_id = text_of_attr(html, "#sp-entity-id", "value")
|
|
integration_identifier = sp_entity_id |> Path.split() |> List.last()
|
|
{:ok, integration} = SSO.get_integration(integration_identifier)
|
|
|
|
assert integration.config.idp_signin_url == "http://signin.example.com"
|
|
assert integration.config.idp_entity_id == "abc123"
|
|
assert [%{domain: "example.com"}] = integration.sso_domains
|
|
end
|
|
|
|
test "renders integration summary", %{conn: conn, team: team} do
|
|
setup_integration(team, "example.com")
|
|
|
|
{_lv, html} = get_lv(conn)
|
|
|
|
assert element_exists?(html, "h2 a#sso-manage-config")
|
|
assert element_exists?(html, "h2 a#sso-domains-config")
|
|
assert element_exists?(html, "h2 a#sso-policy-config")
|
|
end
|
|
|
|
test "edit idp config", %{conn: conn, team: team} do
|
|
setup_integration(team, "example.com")
|
|
|
|
{lv, _html} = get_lv(conn)
|
|
|
|
lv |> element("form#show-idp-form") |> render_submit()
|
|
|
|
html = render(lv)
|
|
|
|
assert element_exists?(html, "input#sso-idp-config_idp_signin_url")
|
|
assert element_exists?(html, "input#sso-idp-config_idp_entity_id")
|
|
assert element_exists?(html, "textarea#sso-idp-config_idp_cert_pem")
|
|
|
|
lv
|
|
|> element("form#sso-idp-config")
|
|
|> render_submit(%{
|
|
saml_config: %{
|
|
idp_signin_url: "http://updated.example.com",
|
|
idp_entity_id: "zxc345",
|
|
idp_cert_pem: @cert_pem
|
|
}
|
|
})
|
|
|
|
html = render(lv)
|
|
assert element_exists?(html, "h2 a#sso-manage-config")
|
|
|
|
assert text_of_attr(html, "input#sso-idp-config_idp_signin_url", "value") ==
|
|
"http://updated.example.com"
|
|
|
|
assert text_of_attr(html, "input#sso-idp-config_idp_entity_id", "value") == "zxc345"
|
|
end
|
|
|
|
test "add another domain", %{conn: conn, team: team} do
|
|
setup_integration(team, "org.example.com")
|
|
|
|
{lv, _html} = get_lv(conn)
|
|
|
|
lv |> element("form#show-domain-setup") |> render_submit()
|
|
|
|
lv
|
|
|> element("form#sso-add-domain")
|
|
|> render_submit(%{
|
|
domain: %{
|
|
domain: "new.example.com"
|
|
}
|
|
})
|
|
|
|
html = render(lv)
|
|
assert text(html) =~ "Verifying domain new.example.com"
|
|
|
|
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"
|
|
assert text =~ "new.example.com"
|
|
end
|
|
|
|
test "policy update", %{conn: conn, team: team} do
|
|
setup_integration(team, "org.example.com")
|
|
{lv, html} = get_lv(conn)
|
|
|
|
assert text_of_attr(
|
|
html,
|
|
~s|#sso-policy_sso_default_role option[selected="selected"]|,
|
|
"value"
|
|
) == "viewer"
|
|
|
|
assert text_of_attr(html, ~s|#sso-policy_sso_session_timeout_minutes|, "value") == "360"
|
|
|
|
lv
|
|
|> element("form#sso-policy")
|
|
|> render_submit(%{
|
|
policy: %{
|
|
sso_default_role: "owner",
|
|
sso_session_timeout_minutes: "710"
|
|
}
|
|
})
|
|
|
|
html = render(lv)
|
|
|
|
assert text_of_attr(
|
|
html,
|
|
~s|#sso-policy_sso_default_role option[selected="selected"]|,
|
|
"value"
|
|
) == "owner"
|
|
|
|
assert text_of_attr(html, ~s|#sso-policy_sso_session_timeout_minutes|, "value") == "710"
|
|
end
|
|
|
|
test "force SSO toggle", %{conn: conn, team: team, user: user} do
|
|
setup_integration(team, "org.example.com")
|
|
{_lv, html} = get_lv(conn)
|
|
|
|
assert element_exists?(html, "button#enable-force-sso-toggle[disabled]")
|
|
|
|
{:ok, user, _} = Auth.TOTP.initiate(user)
|
|
{:ok, _user, _} = Auth.TOTP.enable(user, :skip_verify)
|
|
|
|
identity = new_identity("Lance Wurst", "lance@org.example.com")
|
|
{:ok, _, _, _sso_user} = SSO.provision_user(identity)
|
|
|
|
{lv, html} = get_lv(conn)
|
|
|
|
refute element_exists?(html, "button#enable-force-sso-toggle[disabled]")
|
|
|
|
lv
|
|
|> element("#enable-force-sso-toggle")
|
|
|> render_click()
|
|
|
|
assert Plausible.Repo.reload(team).policy.force_sso == :all_but_owners
|
|
end
|
|
|
|
defp setup_integration(team, domain) do
|
|
integration = SSO.initiate_saml_integration(team)
|
|
{:ok, sso_domain} = SSO.Domains.add(integration, domain)
|
|
SSO.Domains.verify(sso_domain, skip_checks?: true)
|
|
|
|
{:ok, _integration} =
|
|
SSO.update_integration(integration, %{
|
|
idp_signin_url: "https://#{domain}",
|
|
idp_entity_id: "some-entity",
|
|
idp_cert_pem: @cert_pem
|
|
})
|
|
end
|
|
|
|
defp get_lv(conn) do
|
|
conn = assign(conn, :live_module, PlausibleWeb.Live.SSOManagement)
|
|
{:ok, lv, html} = live(conn, Routes.sso_path(conn, :sso_settings))
|
|
{lv, html}
|
|
end
|
|
end
|
|
end
|
|
end
|