%% esaml - SAML for erlang %% %% Copyright (c) 2013, Alex Wilson and the University of Queensland %% All rights reserved. %% %% Distributed subject to the terms of the 2-clause BSD license, see %% the LICENSE file in the root of the distribution. %% @doc SAML HTTP binding handlers -module(esaml_binding). -export([decode_response/2, encode_http_redirect/4, encode_http_post/3]). -include_lib("xmerl/include/xmerl.hrl"). -define(deflate, <<"urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE">>). -type uri() :: binary() | string(). -type html_doc() :: binary(). -type xml() :: #xmlElement{} | #xmlDocument{}. %% @private -spec xml_payload_type(xml()) -> binary(). xml_payload_type(Xml) -> case Xml of #xmlDocument{content = [#xmlElement{name = Atom}]} -> case lists:suffix("Response", atom_to_list(Atom)) of true -> <<"SAMLResponse">>; _ -> <<"SAMLRequest">> end; #xmlElement{name = Atom} -> case lists:suffix("Response", atom_to_list(Atom)) of true -> <<"SAMLResponse">>; _ -> <<"SAMLRequest">> end; _ -> <<"SAMLRequest">> end. %% @doc Unpack and parse a SAMLResponse with given encoding -spec decode_response(SAMLEncoding :: binary(), SAMLResponse :: binary()) -> #xmlDocument{}. decode_response(?deflate, SAMLResponse) -> XmlData = binary_to_list(zlib:unzip(base64:decode(SAMLResponse))), {Xml, _} = xmerl_scan:string(XmlData, [{namespace_conformant, true}]), Xml; decode_response(_, SAMLResponse) -> Data = base64:decode(SAMLResponse), XmlData = case (catch zlib:unzip(Data)) of {'EXIT', _} -> binary_to_list(Data); Bin -> binary_to_list(Bin) end, {Xml, _} = xmerl_scan:string(XmlData, [{namespace_conformant, true}]), Xml. %% @doc Encode a SAMLRequest (or SAMLResponse) as an HTTP-Redirect binding %% %% Returns the URI that should be the target of redirection. -spec encode_http_redirect(IDPTarget :: uri(), SignedXml :: xml(), Username :: undefined | string(), RelayState :: binary()) -> uri(). encode_http_redirect(IdpTarget, SignedXml, Username, RelayState) -> Type = xml_payload_type(SignedXml), Req = lists:flatten(xmerl:export([SignedXml], xmerl_xml)), Param = http_uri:encode(base64:encode_to_string(zlib:zip(Req))), RelayStateEsc = http_uri:encode(binary_to_list(RelayState)), FirstParamDelimiter = case lists:member($?, IdpTarget) of true -> "&"; false -> "?" end, Username_Part = redirect_username_part(Username), iolist_to_binary([IdpTarget, FirstParamDelimiter, "SAMLEncoding=", ?deflate, "&", Type, "=", Param, "&RelayState=", RelayStateEsc | Username_Part]). redirect_username_part(Username) when is_binary(Username), size(Username) > 0 -> ["&username=", http_uri:encode(binary_to_list(Username))]; redirect_username_part(_Other) -> []. %% @doc Encode a SAMLRequest (or SAMLResponse) as an HTTP-POST binding %% %% Returns the HTML document to be sent to the browser, containing a %% form and javascript to automatically submit it. -spec encode_http_post(IDPTarget :: uri(), SignedXml :: xml(), RelayState :: binary()) -> html_doc(). encode_http_post(IdpTarget, SignedXml, RelayState) -> Type = xml_payload_type(SignedXml), Req = lists:flatten(xmerl:export([SignedXml], xmerl_xml)), generate_post_html(Type, IdpTarget, base64:encode(Req), RelayState). generate_post_html(Type, Dest, Req, RelayState) -> iolist_to_binary([<<" POST data
>,Dest,<<"\"> >,Type,<<"\" value=\"">>,Req,<<"\" /> >,RelayState,<<"\" />
">>]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif.