defmodule Plausible.Auth.SSO.Integration do @moduledoc """ Instance of particular SSO integration for a given team. Configuration is embedded and its type is dynamic, paving the way for potentially supporting other SSO mechanisms in the future, like OIDC. The UUID identifier can be used to uniquely identify the integration when configuring external services like IdPs. """ use Ecto.Schema import Ecto.Changeset import PolymorphicEmbed alias Plausible.Auth.SSO alias Plausible.Teams @type t() :: %__MODULE__{} @derive {Plausible.Audit.Encoder, only: [:id, :identifier]} schema "sso_integrations" do field :identifier, Ecto.UUID polymorphic_embeds_one :config, types: [ saml: SSO.SAMLConfig ], on_type_not_found: :raise, on_replace: :update belongs_to :team, Plausible.Teams.Team has_many :users, Plausible.Auth.User, foreign_key: :sso_integration_id has_many :sso_domains, SSO.Domain, foreign_key: :sso_integration_id timestamps() end @spec configured?(t()) :: boolean() def configured?(%__MODULE__{config: %config_mod{} = config}) do config_mod.configured?(config) end @spec init_changeset(Teams.Team.t()) :: Ecto.Changeset.t() def init_changeset(team) do params = %{config: %{__type__: :saml}} %__MODULE__{} |> cast(params, []) |> put_change(:identifier, Ecto.UUID.generate()) |> cast_polymorphic_embed(:config) |> put_assoc(:team, team) end @spec update_changeset(t(), map()) :: Ecto.Changeset.t() def update_changeset(integration, config_params) do params = tag_params(:saml, config_params) integration |> cast(params, []) |> cast_polymorphic_embed(:config, with: [ saml: &SSO.SAMLConfig.update_changeset/2 ] ) end defp tag_params(type, params) when is_atom(type) and is_map(params) do case Enum.take(params, 1) do [{key, _}] when is_binary(key) -> %{"config" => Map.merge(%{"__type__" => Atom.to_string(type)}, params)} _ -> %{config: Map.merge(%{__type__: type}, params)} end end end