defmodule ShinAuth.SAML do
  @moduledoc """
  Security Assertion Markup Language utilities.

  Performs decoding and validation based on the spec: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
  """

  alias ShinAuth.SAML.Request
  alias ShinAuth.SAML.Response

  @doc """
  Performs decoding on a given SAML request XML document
  """
  @spec decode_saml_request(saml_request_xml :: String.t()) ::
          {:ok, Request.t()}
          | {:error, Request.Error.t()}

  def decode_saml_request(""),
    do:
      {:error,
       %Request.Error{
         tag: :malformed_saml_request,
         message: "Verify if the SAML request is structured correctly by the Service Provider."
       }}

  def decode_saml_request(saml_request) do
    error = %Request.Error{
      tag: :malformed_saml_request,
      message: "Verify if the SAML request is structured correctly by the Service Provider."
    }

    if valid_xml?(saml_request) do
      case DataSchema.to_struct(saml_request, Request) do
        {:ok, parsed_saml_request} -> {:ok, parsed_saml_request}
        _ -> {:error, error}
      end
    else
      {:error, error}
    end
  end

  @doc """
  Performs decoding on a given SAML response XML document
  """
  @spec decode_saml_response(saml_response_xml :: String.t()) ::
          {:ok, Response.t()}
          | {:error, Response.Error.t()}

  def decode_saml_response(""),
    do:
      {:error,
       %Response.Error{
         tag: :malformed_saml_response,
         message: "Verify if the SAML response is structured correctly by the Identity Provider."
       }}

  def decode_saml_response(saml_response) do
    error = %Response.Error{
      tag: :malformed_saml_response,
      message: "Verify if the SAML response is structured correctly by the Identity Provider."
    }

    if valid_xml?(saml_response) do
      case DataSchema.to_struct(saml_response, Response) do
        {:ok, parsed_saml_response} -> {:ok, parsed_saml_response}
        _ -> {:error, error}
      end
    else
      {:error, error}
    end
  end

  defp valid_xml?(xml_string) do
    SweetXml.parse(xml_string, quiet: true)
    true
  catch
    :exit, _ -> false
    :error, _ -> false
  end
end
