diff --git a/lib/ex_aws/sts/parsers.ex b/lib/ex_aws/sts/parsers.ex index b892d14..19102d3 100644 --- a/lib/ex_aws/sts/parsers.ex +++ b/lib/ex_aws/sts/parsers.ex @@ -1,142 +1,137 @@ -if Code.ensure_loaded?(SweetXml) do - defmodule ExAws.STS.Parsers do - import SweetXml, only: [sigil_x: 2] - - def parse({:ok, %{body: xml} = resp}, :assume_role) do - parsed_body = - xml - |> SweetXml.xpath(~x"//AssumeRoleResponse", - access_key_id: ~x"./AssumeRoleResult/Credentials/AccessKeyId/text()"s, - secret_access_key: ~x"./AssumeRoleResult/Credentials/SecretAccessKey/text()"s, - session_token: ~x"./AssumeRoleResult/Credentials/SessionToken/text()"s, - expiration: ~x"./AssumeRoleResult/Credentials/Expiration/text()"s, - assumed_role_id: ~x"./AssumeRoleResult/AssumedRoleUser/AssumedRoleId/text()"s, - assumed_role_arn: ~x"./AssumeRoleResult/AssumedRoleUser/Arn/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:ok, %{body: xml} = resp}, :assume_role_with_web_identity) do - parsed_body = - xml - |> SweetXml.xpath(~x"//AssumeRoleWithWebIdentityResponse", - access_key_id: ~x"./AssumeRoleWithWebIdentityResult/Credentials/AccessKeyId/text()"s, - secret_access_key: - ~x"./AssumeRoleWithWebIdentityResult/Credentials/SecretAccessKey/text()"s, - session_token: ~x"./AssumeRoleWithWebIdentityResult/Credentials/SessionToken/text()"s, - expiration: ~x"./AssumeRoleWithWebIdentityResult/Credentials/Expiration/text()"s, - assumed_role_id: - ~x"./AssumeRoleWithWebIdentityResult/AssumedRoleUser/AssumedRoleId/text()"s, - assumed_role_arn: ~x"./AssumeRoleWithWebIdentityResult/AssumedRoleUser/Arn/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:ok, %{body: xml} = resp}, :get_access_key_info) do - parsed_body = - SweetXml.xpath(xml, ~x"//GetAccessKeyInfoResponse", - account: ~x"./GetAccessKeyInfoResult/Account/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:ok, %{body: xml} = resp}, :assume_role_with_s_a_m_l) do - parsed_body = - xml - |> SweetXml.xpath(~x"//AssumeRoleWithSAMLResponse", - access_key_id: ~x"./AssumeRoleResult/Credentials/AccessKeyId/text()"s, - secret_access_key: ~x"./AssumeRoleResult/Credentials/SecretAccessKey/text()"s, - session_token: ~x"./AssumeRoleResult/Credentials/SessionToken/text()"s, - expiration: ~x"./AssumeRoleResult/Credentials/Expiration/text()"s, - assumed_role_id: ~x"./AssumeRoleResult/AssumedRoleUser/AssumedRoleId/text()"s, - assumed_role_arn: ~x"./AssumeRoleResult/AssumedRoleUser/Arn/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:ok, %{body: xml} = resp}, :get_caller_identity) do - parsed_body = - SweetXml.xpath(xml, ~x"//GetCallerIdentityResponse", - arn: ~x"./GetCallerIdentityResult/Arn/text()"s, - user_id: ~x"./GetCallerIdentityResult/UserId/text()"s, - account: ~x"./GetCallerIdentityResult/Account/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:ok, %{body: xml} = resp}, :get_federation_token) do - parsed_body = - xml - |> SweetXml.xpath(~x"//GetFederationTokenResponse", - access_key_id: ~x"./GetFederationTokenResult/Credentials/AccessKeyId/text()"s, - secret_access_key: ~x"./GetFederationTokenResult/Credentials/SecretAccessKey/text()"s, - session_token: ~x"./GetFederationTokenResult/Credentials/SessionToken/text()"s, - expiration: ~x"./GetFederationTokenResult/Credentials/Expiration/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:ok, %{body: xml} = resp}, :get_session_token) do - parsed_body = - xml - |> SweetXml.xpath(~x"//GetSessionTokenResponse", - access_key_id: ~x"./GetSessionTokenResult/Credentials/AccessKeyId/text()"s, - secret_access_key: ~x"./GetSessionTokenResult/Credentials/SecretAccessKey/text()"s, - session_token: ~x"./GetSessionTokenResult/Credentials/SessionToken/text()"s, - expiration: ~x"./GetSessionTokenResult/Credentials/Expiration/text()"s, - request_id: request_id_xpath() - ) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - def parse({:error, {type, http_status_code, %{body: xml}}}, _) do - parsed_body = - xml - |> SweetXml.xpath(~x"//ErrorResponse", - request_id: ~x"./RequestId/text()"s, - type: ~x"./Error/Type/text()"s, - code: ~x"./Error/Code/text()"s, - message: ~x"./Error/Message/text()"s, - detail: ~x"./Error/Detail/text()"s - ) - - {:error, {type, http_status_code, parsed_body}} - end - - def parse(val, _), do: val - - def parse({:ok, %{body: xml} = resp}, :decode_authorization_message, config) do - parsed_body = - xml - |> SweetXml.xpath(~x"//DecodeAuthorizationMessageResponse", - decoded_message: ~x"./DecodedMessage/text()"s, - request_id: ~x"./RequestId/text()"s - ) - |> Map.update!(:decoded_message, fn msg -> config[:json_codec].decode!(msg) end) - - {:ok, Map.put(resp, :body, parsed_body)} - end - - defp request_id_xpath do - ~x"./ResponseMetadata/RequestId/text()"s - end +defmodule ExAws.STS.Parsers do + alias ExAws.STS.Parsers.XML + + import XML, only: [sigil_x: 2] + + def parse({:ok, %{body: xml} = resp}, :assume_role) do + parsed_body = + xml + |> XML.xpath(~x"//AssumeRoleResponse", + access_key_id: ~x"./AssumeRoleResult/Credentials/AccessKeyId/text()"s, + secret_access_key: ~x"./AssumeRoleResult/Credentials/SecretAccessKey/text()"s, + session_token: ~x"./AssumeRoleResult/Credentials/SessionToken/text()"s, + expiration: ~x"./AssumeRoleResult/Credentials/Expiration/text()"s, + assumed_role_id: ~x"./AssumeRoleResult/AssumedRoleUser/AssumedRoleId/text()"s, + assumed_role_arn: ~x"./AssumeRoleResult/AssumedRoleUser/Arn/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} end -else - defmodule ExAws.STS.Parsers do - def parse(val, _), do: val - def parse(val, _, _), do: val + + def parse({:ok, %{body: xml} = resp}, :assume_role_with_web_identity) do + parsed_body = + xml + |> XML.xpath(~x"//AssumeRoleWithWebIdentityResponse", + access_key_id: ~x"./AssumeRoleWithWebIdentityResult/Credentials/AccessKeyId/text()"s, + secret_access_key: + ~x"./AssumeRoleWithWebIdentityResult/Credentials/SecretAccessKey/text()"s, + session_token: ~x"./AssumeRoleWithWebIdentityResult/Credentials/SessionToken/text()"s, + expiration: ~x"./AssumeRoleWithWebIdentityResult/Credentials/Expiration/text()"s, + assumed_role_id: + ~x"./AssumeRoleWithWebIdentityResult/AssumedRoleUser/AssumedRoleId/text()"s, + assumed_role_arn: ~x"./AssumeRoleWithWebIdentityResult/AssumedRoleUser/Arn/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + def parse({:ok, %{body: xml} = resp}, :get_access_key_info) do + parsed_body = + XML.xpath(xml, ~x"//GetAccessKeyInfoResponse", + account: ~x"./GetAccessKeyInfoResult/Account/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + def parse({:ok, %{body: xml} = resp}, :assume_role_with_s_a_m_l) do + parsed_body = + xml + |> XML.xpath(~x"//AssumeRoleWithSAMLResponse", + access_key_id: ~x"./AssumeRoleResult/Credentials/AccessKeyId/text()"s, + secret_access_key: ~x"./AssumeRoleResult/Credentials/SecretAccessKey/text()"s, + session_token: ~x"./AssumeRoleResult/Credentials/SessionToken/text()"s, + expiration: ~x"./AssumeRoleResult/Credentials/Expiration/text()"s, + assumed_role_id: ~x"./AssumeRoleResult/AssumedRoleUser/AssumedRoleId/text()"s, + assumed_role_arn: ~x"./AssumeRoleResult/AssumedRoleUser/Arn/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + def parse({:ok, %{body: xml} = resp}, :get_caller_identity) do + parsed_body = + XML.xpath(xml, ~x"//GetCallerIdentityResponse", + arn: ~x"./GetCallerIdentityResult/Arn/text()"s, + user_id: ~x"./GetCallerIdentityResult/UserId/text()"s, + account: ~x"./GetCallerIdentityResult/Account/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + def parse({:ok, %{body: xml} = resp}, :get_federation_token) do + parsed_body = + xml + |> XML.xpath(~x"//GetFederationTokenResponse", + access_key_id: ~x"./GetFederationTokenResult/Credentials/AccessKeyId/text()"s, + secret_access_key: ~x"./GetFederationTokenResult/Credentials/SecretAccessKey/text()"s, + session_token: ~x"./GetFederationTokenResult/Credentials/SessionToken/text()"s, + expiration: ~x"./GetFederationTokenResult/Credentials/Expiration/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + def parse({:ok, %{body: xml} = resp}, :get_session_token) do + parsed_body = + xml + |> XML.xpath(~x"//GetSessionTokenResponse", + access_key_id: ~x"./GetSessionTokenResult/Credentials/AccessKeyId/text()"s, + secret_access_key: ~x"./GetSessionTokenResult/Credentials/SecretAccessKey/text()"s, + session_token: ~x"./GetSessionTokenResult/Credentials/SessionToken/text()"s, + expiration: ~x"./GetSessionTokenResult/Credentials/Expiration/text()"s, + request_id: request_id_xpath() + ) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + def parse({:error, {type, http_status_code, %{body: xml}}}, _) do + parsed_body = + xml + |> XML.xpath(~x"//ErrorResponse", + request_id: ~x"./RequestId/text()"s, + type: ~x"./Error/Type/text()"s, + code: ~x"./Error/Code/text()"s, + message: ~x"./Error/Message/text()"s, + detail: ~x"./Error/Detail/text()"s + ) + + {:error, {type, http_status_code, parsed_body}} + end + + def parse(val, _), do: val + + def parse({:ok, %{body: xml} = resp}, :decode_authorization_message, config) do + parsed_body = + xml + |> XML.xpath(~x"//DecodeAuthorizationMessageResponse", + decoded_message: ~x"./DecodedMessage/text()"s, + request_id: ~x"./RequestId/text()"s + ) + |> Map.update!(:decoded_message, fn msg -> config[:json_codec].decode!(msg) end) + + {:ok, Map.put(resp, :body, parsed_body)} + end + + defp request_id_xpath do + ~x"./ResponseMetadata/RequestId/text()"s end end diff --git a/lib/ex_aws/sts/parsers/xml.ex b/lib/ex_aws/sts/parsers/xml.ex new file mode 100644 index 0000000..c78a641 --- /dev/null +++ b/lib/ex_aws/sts/parsers/xml.ex @@ -0,0 +1,19 @@ +defmodule ExAws.STS.Parsers.XML do + @moduledoc false + + def xpath(parent, spec, subspec) do + xml_module().xpath(parent, spec, subspec) + rescue + _ in UndefinedFunctionError -> + raise "Dependency sweet_xml is required for role based authentication" + end + + def sigil_x(path, modifiers) do + xml_module().sigil_x(path, modifiers) + rescue + _ in UndefinedFunctionError -> + raise "Dependency sweet_xml is required for role based authentication" + end + + defp xml_module, do: Application.get_env(:ex_aws_sts, :xml_module, SweetXml) +end diff --git a/test/lib/parsers_test.exs b/test/lib/parsers_test.exs index 7151918..daff707 100644 --- a/test/lib/parsers_test.exs +++ b/test/lib/parsers_test.exs @@ -1,5 +1,5 @@ defmodule ExAws.STS.ParsersTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false alias ExAws.STS.Parsers @@ -40,4 +40,22 @@ defmodule ExAws.STS.ParsersTest do end end end + + describe "when sweet_xml is not installed" do + setup do + Application.put_env(:ex_aws_sts, :xml_module, MissingSweetXml) + + on_exit(fn -> + Application.delete_env(:ex_aws_sts, :xml_module) + end) + end + + for {action, arity} <- @actions do + test "raises missing sweet_xml error for `:#{action}`" do + assert_raise RuntimeError, + "Dependency sweet_xml is required for role based authentication", + fn -> parse_mock_response(unquote(action), unquote(arity)) end + end + end + end end