Skip to content

Instantly share code, notes, and snippets.

@chgeuer
Created February 6, 2025 11:23
Show Gist options
  • Save chgeuer/13fdfed857edcaefaa32e8d09498a254 to your computer and use it in GitHub Desktop.
Save chgeuer/13fdfed857edcaefaa32e8d09498a254 to your computer and use it in GitHub Desktop.

Entra Domains

Mix.install([
  {:req, "~> 0.5.8"}
])

Libraries

defmodule XMLXPath do
  def keyword_list_to_namespace(kwl) do
    kwl
    |> Enum.map(fn {k, v} -> {Atom.to_charlist(k), String.to_charlist(v)} end)
    |> then(&[namespace: &1])
  end

  @doc """
  Evaluates an XPath expression on an XML document.
  """
  @spec xpath(String.t(), String.t(), Keyword.t()) :: term()
  def xpath(document, xpath, namespaces) do
    try do
      {doc, ~c[]} =
        document
        |> String.to_charlist()
        |> :xmerl_scan.string(namespace_conformant: true)

      :xmerl_xpath.string(String.to_charlist(xpath), doc, keyword_list_to_namespace(namespaces))
    rescue
      e -> {:error, e}
    end
  end

  require Record

  for {name, fields} <- Record.extract_all(from_lib: "xmerl/include/xmerl.hrl") do
    Record.defrecordp(name, fields)
  end

  @spec text([term()]) :: [String.t()]
  def text(list) do
    list
    |> Enum.map(fn xmlText(value: value) ->
      List.to_string(value)
    end)
  end
end

defmodule Entra.Discovery do
  def get_all_domains(domain) do
    request_body_xml = fn domain ->
      """
      <?xml version="1.0" encoding="utf-8"?>
      <soap:Envelope 
        xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:a="http://www.w3.org/2005/08/addressing"
        xmlns="http://schemas.microsoft.com/exchange/2010/Autodiscover">
       <soap:Header>
        <a:Action soap:mustUnderstand="1">
           http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation
        </a:Action>
        <a:To soap:mustUnderstand="1">
           https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc
        </a:To>
        <a:ReplyTo>
         <a:Address>
           http://www.w3.org/2005/08/addressing/anonymous
         </a:Address>
        </a:ReplyTo>
       </soap:Header>
       <soap:Body>
        <GetFederationInformationRequestMessage>
         <Request>
          <Domain>#{domain}</Domain>
         </Request>
        </GetFederationInformationRequestMessage>
       </soap:Body>
      </soap:Envelope>
      """
    end

    %Req.Response{
      status: 200,
      body: xml_response
    } = Req.new(
      method: :post,
      headers: [
        { "Content-Type", "text/xml; charset=utf-8"},
        { "SOAPAction", ~s{"http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation"}}
      ],
      url: "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc",
      body: request_body_xml.(domain)
    )
    |> Req.request!()
    
    [error] =
      xml_response
      |> XMLXPath.xpath("//discover:ErrorCode/text()",
        discover: "http://schemas.microsoft.com/exchange/2010/Autodiscover"
      )
      |> XMLXPath.text()
    
    error
    |> case do
      "NotFederated" ->
        {:error, :not_federated}
    
      "InternalServerError" ->
        {:error, :internal_server_error}
    
      "InvalidDomain" ->
        {:error, :invalid_domain}
    
      "NoError" ->
        domain_list =
          xml_response
          |> XMLXPath.xpath("//discover:Domain/text()",
            discover: "http://schemas.microsoft.com/exchange/2010/Autodiscover"
          )
          |> XMLXPath.text()
    
        {:ok, domain_list}
    end
  end
end
Entra.Discovery.get_all_domains("chgeuercxpisv03.onmicrosoft.com")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment