From c3aca8cad4ac0385d65ca07a0eeca630baf0e763 Mon Sep 17 00:00:00 2001 From: handnot2 Date: Tue, 14 Nov 2017 20:26:39 -0800 Subject: [PATCH] Handle namespaces in IdP metadata XML. Fixes #7 --- CHANGELOG.md | 4 + lib/samly/esaml.ex | 3 + lib/samly/idp_data.ex | 416 ++++++++++++++++++-------- lib/samly/provider.ex | 8 +- lib/samly/sp_data.ex | 121 ++++---- mix.exs | 3 +- mix.lock | 3 +- test/data/azure_fed_metadata.xml | 212 +++++++++++++ test/data/onelogin_idp_metadata.xml | 49 +++ test/data/shibboleth_idp_metadata.xml | 211 +++++++++++++ test/data/simplesaml_idp_metadata.xml | 36 +++ test/data/testshib_metadata.xml | 302 +++++++++++++++++++ test/samly_idp_data_test.exs | 161 ++++++++++ test/samly_sp_data_test.exs | 59 ++++ test/samly_test.exs | 40 --- 15 files changed, 1404 insertions(+), 224 deletions(-) create mode 100644 test/data/azure_fed_metadata.xml create mode 100644 test/data/onelogin_idp_metadata.xml create mode 100644 test/data/shibboleth_idp_metadata.xml create mode 100644 test/data/simplesaml_idp_metadata.xml create mode 100644 test/data/testshib_metadata.xml create mode 100644 test/samly_idp_data_test.exs create mode 100644 test/samly_sp_data_test.exs delete mode 100644 test/samly_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4060238..ffdd5fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +### v0.8.2 + ++ Handle namespaces in Identity Provider Metadata XML file + ### v0.8.0 + Added support for multiple Identity Providers. Check issue: #4. diff --git a/lib/samly/esaml.ex b/lib/samly/esaml.ex index f01140e..fe8353a 100644 --- a/lib/samly/esaml.ex +++ b/lib/samly/esaml.ex @@ -5,6 +5,8 @@ defmodule Samly.Esaml do import Record, only: [defrecord: 2, extract: 2] @esaml_hrl "esaml/include/esaml.hrl" + @public_key_hrl "public_key/include/OTP-PUB-KEY.hrl" + defrecord :esaml_org, extract(:esaml_org, from_lib: @esaml_hrl) defrecord :esaml_contact, extract(:esaml_contact, from_lib: @esaml_hrl) defrecord :esaml_sp_metadata, extract(:esaml_sp_metadata, from_lib: @esaml_hrl) @@ -16,4 +18,5 @@ defmodule Samly.Esaml do defrecord :esaml_logoutresp, extract(:esaml_logoutresp, from_lib: @esaml_hrl) defrecord :esaml_response, extract(:esaml_response, from_lib: @esaml_hrl) defrecord :esaml_sp, extract(:esaml_sp, from_lib: @esaml_hrl) + defrecord :RSAPrivateKey, extract(:RSAPrivateKey, from_lib: @public_key_hrl) end diff --git a/lib/samly/idp_data.ex b/lib/samly/idp_data.ex index eff887a..836cabe 100644 --- a/lib/samly/idp_data.ex +++ b/lib/samly/idp_data.ex @@ -1,20 +1,17 @@ defmodule Samly.IdpData do @moduledoc false + import SweetXml require Logger require Samly.Esaml - alias Samly.{ConfigError, Esaml, Helper, IdpData, SpData} - - @boolean_attrs [ - :use_redirect_for_req, - :sign_requests, - :sign_metadata, - :signed_assertion_in_resp, - :signed_envelopes_in_resp - ] - - defstruct id: nil, - sp_id: nil, + alias Samly.{Esaml, Helper, IdpData, SpData} + + @type nameid_formats :: :esaml.name_format() + @type certs :: [binary()] + @type url :: nil | binary() + + defstruct id: "", + sp_id: "", base_url: nil, metadata_file: nil, pre_session_create_pipeline: nil, @@ -23,148 +20,321 @@ defmodule Samly.IdpData do sign_metadata: true, signed_assertion_in_resp: true, signed_envelopes_in_resp: true, + entity_id: "", + signed_requests: "", + certs: [], + sso_redirect_url: nil, + sso_post_url: nil, + slo_redirect_url: nil, + slo_post_url: nil, + nameid_format: :unknown, fingerprints: [], - esaml_idp_rec: nil, - esaml_sp_rec: nil + esaml_idp_rec: Esaml.esaml_idp_metadata(), + esaml_sp_rec: Esaml.esaml_sp(), + valid?: false @type t :: %__MODULE__{ - id: nil | String.t(), - sp_id: nil | String.t(), - base_url: nil | String.t(), - metadata_file: nil | String.t(), - pre_session_create_pipeline: nil | module, - use_redirect_for_req: boolean, - sign_requests: boolean, - sign_metadata: boolean, - signed_assertion_in_resp: boolean, - signed_envelopes_in_resp: boolean, - fingerprints: keyword(binary), - esaml_idp_rec: nil | tuple, - esaml_sp_rec: nil | tuple + id: binary(), + sp_id: binary(), + base_url: nil | binary(), + metadata_file: nil | binary(), + pre_session_create_pipeline: nil | module(), + use_redirect_for_req: boolean(), + sign_requests: boolean(), + sign_metadata: boolean(), + signed_assertion_in_resp: boolean(), + signed_envelopes_in_resp: boolean(), + entity_id: binary(), + signed_requests: binary(), + certs: certs(), + sso_redirect_url: url(), + sso_post_url: url(), + slo_redirect_url: url(), + slo_post_url: url(), + nameid_format: nameid_formats(), + fingerprints: [binary()], + esaml_idp_rec: :esaml_idp_metadata, + esaml_sp_rec: :esaml_sp, + valid?: boolean() } - @type id :: String.t() + @entdesc "md:EntityDescriptor" + @idpdesc "md:IDPSSODescriptor" + @signedreq "WantAuthnRequestsSigned" + @nameid "md:NameIDFormat" + @keydesc "md:KeyDescriptor" + @ssos "md:SingleSignOnService" + @slos "md:SingleLogoutService" + @redirect "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + @post "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - @spec load_identity_providers(list(map), %{required(id) => SpData.t()}, binary) :: %{ - required(id) => __MODULE__.t() - } - def load_identity_providers(prov_config, service_providers, base_url) do + @entity_id_selector ~x"//#{@entdesc}/@entityID"sl + @nameid_format_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@nameid}/text()"s + @req_signed_selector ~x"//#{@entdesc}/#{@idpdesc}/@#{@signedreq}"s + @sso_redirect_url_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@ssos}[@Binding = '#{@redirect}']/@Location"s + @sso_post_url_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@ssos}[@Binding = '#{@post}']/@Location"s + @slo_redirect_url_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@slos}[@Binding = '#{@redirect}']/@Location"s + @slo_post_url_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@slos}[@Binding = '#{@post}']/@Location"s + @signing_keys_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@keydesc}[@use != 'encryption']"l + @enc_keys_selector ~x"//#{@entdesc}/#{@idpdesc}/#{@keydesc}[@use = 'encryption']"l + @cert_selector ~x"./ds:KeyInfo/ds:X509Data/ds:X509Certificate/text()"s + + @type id :: binary() + + @spec load_providers([map], %{required(id()) => %SpData{}}) :: + %{required(id()) => %IdpData{}} | no_return() + def load_providers(prov_config, service_providers) do prov_config - |> Enum.map(fn idp -> load_idp_data(idp, service_providers, base_url) end) + |> Enum.map(fn idp_config -> load_provider(idp_config, service_providers) end) + |> Enum.filter(fn idp_data -> idp_data.valid? end) + |> Enum.map(fn idp_data -> {idp_data.id, idp_data} end) |> Enum.into(%{}) end - @default_idp_metadata_file "idp_metadata.xml" - - @spec load_idp_data(map, %{required(id) => SpData.t()}, binary) :: {id, IdpData.t()} | no_return - defp load_idp_data(%{} = idp_entry, service_providers, default_base_url) do - with idp_id when idp_id != nil <- Map.get(idp_entry, :id), - base_url when base_url == nil or is_binary(base_url) <- - Map.get(idp_entry, :base_url, default_base_url), - metadata_file when metadata_file != nil <- - Map.get(idp_entry, :metadata_file, @default_idp_metadata_file), - pl when pl == nil or is_atom(pl) <- Map.get(idp_entry, :pre_session_create_pipeline), - {:reading, {:ok, xml}} <- {:reading, File.read(metadata_file)}, - {:parsing, {:ok, mdt}} <- {:parsing, idp_metadata_from_xml(xml)}, - sp_id when sp_id != nil <- Map.get(idp_entry, :sp_id, nil), - sp when sp != nil <- Map.get(service_providers, sp_id, nil) do - idp = - @boolean_attrs - |> Enum.reduce(%__MODULE__{}, fn attr, idp -> - v = Map.get(idp_entry, attr) - if is_boolean(v), do: Map.put(idp, attr, v), else: idp - end) - - idp = %__MODULE__{ - idp - | id: idp_id, - sp_id: sp_id, - base_url: base_url, - metadata_file: metadata_file, - pre_session_create_pipeline: pl, - fingerprints: idp_cert_fingerprints(mdt), - esaml_idp_rec: mdt - } - - {idp.id, %__MODULE__{idp | esaml_sp_rec: get_esaml_sp_rec(sp, idp, base_url)}} + @spec load_provider(map(), %{required(id()) => %SpData{}}) :: %IdpData{} | no_return + def load_provider(idp_config, service_providers) do + %IdpData{} + |> save_idp_config(idp_config) + |> load_metadata(idp_config) + |> update_esaml_recs(service_providers, idp_config) + end + + @spec save_idp_config(%IdpData{}, map()) :: %IdpData{} + defp save_idp_config(idp_data, %{id: id, sp_id: sp_id} = opts_map) + when is_binary(id) and is_binary(sp_id) do + %IdpData{idp_data | id: id, sp_id: sp_id, base_url: Map.get(opts_map, :base_url)} + |> set_metadata_file(opts_map) + |> set_pipeline(opts_map) + |> set_boolean_attr(opts_map, :use_redirect_for_req) + |> set_boolean_attr(opts_map, :sign_requests) + |> set_boolean_attr(opts_map, :sign_metadata) + |> set_boolean_attr(opts_map, :signed_assertion_in_resp) + |> set_boolean_attr(opts_map, :signed_envelopes_in_resp) + end + + @spec load_metadata(%IdpData{}, map()) :: %IdpData{} + defp load_metadata(idp_data, _opts_map) do + with {:reading, {:ok, raw_xml}} <- {:reading, File.read(idp_data.metadata_file)}, + {:parsing, {:ok, idp_data}} <- {:parsing, from_xml(raw_xml, idp_data)} do + idp_data else {:reading, {:error, reason}} -> Logger.error("[Samly] Failed to read metadata_file: #{inspect(reason)}") - raise ConfigError, idp_entry + idp_data {:parsing, {:error, reason}} -> Logger.error("[Samly] Invalid metadata_file content: #{inspect(reason)}") - raise ConfigError, idp_entry + idp_data + end + end + + @spec update_esaml_recs(%IdpData{}, %{required(id()) => %SpData{}}, map()) :: %IdpData{} + defp update_esaml_recs(idp_data, service_providers, opts_map) do + case Map.get(service_providers, idp_data.sp_id) do + %SpData{} = sp -> + idp_data = %IdpData{idp_data | esaml_idp_rec: to_esaml_idp_metadata(idp_data, opts_map)} + idp_data = %IdpData{idp_data | esaml_sp_rec: get_esaml_sp(sp, idp_data)} + %IdpData{idp_data | valid?: true} _ -> - raise ConfigError, idp_entry + Logger.error("[Samly] Unknown/invalid sp_id: #{idp_data.sp_id}") + idp_data end end - defp idp_metadata_from_xml(metadata_xml) when is_binary(metadata_xml) do - try do - {xml, _} = - metadata_xml - |> String.to_charlist() - |> :xmerl_scan.string(namespace_conformant: true) + @default_metadata_file "idp_metadata.xml" - :esaml.decode_idp_metadata(xml) - rescue - _ -> {:error, :invalid_metadata_xml} - end + @spec set_metadata_file(%IdpData{}, map()) :: %IdpData{} + defp set_metadata_file(%IdpData{} = idp_data, %{} = opts_map) do + %IdpData{idp_data | metadata_file: Map.get(opts_map, :metadata_file, @default_metadata_file)} + end + + @spec set_pipeline(%IdpData{}, map()) :: %IdpData{} + defp set_pipeline(%IdpData{} = idp_data, %{} = opts_map) do + pipeline = Map.get(opts_map, :pre_session_create_pipeline) + %IdpData{idp_data | pre_session_create_pipeline: pipeline} + end + + @spec set_boolean_attr(%IdpData{}, map(), atom()) :: %IdpData{} + defp set_boolean_attr(%IdpData{} = idp_data, %{} = opts_map, attr_name) when is_atom(attr_name) do + v = Map.get(opts_map, attr_name) + if is_boolean(v), do: Map.put(idp_data, attr_name, v), else: idp_data + end + + @spec from_xml(binary, %IdpData{}) :: {:ok, %IdpData{}} + def from_xml(metadata_xml, idp_data) when is_binary(metadata_xml) do + xml_opts = [ + space: :normalize, + namespace_conformant: true, + comments: false, + default_attrs: true + ] + + md_xml = SweetXml.parse(metadata_xml, xml_opts) + signing_certs = get_signing_certs(md_xml) + + {:ok, %IdpData{ + idp_data + | entity_id: get_entity_id(md_xml), + signed_requests: get_req_signed(md_xml), + certs: signing_certs, + fingerprints: idp_cert_fingerprints(signing_certs), + sso_redirect_url: get_sso_redirect_url(md_xml), + sso_post_url: get_sso_post_url(md_xml), + slo_redirect_url: get_slo_redirect_url(md_xml), + slo_post_url: get_slo_post_url(md_xml), + nameid_format: get_nameid_format(md_xml) + }} end - defp idp_cert_fingerprints(idp_metadata) do - fingerprint = - idp_metadata - |> Esaml.esaml_idp_metadata(:certificate) - |> cert_fingerprint() - |> String.to_charlist() + # @spec to_esaml_idp_metadata(IdpData.t(), map()) :: :esaml_idp_metadata + defp to_esaml_idp_metadata(%IdpData{} = idp_data, %{} = idp_config) do + {sso_url, slo_url} = get_sso_slo_urls(idp_data, idp_config) + sso_url = if sso_url, do: String.to_charlist(sso_url), else: [] + slo_url = if slo_url, do: String.to_charlist(slo_url), else: :undefined - [fingerprint] |> :esaml_util.convert_fingerprints() + Esaml.esaml_idp_metadata( + entity_id: String.to_charlist(idp_data.entity_id), + login_location: sso_url, + logout_location: slo_url, + name_format: idp_data.nameid_format + ) + end + + defp get_sso_slo_urls(%IdpData{} = idp_data, %{use_redirect_for_req: true}) do + {idp_data.sso_redirect_url, idp_data.slo_redirect_url} + end + + defp get_sso_slo_urls(%IdpData{} = idp_data, %{use_redirect_for_req: false}) do + {idp_data.sso_post_url, idp_data.slo_post_url} + end + + defp get_sso_slo_urls(%IdpData{} = idp_data, _opts_map) do + { + idp_data.sso_post_url || idp_data.sso_redirect_url, + idp_data.slo_post_url || idp_data.slo_redirect_url + } + end + + @spec nameid_map(nil | binary) :: nameid_formats() + defp nameid_map("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"), do: :email + defp nameid_map("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"), do: :x509 + defp nameid_map("urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"), do: :krb + defp nameid_map("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"), do: :persistent + defp nameid_map("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"), do: :transient + + defp nameid_map("urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName"), + do: :windows + + defp nameid_map(_unknown), do: :unknown + + @spec idp_cert_fingerprints(certs()) :: [binary()] + defp idp_cert_fingerprints(certs) when is_list(certs) do + certs + |> Enum.map(&Base.decode64!/1) + |> Enum.map(&cert_fingerprint/1) + |> Enum.map(&String.to_charlist/1) + |> :esaml_util.convert_fingerprints() end defp cert_fingerprint(dercert) do "sha256:" <> (:sha256 |> :crypto.hash(dercert) |> Base.encode64()) end - def get_esaml_sp_rec(%SpData{} = sp, %IdpData{} = idp, base_url) do - entity_id = - case sp.entity_id do - nil -> :undefined - :undefined -> :undefined - id -> String.to_charlist(id) - end - + # @spec get_esaml_sp(%SpData{}, %IdpData{}) :: :esaml_sp + defp get_esaml_sp(%SpData{} = sp_data, %IdpData{} = idp_data) do idp_id_from = Application.get_env(:samly, :idp_id_from) - path_segment_idp_id = if idp_id_from == :subdomain, do: nil, else: idp.id - - sp_rec = - Esaml.esaml_sp( - key: sp.key, - certificate: sp.cert, - sp_sign_requests: idp.sign_requests, - sp_sign_metadata: idp.sign_metadata, - idp_signs_envelopes: idp.signed_envelopes_in_resp, - idp_signs_assertions: idp.signed_assertion_in_resp, - trusted_fingerprints: idp.fingerprints, - metadata_uri: Helper.get_metadata_uri(base_url, path_segment_idp_id), - consume_uri: Helper.get_consume_uri(base_url, path_segment_idp_id), - logout_uri: Helper.get_logout_uri(base_url, path_segment_idp_id), - entity_id: entity_id, - org: - Esaml.esaml_org( - name: String.to_charlist(sp.org_name), - displayname: String.to_charlist(sp.org_displayname), - url: String.to_charlist(sp.org_url) - ), - tech: - Esaml.esaml_contact( - name: String.to_charlist(sp.contact_name), - email: String.to_charlist(sp.contact_email) - ) - ) - - sp_rec + path_segment_idp_id = if idp_id_from == :subdomain, do: nil, else: idp_data.id + + Esaml.esaml_sp( + org: + Esaml.esaml_org( + name: String.to_charlist(sp_data.org_name), + displayname: String.to_charlist(sp_data.org_displayname), + url: String.to_charlist(sp_data.org_url) + ), + tech: + Esaml.esaml_contact( + name: String.to_charlist(sp_data.contact_name), + email: String.to_charlist(sp_data.contact_email) + ), + key: sp_data.key, + certificate: sp_data.cert, + sp_sign_requests: idp_data.sign_requests, + sp_sign_metadata: idp_data.sign_metadata, + idp_signs_envelopes: idp_data.signed_envelopes_in_resp, + idp_signs_assertions: idp_data.signed_assertion_in_resp, + trusted_fingerprints: idp_data.fingerprints, + metadata_uri: Helper.get_metadata_uri(idp_data.base_url, path_segment_idp_id), + consume_uri: Helper.get_consume_uri(idp_data.base_url, path_segment_idp_id), + logout_uri: Helper.get_logout_uri(idp_data.base_url, path_segment_idp_id), + entity_id: String.to_charlist(sp_data.entity_id) + ) + end + + @spec get_entity_id(:xmlElement) :: binary() + def get_entity_id(md_elem) do + md_elem |> xpath(@entity_id_selector |> add_ns()) |> hd() |> String.trim() + end + + @spec get_nameid_format(:xmlElement) :: nameid_formats() + def get_nameid_format(md_elem) do + get_data(md_elem, @nameid_format_selector) |> nameid_map() + end + + @spec get_req_signed(:xmlElement) :: binary() + def get_req_signed(md_elem), do: get_data(md_elem, @req_signed_selector) + + @spec get_signing_certs(:xmlElement) :: certs() + def get_signing_certs(md_elem), do: get_certs(md_elem, @signing_keys_selector) + + @spec get_enc_certs(:xmlElement) :: certs() + def get_enc_certs(md_elem), do: get_certs(md_elem, @enc_keys_selector) + + @spec get_certs(:xmlElement, %SweetXpath{}) :: certs() + defp get_certs(md_elem, key_selector) do + md_elem + |> xpath(key_selector |> add_ns()) + |> Enum.map(fn e -> + # Extract base64 encoded cert from XML (strip away any whitespace) + cert = xpath(e, @cert_selector |> add_ns()) + + cert + |> String.split() + |> Enum.map(&String.trim/1) + |> Enum.join() + end) + end + + @spec get_sso_redirect_url(:xmlElement) :: url() + def get_sso_redirect_url(md_elem), do: get_url(md_elem, @sso_redirect_url_selector) + + @spec get_sso_post_url(:xmlElement) :: url() + def get_sso_post_url(md_elem), do: get_url(md_elem, @sso_post_url_selector) + + @spec get_slo_redirect_url(:xmlElement) :: url() + def get_slo_redirect_url(md_elem), do: get_url(md_elem, @slo_redirect_url_selector) + + @spec get_slo_post_url(:xmlElement) :: url() + def get_slo_post_url(md_elem), do: get_url(md_elem, @slo_post_url_selector) + + @spec get_url(:xmlElement, %SweetXpath{}) :: url() + defp get_url(md_elem, selector) do + case get_data(md_elem, selector) do + "" -> nil + url -> url + end + end + + @spec get_data(:xmlElement, %SweetXpath{}) :: binary() + def get_data(md_elem, selector) do + md_elem |> xpath(selector |> add_ns()) |> String.trim() + end + + @spec add_ns(%SweetXpath{}) :: %SweetXpath{} + defp add_ns(xpath) do + xpath + |> SweetXml.add_namespace("md", "urn:oasis:names:tc:SAML:2.0:metadata") + |> SweetXml.add_namespace("ds", "http://www.w3.org/2000/09/xmldsig#") end end diff --git a/lib/samly/provider.ex b/lib/samly/provider.ex index a588dcd..133a0b3 100644 --- a/lib/samly/provider.ex +++ b/lib/samly/provider.ex @@ -53,14 +53,10 @@ defmodule Samly.Provider do Application.put_env(:samly, :idp_id_from, idp_id_from) - service_providers = Samly.SpData.load_service_providers(opts[:service_providers] || []) + service_providers = Samly.SpData.load_providers(opts[:service_providers] || []) identity_providers = - Samly.IdpData.load_identity_providers( - opts[:identity_providers] || [], - service_providers, - opts[:base_url] - ) + Samly.IdpData.load_providers(opts[:identity_providers] || [], service_providers) Application.put_env(:samly, :service_providers, service_providers) Application.put_env(:samly, :identity_providers, identity_providers) diff --git a/lib/samly/sp_data.ex b/lib/samly/sp_data.ex index 2947951..cde934f 100644 --- a/lib/samly/sp_data.ex +++ b/lib/samly/sp_data.ex @@ -2,35 +2,38 @@ defmodule Samly.SpData do @moduledoc false require Logger - alias Samly.ConfigError + require Samly.Esaml + alias Samly.SpData - defstruct id: nil, - entity_id: :undefined, - certfile: nil, - keyfile: nil, - contact_name: nil, - contact_email: nil, - org_name: nil, - org_displayname: nil, - org_url: nil, - cert: nil, - key: nil + defstruct id: "", + entity_id: "", + certfile: "", + keyfile: "", + contact_name: "", + contact_email: "", + org_name: "", + org_displayname: "", + org_url: "", + cert: :undefined, + key: :undefined, + valid?: true @type t :: %__MODULE__{ - id: nil | String.t(), - entity_id: nil | :undefined | String.t(), - certfile: nil | String.t(), - keyfile: nil | String.t(), - contact_name: nil | String.t(), - contact_email: nil | String.t(), - org_name: nil | String.t(), - org_displayname: nil | String.t(), - org_url: nil | String.t(), - cert: nil | binary, - key: nil | tuple + id: binary(), + entity_id: binary(), + certfile: binary(), + keyfile: binary(), + contact_name: binary(), + contact_email: binary(), + org_name: binary(), + org_displayname: binary(), + org_url: binary(), + cert: :undefined | binary(), + key: :undefined | :RSAPrivateKey, + valid?: boolean() } - @type id :: String.t() + @type id :: binary @default_contact_name "Samly SP Admin" @default_contact_email "admin@samly" @@ -38,53 +41,65 @@ defmodule Samly.SpData do @default_org_displayname "SAML SP built with Samly" @default_org_url "https://github.com/handnot2/samly" - @spec load_service_providers(list(map)) :: %{required(id) => t} - def load_service_providers(providers) do - providers - |> Enum.map(&load_sp/1) + @spec load_providers(list(map)) :: %{required(id) => t} + def load_providers(prov_configs) do + prov_configs + |> Enum.map(&load_provider/1) + |> Enum.filter(fn sp_data -> sp_data.valid? end) + |> Enum.map(fn sp_data -> {sp_data.id, sp_data} end) |> Enum.into(%{}) end - @spec load_sp(map) :: {String.t(), t} | no_return - defp load_sp(%{} = provider) do - sp = %__MODULE__{ - id: Map.get(provider, :id, nil), - entity_id: Map.get(provider, :entity_id, nil), - certfile: Map.get(provider, :certfile, nil), - keyfile: Map.get(provider, :keyfile, nil), - contact_name: Map.get(provider, :contact_name, @default_contact_name), - contact_email: Map.get(provider, :contact_email, @default_contact_email), - org_name: Map.get(provider, :org_name, @default_org_name), - org_displayname: Map.get(provider, :org_displayname, @default_org_displayname), - org_url: Map.get(provider, :org_url, @default_org_url) + @spec load_provider(map) :: %SpData{} | no_return + def load_provider(%{} = opts_map) do + sp_data = %__MODULE__{ + id: Map.get(opts_map, :id, ""), + entity_id: Map.get(opts_map, :entity_id, ""), + certfile: Map.get(opts_map, :certfile, ""), + keyfile: Map.get(opts_map, :keyfile, ""), + contact_name: Map.get(opts_map, :contact_name, @default_contact_name), + contact_email: Map.get(opts_map, :contact_email, @default_contact_email), + org_name: Map.get(opts_map, :org_name, @default_org_name), + org_displayname: Map.get(opts_map, :org_displayname, @default_org_displayname), + org_url: Map.get(opts_map, :org_url, @default_org_url) } - sp = %__MODULE__{sp | cert: load_cert(sp.certfile, sp.id), key: load_key(sp.keyfile, sp.id)} + sp_data |> set_id(opts_map) |> load_cert(opts_map) |> load_key(opts_map) + end - if sp.id == nil || sp.certfile == nil || sp.keyfile == nil do - raise ConfigError, provider - end + @spec set_id(%SpData{}, map()) :: %SpData{} + defp set_id(%SpData{} = sp_data, %{} = opts_map) do + case Map.get(opts_map, :id, "") do + "" -> + Logger.error("[Samly] Invalid SP Config: #{inspect(opts_map)}") + %SpData{sp_data | valid?: false} - {sp.id, sp} + id -> + %SpData{sp_data | id: id} + end end - defp load_key(file, label) do + @spec load_cert(%SpData{}, map()) :: %SpData{} + defp load_cert(%SpData{certfile: certfile} = sp_data, %{} = opts_map) do try do - file |> :esaml_util.load_private_key() + cert = :esaml_util.load_certificate(certfile) + %SpData{sp_data | cert: cert} rescue _error -> - Logger.error("[Samly] Failed load SP keyfile: #{label}:#{file}") - nil + Logger.error("[Samly] Failed load SP certfile: #{inspect(opts_map)}") + %SpData{sp_data | valid?: false} end end - defp load_cert(file, label) do + @spec load_key(%SpData{}, map()) :: %SpData{} + defp load_key(%SpData{keyfile: keyfile} = sp_data, %{} = opts_map) do try do - file |> :esaml_util.load_certificate() + key = :esaml_util.load_private_key(keyfile) + %SpData{sp_data | key: key} rescue _error -> - Logger.error("[Samly] Failed load SP certfile: #{label}:#{file}") - nil + Logger.error("[Samly] Failed load SP keyfile: #{inspect(opts_map)}") + %SpData{sp_data | key: :undefined, valid?: false} end end end diff --git a/mix.exs b/mix.exs index 71a00f1..e986a42 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Samly.Mixfile do use Mix.Project - @version "0.8.1" + @version "0.8.2" @description "SAML SP SSO made easy" @source_url "https://github.com/handnot2/samly" @@ -30,6 +30,7 @@ defmodule Samly.Mixfile do [ {:plug, "~> 1.4"}, {:esaml, "~> 3.1"}, + {:sweet_xml, "~> 0.6"}, {:ex_doc, "~> 0.18", only: :dev}, {:inch_ex, "~> 0.5", only: :docs} ] diff --git a/mix.lock b/mix.lock index 41dec00..68f46f4 100644 --- a/mix.lock +++ b/mix.lock @@ -7,4 +7,5 @@ "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"}, "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}} + "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, + "sweet_xml": {:hex, :sweet_xml, "0.6.5", "dd9cde443212b505d1b5f9758feb2000e66a14d3c449f04c572f3048c66e6697", [:mix], [], "hexpm"}} diff --git a/test/data/azure_fed_metadata.xml b/test/data/azure_fed_metadata.xml new file mode 100644 index 0000000..0bc9549 --- /dev/null +++ b/test/data/azure_fed_metadata.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + PxBXxVXHYmtelxnhr2ewHTGKEkuviPF429grQa1WNIw= + + + o/8AXiWeX8TpD76ix+hlpX//CnBjL4i5xxniJLDTqcKsVBCa544vBOJsCFKS4Syd7AoXD5Yhj6PPzpfUq6kR+vQRZS1w5/XQ7juSv/GCSLSJaL3Zhhm2Uej/jtJf+Wxq8yrsTXyECPfqx8XTD6RIbEOGcn9Ug3wkuUpnzf3Kgl8PNUNhYa+bKyehsUNmPr5geFqxk/o1Pq/prVFy7gBGXEGPMHigkczYya2vxhZCpLkKUcqm3JlQvPdCFuH060F5/MSKxEJttt/KQIer7fvLD832Yw1LeI+aarJxOXEAgJpbohlE5Wzxs2GvrSvzCqiECTCXZOVAdQ+LGIHJMvehcA== + + + MIIDBTCCAe2gAwIBAgIQQm0sN9lDrblM/7U/vYMVmTANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MDkwNjAwMDAwMFoXDTE5MDkwNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAONDCuIodN3I6J7+Wc9j5sY5MgLkGZZuUxLApHEmIhgWIVSSyMgM+eu0Nrr5FuIylJrKg/SHOJeLwFuYZOEMfRUVcKRewUTIBKmILqlg5+d56ztYZATuhm60B5bp1RUlUQMZ0GTepz9xpS9hO4Mv5GRBxR5xoBKCOF74xfVuHNkk/wnbV/Yggwt82FrGm8CpiXjbmFzy61Vmp6KbFDadJDij2mmICSwzAJEoVvklLzcL/4Vf03v3l8aBkI/SPfDjm6k55dqeDm+nEIN/baWbs7M2WtNkJXNePy8dR0GkdlhbgESEIJdSVLWeBFt8eV0JQqUXcPCjhwpJE89jrHhwyCECAwEAAaMhMB8wHQYDVR0OBBYEFNISA3dtAzEd0muqNDbWm3kvNlJDMA0GCSqGSIb3DQEBCwUAA4IBAQClLLoAvg3dYqWO63Z6O5L7yataGcilmL3YUqCFoRKsuwej2T833qyc1iLG0iWCGeWAUonKXuGwfCSSSj2E3ksLtgV6xmuMl+NuVPpRpQo+38n+OxUoWKu963dMxnORFENEqKW0pMioipMk/HBaW3aJWyH1oT2rZ3KhFm67SFjKscF8ShAE82tQQIFwEFAXjMItW2oZVGDz3vDOaJN5xC8rfA6xkXTdcCuzy74SalKkLhpBO8S3XIOBVRZw+l0Koog8YNqhsvGsGS+hGXXNlCZTg0I1tR3g2DcSuHRcuTZKh7Z7XPPsDgleNirtvYFEvdvD4K2I7gb2H1xQn87oYAIX + + + + + + + + + MIIDBTCCAe2gAwIBAgIQQm0sN9lDrblM/7U/vYMVmTANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MDkwNjAwMDAwMFoXDTE5MDkwNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAONDCuIodN3I6J7+Wc9j5sY5MgLkGZZuUxLApHEmIhgWIVSSyMgM+eu0Nrr5FuIylJrKg/SHOJeLwFuYZOEMfRUVcKRewUTIBKmILqlg5+d56ztYZATuhm60B5bp1RUlUQMZ0GTepz9xpS9hO4Mv5GRBxR5xoBKCOF74xfVuHNkk/wnbV/Yggwt82FrGm8CpiXjbmFzy61Vmp6KbFDadJDij2mmICSwzAJEoVvklLzcL/4Vf03v3l8aBkI/SPfDjm6k55dqeDm+nEIN/baWbs7M2WtNkJXNePy8dR0GkdlhbgESEIJdSVLWeBFt8eV0JQqUXcPCjhwpJE89jrHhwyCECAwEAAaMhMB8wHQYDVR0OBBYEFNISA3dtAzEd0muqNDbWm3kvNlJDMA0GCSqGSIb3DQEBCwUAA4IBAQClLLoAvg3dYqWO63Z6O5L7yataGcilmL3YUqCFoRKsuwej2T833qyc1iLG0iWCGeWAUonKXuGwfCSSSj2E3ksLtgV6xmuMl+NuVPpRpQo+38n+OxUoWKu963dMxnORFENEqKW0pMioipMk/HBaW3aJWyH1oT2rZ3KhFm67SFjKscF8ShAE82tQQIFwEFAXjMItW2oZVGDz3vDOaJN5xC8rfA6xkXTdcCuzy74SalKkLhpBO8S3XIOBVRZw+l0Koog8YNqhsvGsGS+hGXXNlCZTg0I1tR3g2DcSuHRcuTZKh7Z7XPPsDgleNirtvYFEvdvD4K2I7gb2H1xQn87oYAIX + + + + + + + + MIIDBTCCAe2gAwIBAgIQG3bMDDyO6q1GrI5sdZXCrTANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MTAxODAwMDAwMFoXDTE5MTAxOTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPkg1ILQtpwFPPqiBBV3wXd89KoDW5NA/DiBQPZYINe//g6gdrOcnT6JjS5mnntXURsvlC8a8QPNCeX9zuxi0MvEoXjuyROJwnAhZyHmkWQh850pUiFTICCuDJXBrMiMdLYfeaJ9C0SApw2OhDiCED77Hh3oxuP49ooS5My96/b3PM76KC/GTv4VM5ydPOXpvUpLzMoavQdOGUCjUYU2z9twCYXmCLtwOiNWFW9yqof6tSAxEZRezq0Hi9qU6DF0MIp0TT6FKvUC8ui4pcYX7hZ6U9yOXJVB0+6HXdpGQ42sypvoK1h8mcRRLA5Ad/ox6YIJ39j0tJm5y+4H37NvHUCAwEAAaMhMB8wHQYDVR0OBBYEFCO/QGygHvo1YiKeQVulJFVxO9dnMA0GCSqGSIb3DQEBCwUAA4IBAQBZTJK52b+QnBbLicaT5uxC3JnRwps6RovQzPZRBLpxATq4kj5jNMhegb5fx4Rc1dpepXWJHAGzD0Nwsab/vYSx7iqyU02IAUkwt3k7XyYK17R6gTgUAxEFBfRKM3PSFiH0b3tGA+baLT3BdY5U6ZqjxhFA0Rh7tzPZM1TO2WtENk3hKmG5r5GKECnwa5NiE5jxN+d6i8dqM+vMqDvIrfqTA3ooQWXpvs0I9YUWl/LjBNFqyY3rMzxLX3STobLFf8ayHIvVmtiFSM3glCO+8UtGKLwNnPFIfYx3VstJjOO8rjP0Z/oaZwhD0A7MrNp4ztwmXAIzYkGTVyDsNuQJgi1e + + + + + + + + MIIDKDCCAhCgAwIBAgIQBHJvVNxP1oZO4HYKh+rypDANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDExhsb2dpbi5taWNyb3NvZnRvbmxpbmUudXMwHhcNMTYxMTE2MDgwMDAwWhcNMTgxMTE2MDgwMDAwWjAjMSEwHwYDVQQDExhsb2dpbi5taWNyb3NvZnRvbmxpbmUudXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChn5BCs24Hh6L0BNitPrV5s+2/DBhaeytOmnghJKnqeJlhv3ZczShRM2Cp38LW8Y3wn7L3AJtolaSkF/joKN1l6GupzM+HOEdq7xZxFehxIHW7+25mG/WigBnhsBzLv1SR4uIbrQeS5M0kkLwJ9pOnVH3uzMGG6TRXPnK3ivlKl97AiUEKdlRjCQNLXvYf1ZqlC77c/ZCOHSX4kvIKR2uG+LNlSTRq2rn8AgMpFT4DSlEZz4RmFQvQupQzPpzozaz/gadBpJy/jgDmJlQMPXkHp7wClvbIBGiGRaY6eZFxNV96zwSR/GPNkTObdw2S8/SiAgvIhIcqWTPLY6aVTqJfAgMBAAGjWDBWMFQGA1UdAQRNMEuAEDUj0BrjP0RTbmoRPTRMY3WhJTAjMSEwHwYDVQQDExhsb2dpbi5taWNyb3NvZnRvbmxpbmUudXOCEARyb1TcT9aGTuB2Cofq8qQwDQYJKoZIhvcNAQELBQADggEBAGnLhDHVz2gLDiu9L34V3ro/6xZDiSWhGyHcGqky7UlzQH3pT5so8iF5P0WzYqVtogPsyC2LPJYSTt2vmQugD4xlu/wbvMFLcV0hmNoTKCF1QTVtEQiAiy0Aq+eoF7Al5fV1S3Sune0uQHimuUFHCmUuF190MLcHcdWnPAmzIc8fv7quRUUsExXmxSX2ktUYQXzqFyIOSnDCuWFm6tpfK5JXS8fW5bpqTlrysXXz/OW/8NFGq/alfjrya4ojrOYLpunGriEtNPwK7hxj1AlCYEWaRHRXaUIW1ByoSff/6Y6+ZhXPUe0cDlNRt/qIz5aflwO7+W8baTS4O8m/icu7ItE= + + + + + + + Name + The mutable display name of the user. + + + Subject + An immutable, globally unique, non-reusable identifier of the user that is unique to the application for which a token is issued.Given Name + First name of the user. + + + Surname + Last name of the user. + + + Display Name + Display name of the user. + + + Nick Name + Nick name of the user. + + + Authentication Instant + The time (UTC) when the user is authenticated to Windows Azure Active Directory. + + + Authentication Method + The method that Windows Azure Active Directory uses to authenticate users. + + + ObjectIdentifier + Primary identifier for the user in the directory. Immutable, globally unique, non-reusable. + + + TenantId + Identifier for the user's tenant. + + + IdentityProvider + Identity provider for the user. + + + Email + Email address of the user. + + + Groups + Groups of the user. + + + External Access Token + Access token issued by external identity provider. + + + External Access Token Expiration + UTC expiration time of access token issued by external identity provider. + + + External OpenID 2.0 Identifier + OpenID 2.0 identifier issued by external identity provider. + + + GroupsOverageClaim + Issued when number of user's group claims exceeds return limit. + + + Role Claim + Roles that the user or Service Principal is attached to + + + RoleTemplate Id Claim + Role template id of the Built-in Directory Roles that the user is a member of + + + + + https://login.microsoftonline.com/common/wsfed + + + + + https://login.microsoftonline.com/common/wsfed + + + + MIIDBTCCAe2gAwIBAgIQQm0sN9lDrblM/7U/vYMVmTANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MDkwNjAwMDAwMFoXDTE5MDkwNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAONDCuIodN3I6J7+Wc9j5sY5MgLkGZZuUxLApHEmIhgWIVSSyMgM+eu0Nrr5FuIylJrKg/SHOJeLwFuYZOEMfRUVcKRewUTIBKmILqlg5+d56ztYZATuhm60B5bp1RUlUQMZ0GTepz9xpS9hO4Mv5GRBxR5xoBKCOF74xfVuHNkk/wnbV/Yggwt82FrGm8CpiXjbmFzy61Vmp6KbFDadJDij2mmICSwzAJEoVvklLzcL/4Vf03v3l8aBkI/SPfDjm6k55dqeDm+nEIN/baWbs7M2WtNkJXNePy8dR0GkdlhbgESEIJdSVLWeBFt8eV0JQqUXcPCjhwpJE89jrHhwyCECAwEAAaMhMB8wHQYDVR0OBBYEFNISA3dtAzEd0muqNDbWm3kvNlJDMA0GCSqGSIb3DQEBCwUAA4IBAQClLLoAvg3dYqWO63Z6O5L7yataGcilmL3YUqCFoRKsuwej2T833qyc1iLG0iWCGeWAUonKXuGwfCSSSj2E3ksLtgV6xmuMl+NuVPpRpQo+38n+OxUoWKu963dMxnORFENEqKW0pMioipMk/HBaW3aJWyH1oT2rZ3KhFm67SFjKscF8ShAE82tQQIFwEFAXjMItW2oZVGDz3vDOaJN5xC8rfA6xkXTdcCuzy74SalKkLhpBO8S3XIOBVRZw+l0Koog8YNqhsvGsGS+hGXXNlCZTg0I1tR3g2DcSuHRcuTZKh7Z7XPPsDgleNirtvYFEvdvD4K2I7gb2H1xQn87oYAIXMIIDBTCCAe2gAwIBAgIQG3bMDDyO6q1GrI5sdZXCrTANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MTAxODAwMDAwMFoXDTE5MTAxOTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPkg1ILQtpwFPPqiBBV3wXd89KoDW5NA/DiBQPZYINe//g6gdrOcnT6JjS5mnntXURsvlC8a8QPNCeX9zuxi0MvEoXjuyROJwnAhZyHmkWQh850pUiFTICCuDJXBrMiMdLYfeaJ9C0SApw2OhDiCED77Hh3oxuP49ooS5My96/b3PM76KC/GTv4VM5ydPOXpvUpLzMoavQdOGUCjUYU2z9twCYXmCLtwOiNWFW9yqof6tSAxEZRezq0Hi9qU6DF0MIp0TT6FKvUC8ui4pcYX7hZ6U9yOXJVB0+6HXdpGQ42sypvoK1h8mcRRLA5Ad/ox6YIJ39j0tJm5y+4H37NvHUCAwEAAaMhMB8wHQYDVR0OBBYEFCO/QGygHvo1YiKeQVulJFVxO9dnMA0GCSqGSIb3DQEBCwUAA4IBAQBZTJK52b+QnBbLicaT5uxC3JnRwps6RovQzPZRBLpxATq4kj5jNMhegb5fx4Rc1dpepXWJHAGzD0Nwsab/vYSx7iqyU02IAUkwt3k7XyYK17R6gTgUAxEFBfRKM3PSFiH0b3tGA+baLT3BdY5U6ZqjxhFA0Rh7tzPZM1TO2WtENk3hKmG5r5GKECnwa5NiE5jxN+d6i8dqM+vMqDvIrfqTA3ooQWXpvs0I9YUWl/LjBNFqyY3rMzxLX3STobLFf8ayHIvVmtiFSM3glCO+8UtGKLwNnPFIfYx3VstJjOO8rjP0Z/oaZwhD0A7MrNp4ztwmXAIzYkGTVyDsNuQJgi1eMIIDKDCCAhCgAwIBAgIQBHJvVNxP1oZO4HYKh+rypDANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDExhsb2dpbi5taWNyb3NvZnRvbmxpbmUudXMwHhcNMTYxMTE2MDgwMDAwWhcNMTgxMTE2MDgwMDAwWjAjMSEwHwYDVQQDExhsb2dpbi5taWNyb3NvZnRvbmxpbmUudXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChn5BCs24Hh6L0BNitPrV5s+2/DBhaeytOmnghJKnqeJlhv3ZczShRM2Cp38LW8Y3wn7L3AJtolaSkF/joKN1l6GupzM+HOEdq7xZxFehxIHW7+25mG/WigBnhsBzLv1SR4uIbrQeS5M0kkLwJ9pOnVH3uzMGG6TRXPnK3ivlKl97AiUEKdlRjCQNLXvYf1ZqlC77c/ZCOHSX4kvIKR2uG+LNlSTRq2rn8AgMpFT4DSlEZz4RmFQvQupQzPpzozaz/gadBpJy/jgDmJlQMPXkHp7wClvbIBGiGRaY6eZFxNV96zwSR/GPNkTObdw2S8/SiAgvIhIcqWTPLY6aVTqJfAgMBAAGjWDBWMFQGA1UdAQRNMEuAEDUj0BrjP0RTbmoRPTRMY3WhJTAjMSEwHwYDVQQDExhsb2dpbi5taWNyb3NvZnRvbmxpbmUudXOCEARyb1TcT9aGTuB2Cofq8qQwDQYJKoZIhvcNAQELBQADggEBAGnLhDHVz2gLDiu9L34V3ro/6xZDiSWhGyHcGqky7UlzQH3pT5so8iF5P0WzYqVtogPsyC2LPJYSTt2vmQugD4xlu/wbvMFLcV0hmNoTKCF1QTVtEQiAiy0Aq+eoF7Al5fV1S3Sune0uQHimuUFHCmUuF190MLcHcdWnPAmzIc8fv7quRUUsExXmxSX2ktUYQXzqFyIOSnDCuWFm6tpfK5JXS8fW5bpqTlrysXXz/OW/8NFGq/alfjrya4ojrOYLpunGriEtNPwK7hxj1AlCYEWaRHRXaUIW1ByoSff/6Y6+ZhXPUe0cDlNRt/qIz5aflwO7+W8baTS4O8m/icu7ItE=https://sts.windows.net/%7Btenantid%7D/https://login.microsoftonline.com/common/wsfedhttps://login.microsoftonline.com/common/wsfed + + + + + + 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 + + + + + + + + diff --git a/test/data/onelogin_idp_metadata.xml b/test/data/onelogin_idp_metadata.xml new file mode 100644 index 0000000..c23cae6 --- /dev/null +++ b/test/data/onelogin_idp_metadata.xml @@ -0,0 +1,49 @@ + + + + + + + +MIIDEDCCAfigAwIBAgIVALIKvj2cp9VIRuWNKjHwGiV1ITxfMA0GCSqGSIb3DQEB +CwUAMBQxEjAQBgNVBAMMCXNhbWx5LmlkcDAeFw0xNzExMDcxNTE3NDNaFw0zNzEx +MDcxNTE3NDNaMBQxEjAQBgNVBAMMCXNhbWx5LmlkcDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALmLey0ZWrMYz2O+CTTjr97UcDkaaUzbIfTjw2/0HofU +czVl5b3ElzOjnB0pJ6xl8s27Qyctdq0EZrlmR9hHKUnF2U9v95rG005Cx+QQVdsg +OaZjDZsmC7eLABQcLdfLP3f42dOozxCH9bBQcs+f/ndrumxdL2sIXflmer4mXfEg +57+HCRL3+s9y07fxdi2LB2ac5gVI8HJbIo8bPOeCyWLYc3UpSZUsxTZouK/wjft0 +TMNJ0gu5TCptiyxxZRhJcg6gm2L77d6rjbnax8fWqj/YNMlXkT7BagUxbPbEklAY +YzIKnt6egw8SpOURgAJynZDl4cYxM1QynfIuWaYi5gECAwEAAaNZMFcwHQYDVR0O +BBYEFJhsexKytkNruELg386zOyW1icH1MDYGA1UdEQQvMC2CCXNhbWx5LmlkcIYg +aHR0cHM6Ly9zYW1seS5pZHAvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQELBQAD +ggEBAAhCAuNPhWsrd/b3MSRK+I5GGe0eDSkpQiCT0ULbqucW+BHj0by0DOy6yP98 +0mfATI6eDJ/LUpT+Wenxljujy5nh0EPu6t6RG/MvWTplnr0//m+41L8tQXEZtZkM +NKkrFieiUBe+rcDx7xywzGUvM0qWRdTyD7Yb0JUU8bZKnIFAEZ+mm8Fu1KfXI6kK +sdeh/6gdpah9v2mermegdNfGpktWtXOukvmR4M8ZYLEyAwGQQAuqJcUnOUwuVMFU +chLUXbAfJUduUkGQ3WKw/SNKyv/7Z2ayr7wlkEA7fxIIrLaJzSm928y9wB9s7Irr +78rpJG67hSRlA+CGTGZyksrk2fE= + + + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + + diff --git a/test/data/shibboleth_idp_metadata.xml b/test/data/shibboleth_idp_metadata.xml new file mode 100644 index 0000000..f92c26f --- /dev/null +++ b/test/data/shibboleth_idp_metadata.xml @@ -0,0 +1,211 @@ + + + + + + + + example.org + + + + + + + +MIIDEDCCAfigAwIBAgIVALIKvj2cp9VIRuWNKjHwGiV1ITxfMA0GCSqGSIb3DQEB +CwUAMBQxEjAQBgNVBAMMCXNhbWx5LmlkcDAeFw0xNzExMDcxNTE3NDNaFw0zNzEx +MDcxNTE3NDNaMBQxEjAQBgNVBAMMCXNhbWx5LmlkcDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALmLey0ZWrMYz2O+CTTjr97UcDkaaUzbIfTjw2/0HofU +czVl5b3ElzOjnB0pJ6xl8s27Qyctdq0EZrlmR9hHKUnF2U9v95rG005Cx+QQVdsg +OaZjDZsmC7eLABQcLdfLP3f42dOozxCH9bBQcs+f/ndrumxdL2sIXflmer4mXfEg +57+HCRL3+s9y07fxdi2LB2ac5gVI8HJbIo8bPOeCyWLYc3UpSZUsxTZouK/wjft0 +TMNJ0gu5TCptiyxxZRhJcg6gm2L77d6rjbnax8fWqj/YNMlXkT7BagUxbPbEklAY +YzIKnt6egw8SpOURgAJynZDl4cYxM1QynfIuWaYi5gECAwEAAaNZMFcwHQYDVR0O +BBYEFJhsexKytkNruELg386zOyW1icH1MDYGA1UdEQQvMC2CCXNhbWx5LmlkcIYg +aHR0cHM6Ly9zYW1seS5pZHAvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQELBQAD +ggEBAAhCAuNPhWsrd/b3MSRK+I5GGe0eDSkpQiCT0ULbqucW+BHj0by0DOy6yP98 +0mfATI6eDJ/LUpT+Wenxljujy5nh0EPu6t6RG/MvWTplnr0//m+41L8tQXEZtZkM +NKkrFieiUBe+rcDx7xywzGUvM0qWRdTyD7Yb0JUU8bZKnIFAEZ+mm8Fu1KfXI6kK +sdeh/6gdpah9v2mermegdNfGpktWtXOukvmR4M8ZYLEyAwGQQAuqJcUnOUwuVMFU +chLUXbAfJUduUkGQ3WKw/SNKyv/7Z2ayr7wlkEA7fxIIrLaJzSm928y9wB9s7Irr +78rpJG67hSRlA+CGTGZyksrk2fE= + + + + + + + + + +MIIDDzCCAfegAwIBAgIUawrhfDAK6t2xrB0CCKKiLILXdUEwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJc2FtbHkuaWRwMB4XDTE3MTEwNzE1MTczMVoXDTM3MTEw +NzE1MTczMVowFDESMBAGA1UEAwwJc2FtbHkuaWRwMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAgx/bOpdbzlR33T9ZgVkLWAYVTJvPnS6EbOouV9iSsrul +9Sg6kxrb3NK9HumFqalDxmwZH81snt+isgIUyIX0uZDEu0eBt++hGrLH4/gvZjQW +Ow5ju1+dVOIt28Qy9+ExzWS4XEblId4m8xxNew2FlKKQwThYojuGH95FkMDo736A +wLJNol7FY3BgZwcGajIDFQoAyBhUrfrScBvE/eEGmPyJPzIO7NmtrlPq5NmATi4W +fG5U7I+dT6t3rasPbhbKf1xsN5dNOgHEYAZmp+wqMJ9t4GNDJgqt5Sftryd/zskk +9fPjk8MFll4XVJ9NGjg1AjUwS3swQBIK2xejK1zl2QIDAQABo1kwVzAdBgNVHQ4E +FgQUs0m5/0iOU8Z9Rf7JrbfYd2EUrBowNgYDVR0RBC8wLYIJc2FtbHkuaWRwhiBo +dHRwczovL3NhbWx5LmlkcC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsFAAOC +AQEAYMBZvg8V9468Jn1mbMJ7YuOb1A8XFB5nuewpBzjnFoDZKRsUim6DUOAt/NYZ +xWxaC7l8t70LdGskxaFgdE2+L7z7TibZRj2Ibc+CRg20O615rCR3C5fUdRv6Z4C/ +pSu5yNPQz5NPWOI5ryFmbp9TCf+Yh8hwa49BY/pOIPSjGk5usJk9OVBSqwdJrBpp +iO9wLLCB2z6ZFpK3LpF2DpGAewuJOzHaD8VfPoqqAcXnWR+Q263QOrfv+9GeFv8G +odjFk9wMTYRX5iitBAank4vrE0USbovz30F+wK4VLxm/806Evh4wOPwkroxBomnc +a/dmaqTz0EZ80cr5Le+54VhF/w== + + + + + + + + + +MIIDDzCCAfegAwIBAgIUFjdO5mF6khL3fHOV3CcPpMD1juMwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJc2FtbHkuaWRwMB4XDTE3MTEwNzE1MTczMloXDTM3MTEw +NzE1MTczMlowFDESMBAGA1UEAwwJc2FtbHkuaWRwMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA8X53oxFAwsIdsCltzcko0NgUg63NQtOxXVEoqmPpSKxX +8wvsXWa0/KLa5XOWuoxshc45yI0Tn9bxGxrzP90StABRIKSnbjs2k1sebXPbOHDM +LPMyMcKq34qiIffg+KfPHiOajksB8gD1EPdu8hSTPuLsiJnCUrWtPB8s/IydEdTa +o79Hk0cd8BQskB/l7PwuakPdsF6QRJj+kcFrBAkECzrET0LZahpIPKmSQpE3LQ08 +XxMLEoFJrhvt1Dwj4LTEbmjrJY+l+2HpJxXH9ibwVlJpTzqNAz81vC7Xad/c4oMg +thnk1uhk73IqmeAgQA8FKFKCLwK7iFNJ8X/B99EfYwIDAQABo1kwVzAdBgNVHQ4E +FgQU3jbqNvR4yPzb9R173XFB+E+VGCIwNgYDVR0RBC8wLYIJc2FtbHkuaWRwhiBo +dHRwczovL3NhbWx5LmlkcC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsFAAOC +AQEAHlcb/jlVDu8Lj4X6u/IAEiBzwtVjVLpH6X0GHWq1WU8IN4zAtPhsc4ELmtGj +ZJNCZwD7eBi4Ibby6I3NCwvhafpRVB8+PCD/jGB53t/vV8VZII/Zpk/wgYTh11T+ +TyOuW/JCckLeHLSwYPiDHmk3z9e8K9CVI/lhHvNXoEEfGtrIiGE8T00RhBAWvflv +2YATb9dsUGKXojAKi1xZpBToxQPllryjGqN+Big6IcR0Pv/IVOxVvcgU5ZOtYF0g +HuK+YEeTSVMkW24+aC6185LocIKrUPg9d/kyUTi8WK047fctGv+7IdP5mbeGMRiU +pgvjU5nUCkPOUOyZZiVO0VMEIA== + + + + + + + + + + + + + + + + + + + + + + + + + + example.org + + + + + + +MIIDEDCCAfigAwIBAgIVALIKvj2cp9VIRuWNKjHwGiV1ITxfMA0GCSqGSIb3DQEB +CwUAMBQxEjAQBgNVBAMMCXNhbWx5LmlkcDAeFw0xNzExMDcxNTE3NDNaFw0zNzEx +MDcxNTE3NDNaMBQxEjAQBgNVBAMMCXNhbWx5LmlkcDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALmLey0ZWrMYz2O+CTTjr97UcDkaaUzbIfTjw2/0HofU +czVl5b3ElzOjnB0pJ6xl8s27Qyctdq0EZrlmR9hHKUnF2U9v95rG005Cx+QQVdsg +OaZjDZsmC7eLABQcLdfLP3f42dOozxCH9bBQcs+f/ndrumxdL2sIXflmer4mXfEg +57+HCRL3+s9y07fxdi2LB2ac5gVI8HJbIo8bPOeCyWLYc3UpSZUsxTZouK/wjft0 +TMNJ0gu5TCptiyxxZRhJcg6gm2L77d6rjbnax8fWqj/YNMlXkT7BagUxbPbEklAY +YzIKnt6egw8SpOURgAJynZDl4cYxM1QynfIuWaYi5gECAwEAAaNZMFcwHQYDVR0O +BBYEFJhsexKytkNruELg386zOyW1icH1MDYGA1UdEQQvMC2CCXNhbWx5LmlkcIYg +aHR0cHM6Ly9zYW1seS5pZHAvaWRwL3NoaWJib2xldGgwDQYJKoZIhvcNAQELBQAD +ggEBAAhCAuNPhWsrd/b3MSRK+I5GGe0eDSkpQiCT0ULbqucW+BHj0by0DOy6yP98 +0mfATI6eDJ/LUpT+Wenxljujy5nh0EPu6t6RG/MvWTplnr0//m+41L8tQXEZtZkM +NKkrFieiUBe+rcDx7xywzGUvM0qWRdTyD7Yb0JUU8bZKnIFAEZ+mm8Fu1KfXI6kK +sdeh/6gdpah9v2mermegdNfGpktWtXOukvmR4M8ZYLEyAwGQQAuqJcUnOUwuVMFU +chLUXbAfJUduUkGQ3WKw/SNKyv/7Z2ayr7wlkEA7fxIIrLaJzSm928y9wB9s7Irr +78rpJG67hSRlA+CGTGZyksrk2fE= + + + + + + + + + +MIIDDzCCAfegAwIBAgIUawrhfDAK6t2xrB0CCKKiLILXdUEwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJc2FtbHkuaWRwMB4XDTE3MTEwNzE1MTczMVoXDTM3MTEw +NzE1MTczMVowFDESMBAGA1UEAwwJc2FtbHkuaWRwMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAgx/bOpdbzlR33T9ZgVkLWAYVTJvPnS6EbOouV9iSsrul +9Sg6kxrb3NK9HumFqalDxmwZH81snt+isgIUyIX0uZDEu0eBt++hGrLH4/gvZjQW +Ow5ju1+dVOIt28Qy9+ExzWS4XEblId4m8xxNew2FlKKQwThYojuGH95FkMDo736A +wLJNol7FY3BgZwcGajIDFQoAyBhUrfrScBvE/eEGmPyJPzIO7NmtrlPq5NmATi4W +fG5U7I+dT6t3rasPbhbKf1xsN5dNOgHEYAZmp+wqMJ9t4GNDJgqt5Sftryd/zskk +9fPjk8MFll4XVJ9NGjg1AjUwS3swQBIK2xejK1zl2QIDAQABo1kwVzAdBgNVHQ4E +FgQUs0m5/0iOU8Z9Rf7JrbfYd2EUrBowNgYDVR0RBC8wLYIJc2FtbHkuaWRwhiBo +dHRwczovL3NhbWx5LmlkcC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsFAAOC +AQEAYMBZvg8V9468Jn1mbMJ7YuOb1A8XFB5nuewpBzjnFoDZKRsUim6DUOAt/NYZ +xWxaC7l8t70LdGskxaFgdE2+L7z7TibZRj2Ibc+CRg20O615rCR3C5fUdRv6Z4C/ +pSu5yNPQz5NPWOI5ryFmbp9TCf+Yh8hwa49BY/pOIPSjGk5usJk9OVBSqwdJrBpp +iO9wLLCB2z6ZFpK3LpF2DpGAewuJOzHaD8VfPoqqAcXnWR+Q263QOrfv+9GeFv8G +odjFk9wMTYRX5iitBAank4vrE0USbovz30F+wK4VLxm/806Evh4wOPwkroxBomnc +a/dmaqTz0EZ80cr5Le+54VhF/w== + + + + + + + + + +MIIDDzCCAfegAwIBAgIUFjdO5mF6khL3fHOV3CcPpMD1juMwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJc2FtbHkuaWRwMB4XDTE3MTEwNzE1MTczMloXDTM3MTEw +NzE1MTczMlowFDESMBAGA1UEAwwJc2FtbHkuaWRwMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA8X53oxFAwsIdsCltzcko0NgUg63NQtOxXVEoqmPpSKxX +8wvsXWa0/KLa5XOWuoxshc45yI0Tn9bxGxrzP90StABRIKSnbjs2k1sebXPbOHDM +LPMyMcKq34qiIffg+KfPHiOajksB8gD1EPdu8hSTPuLsiJnCUrWtPB8s/IydEdTa +o79Hk0cd8BQskB/l7PwuakPdsF6QRJj+kcFrBAkECzrET0LZahpIPKmSQpE3LQ08 +XxMLEoFJrhvt1Dwj4LTEbmjrJY+l+2HpJxXH9ibwVlJpTzqNAz81vC7Xad/c4oMg +thnk1uhk73IqmeAgQA8FKFKCLwK7iFNJ8X/B99EfYwIDAQABo1kwVzAdBgNVHQ4E +FgQU3jbqNvR4yPzb9R173XFB+E+VGCIwNgYDVR0RBC8wLYIJc2FtbHkuaWRwhiBo +dHRwczovL3NhbWx5LmlkcC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsFAAOC +AQEAHlcb/jlVDu8Lj4X6u/IAEiBzwtVjVLpH6X0GHWq1WU8IN4zAtPhsc4ELmtGj +ZJNCZwD7eBi4Ibby6I3NCwvhafpRVB8+PCD/jGB53t/vV8VZII/Zpk/wgYTh11T+ +TyOuW/JCckLeHLSwYPiDHmk3z9e8K9CVI/lhHvNXoEEfGtrIiGE8T00RhBAWvflv +2YATb9dsUGKXojAKi1xZpBToxQPllryjGqN+Big6IcR0Pv/IVOxVvcgU5ZOtYF0g +HuK+YEeTSVMkW24+aC6185LocIKrUPg9d/kyUTi8WK047fctGv+7IdP5mbeGMRiU +pgvjU5nUCkPOUOyZZiVO0VMEIA== + + + + + + + + + + + + + diff --git a/test/data/simplesaml_idp_metadata.xml b/test/data/simplesaml_idp_metadata.xml new file mode 100644 index 0000000..7eb081e --- /dev/null +++ b/test/data/simplesaml_idp_metadata.xml @@ -0,0 +1,36 @@ + + + + + + + + MIIF1TCCA72gAwIBAgIJAKib45YfneRFMA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQGEwJVUzERMA8GA1UECAwITWlkbGFuZHMxEjAQBgNVBAcMCVNhZmV2aWxsZTEWMBQGA1UECgwNSUQgRmVkZXJhdGlvbjEhMB8GA1UECwwYRGVwYXJ0bWVudCBvZiBJZGVudGl0aWVzMQ8wDQYDVQQDDAZteS5pZHAwHhcNMTcwOTI5MjAyNDAxWhcNMTgwOTI5MjAyNDAxWjCBgDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pZGxhbmRzMRIwEAYDVQQHDAlTYWZldmlsbGUxFjAUBgNVBAoMDUlEIEZlZGVyYXRpb24xITAfBgNVBAsMGERlcGFydG1lbnQgb2YgSWRlbnRpdGllczEPMA0GA1UEAwwGbXkuaWRwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtZ6sClsZ55ulmtcZYIf18FPd5Hwc/TF4J32fu6WY0J0R9zHbFo+2HiSHSwM8jFHVCbKbvOS0bspHSh40RI/hfdWCnGujbZTZ0z9NPCluziruerKr3lb7BVTw3YGAA1h7TsdrsFlosTYFcn4dJcXwrLXkCQyi5wXC4IcPzNrL1Wuved97oxAsLdKopyBjvYRTaxFgNh5dfxyxX8RU+LIpcNnE8ZvKUy3QeN1idN1mywYCIoNnXXVu+hDpden4WIHcDOj/upYE2RqdywE1yV4KlxUx3Wgpc3382vEFtRXDHABi/qwnxiN3H7GCB0LK3eduasaBLJek0D10ONO4kWxQDSqVjzISWXVylSHijFX1IuIJoEHr3CRiY/e5xRIgVRnMfnUQu+yv6Fjp/aN5gMJypi2QHAXNWAS7bUjAvnHTfCMz2oPZokCtmQSqfNzq3RFHQye/dcLI8f4IwZ5Te96NV7egle3owXYdxrdCgkGglwdLOkf469Z6MjniH0doGtIF/mDlvr1Fww2Gp9E9gblaaLj5pVxpTKfVnw1OPcpDwfXiK39+Pz2m0mMKhhYBg8WZN8dddigfGmlZhBgbMPrqr7tBHm+c99MktskZq23fvVtsif7ARsG26Sm3lkCb03T4zOvPmENRcM2VAD7C2I+2IseVElCyEOadku3WZULYIP8CAwEAAaNQME4wHQYDVR0OBBYEFNHR4yCVyK5oB1LGcD+qXxeYEgOmMB8GA1UdIwQYMBaAFNHR4yCVyK5oB1LGcD+qXxeYEgOmMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFrx1ggzUb+BKpvxWU9Nypb+jJyuRPYpBbvCnNohUcm8hdEE2wwybkesX01CKIH8KMjNBRytq/1aoR2nwog4aPXHciZBM5/zX2tIzDdfk4x3rRR6IdEfsXkC919TuVM+Uj/A+VgGHbqtGxEt1+nqTy9jRCOuALuYbFt3cycgqH2WjJazUPbVlkx2KdjkOmSYi1IrxrW3HXjuDPgpQKMFfaXiS7DwKzN4U5DIfTnX476u3N5oTNHfWK/JsqtrfQ061uJGEMt4P8BaliAqHoHt289XHD03wAm/b2ajgJQoW/Hhkpz8gN785doKHjEeoEkZzFllUj7e69SmD7HWDWAFeO1OOpikgwuX93f+YYccTnUAznqN8r05t5WjqPypizhr193Qc5TlEm+FkzlfjzivltTLCv0aJDlRlrQVOrZL4Buh0E4weZFk97jKeghLO6qnwxID/jSptEeN5L69Q8QzOFWia7SFGszlvrl8Y/3CQxYCzRmtSZmqdY9a6taZZ/w433+Ktc6vOstjqRE67yq1pp+0Ee4haMB/E87hDuh3Drcq2tT+b9wLNNfhSTYIw4AGpW27xhPh65RouOi9ufGjyFs1ZqqEEmBMIJi95KhUCFokMRf8L29ijqQqpDve8EvEYkBa7KgzTq3R925D5G55orLXoG4Bfdq9FY35hmR8TbLC + + + + + + + + MIIF1TCCA72gAwIBAgIJAKib45YfneRFMA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQGEwJVUzERMA8GA1UECAwITWlkbGFuZHMxEjAQBgNVBAcMCVNhZmV2aWxsZTEWMBQGA1UECgwNSUQgRmVkZXJhdGlvbjEhMB8GA1UECwwYRGVwYXJ0bWVudCBvZiBJZGVudGl0aWVzMQ8wDQYDVQQDDAZteS5pZHAwHhcNMTcwOTI5MjAyNDAxWhcNMTgwOTI5MjAyNDAxWjCBgDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pZGxhbmRzMRIwEAYDVQQHDAlTYWZldmlsbGUxFjAUBgNVBAoMDUlEIEZlZGVyYXRpb24xITAfBgNVBAsMGERlcGFydG1lbnQgb2YgSWRlbnRpdGllczEPMA0GA1UEAwwGbXkuaWRwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtZ6sClsZ55ulmtcZYIf18FPd5Hwc/TF4J32fu6WY0J0R9zHbFo+2HiSHSwM8jFHVCbKbvOS0bspHSh40RI/hfdWCnGujbZTZ0z9NPCluziruerKr3lb7BVTw3YGAA1h7TsdrsFlosTYFcn4dJcXwrLXkCQyi5wXC4IcPzNrL1Wuved97oxAsLdKopyBjvYRTaxFgNh5dfxyxX8RU+LIpcNnE8ZvKUy3QeN1idN1mywYCIoNnXXVu+hDpden4WIHcDOj/upYE2RqdywE1yV4KlxUx3Wgpc3382vEFtRXDHABi/qwnxiN3H7GCB0LK3eduasaBLJek0D10ONO4kWxQDSqVjzISWXVylSHijFX1IuIJoEHr3CRiY/e5xRIgVRnMfnUQu+yv6Fjp/aN5gMJypi2QHAXNWAS7bUjAvnHTfCMz2oPZokCtmQSqfNzq3RFHQye/dcLI8f4IwZ5Te96NV7egle3owXYdxrdCgkGglwdLOkf469Z6MjniH0doGtIF/mDlvr1Fww2Gp9E9gblaaLj5pVxpTKfVnw1OPcpDwfXiK39+Pz2m0mMKhhYBg8WZN8dddigfGmlZhBgbMPrqr7tBHm+c99MktskZq23fvVtsif7ARsG26Sm3lkCb03T4zOvPmENRcM2VAD7C2I+2IseVElCyEOadku3WZULYIP8CAwEAAaNQME4wHQYDVR0OBBYEFNHR4yCVyK5oB1LGcD+qXxeYEgOmMB8GA1UdIwQYMBaAFNHR4yCVyK5oB1LGcD+qXxeYEgOmMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFrx1ggzUb+BKpvxWU9Nypb+jJyuRPYpBbvCnNohUcm8hdEE2wwybkesX01CKIH8KMjNBRytq/1aoR2nwog4aPXHciZBM5/zX2tIzDdfk4x3rRR6IdEfsXkC919TuVM+Uj/A+VgGHbqtGxEt1+nqTy9jRCOuALuYbFt3cycgqH2WjJazUPbVlkx2KdjkOmSYi1IrxrW3HXjuDPgpQKMFfaXiS7DwKzN4U5DIfTnX476u3N5oTNHfWK/JsqtrfQ061uJGEMt4P8BaliAqHoHt289XHD03wAm/b2ajgJQoW/Hhkpz8gN785doKHjEeoEkZzFllUj7e69SmD7HWDWAFeO1OOpikgwuX93f+YYccTnUAznqN8r05t5WjqPypizhr193Qc5TlEm+FkzlfjzivltTLCv0aJDlRlrQVOrZL4Buh0E4weZFk97jKeghLO6qnwxID/jSptEeN5L69Q8QzOFWia7SFGszlvrl8Y/3CQxYCzRmtSZmqdY9a6taZZ/w433+Ktc6vOstjqRE67yq1pp+0Ee4haMB/E87hDuh3Drcq2tT+b9wLNNfhSTYIw4AGpW27xhPh65RouOi9ufGjyFs1ZqqEEmBMIJi95KhUCFokMRf8L29ijqQqpDve8EvEYkBa7KgzTq3R925D5G55orLXoG4Bfdq9FY35hmR8TbLC + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + Jane + Doe + jane.doe@company.com + + diff --git a/test/data/testshib_metadata.xml b/test/data/testshib_metadata.xml new file mode 100644 index 0000000..bb5ed49 --- /dev/null +++ b/test/data/testshib_metadata.xml @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + testshib.org + + TestShib Test IdP + TestShib IdP. Use this as a source of attributes + for your test SP. + https://www.testshib.org/testshibtwo.jpg + + + + + + + + + + MIIDAzCCAeugAwIBAgIVAPX0G6LuoXnKS0Muei006mVSBXbvMA0GCSqGSIb3DQEB + CwUAMBsxGTAXBgNVBAMMEGlkcC50ZXN0c2hpYi5vcmcwHhcNMTYwODIzMjEyMDU0 + WhcNMzYwODIzMjEyMDU0WjAbMRkwFwYDVQQDDBBpZHAudGVzdHNoaWIub3JnMIIB + IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg9C4J2DiRTEhJAWzPt1S3ryh + m3M2P3hPpwJwvt2q948vdTUxhhvNMuc3M3S4WNh6JYBs53R+YmjqJAII4ShMGNEm + lGnSVfHorex7IxikpuDPKV3SNf28mCAZbQrX+hWA+ann/uifVzqXktOjs6DdzdBn + xoVhniXgC8WCJwKcx6JO/hHsH1rG/0DSDeZFpTTcZHj4S9MlLNUtt5JxRzV/MmmB + 3ObaX0CMqsSWUOQeE4nylSlp5RWHCnx70cs9kwz5WrflnbnzCeHU2sdbNotBEeTH + ot6a2cj/pXlRJIgPsrL/4VSicPZcGYMJMPoLTJ8mdy6mpR6nbCmP7dVbCIm/DQID + AQABoz4wPDAdBgNVHQ4EFgQUUfaDa2mPi24x09yWp1OFXmZ2GPswGwYDVR0RBBQw + EoIQaWRwLnRlc3RzaGliLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEASKKgqTxhqBzR + OZ1eVy++si+eTTUQZU4+8UywSKLia2RattaAPMAcXUjO+3cYOQXLVASdlJtt+8QP + dRkfp8SiJemHPXC8BES83pogJPYEGJsKo19l4XFJHPnPy+Dsn3mlJyOfAa8RyWBS + 80u5lrvAcr2TJXt9fXgkYs7BOCigxtZoR8flceGRlAZ4p5FPPxQR6NDYb645jtOT + MVr3zgfjP6Wh2dt+2p04LG7ENJn8/gEwtXVuXCsPoSCDx9Y0QmyXTJNdV1aB0AhO + RkWPlFYwp+zOyOIR+3m1+pqWFpn0eT/HrxpdKa74FA3R2kq4R7dXe4G0kUgXTdqX + MLRKhDgdmA== + + + + + + + + + + + + + + + urn:mace:shibboleth:1.0:nameIdentifier + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + MIIDAzCCAeugAwIBAgIVAPX0G6LuoXnKS0Muei006mVSBXbvMA0GCSqGSIb3DQEB + CwUAMBsxGTAXBgNVBAMMEGlkcC50ZXN0c2hpYi5vcmcwHhcNMTYwODIzMjEyMDU0 + WhcNMzYwODIzMjEyMDU0WjAbMRkwFwYDVQQDDBBpZHAudGVzdHNoaWIub3JnMIIB + IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg9C4J2DiRTEhJAWzPt1S3ryh + m3M2P3hPpwJwvt2q948vdTUxhhvNMuc3M3S4WNh6JYBs53R+YmjqJAII4ShMGNEm + lGnSVfHorex7IxikpuDPKV3SNf28mCAZbQrX+hWA+ann/uifVzqXktOjs6DdzdBn + xoVhniXgC8WCJwKcx6JO/hHsH1rG/0DSDeZFpTTcZHj4S9MlLNUtt5JxRzV/MmmB + 3ObaX0CMqsSWUOQeE4nylSlp5RWHCnx70cs9kwz5WrflnbnzCeHU2sdbNotBEeTH + ot6a2cj/pXlRJIgPsrL/4VSicPZcGYMJMPoLTJ8mdy6mpR6nbCmP7dVbCIm/DQID + AQABoz4wPDAdBgNVHQ4EFgQUUfaDa2mPi24x09yWp1OFXmZ2GPswGwYDVR0RBBQw + EoIQaWRwLnRlc3RzaGliLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEASKKgqTxhqBzR + OZ1eVy++si+eTTUQZU4+8UywSKLia2RattaAPMAcXUjO+3cYOQXLVASdlJtt+8QP + dRkfp8SiJemHPXC8BES83pogJPYEGJsKo19l4XFJHPnPy+Dsn3mlJyOfAa8RyWBS + 80u5lrvAcr2TJXt9fXgkYs7BOCigxtZoR8flceGRlAZ4p5FPPxQR6NDYb645jtOT + MVr3zgfjP6Wh2dt+2p04LG7ENJn8/gEwtXVuXCsPoSCDx9Y0QmyXTJNdV1aB0AhO + RkWPlFYwp+zOyOIR+3m1+pqWFpn0eT/HrxpdKa74FA3R2kq4R7dXe4G0kUgXTdqX + MLRKhDgdmA== + + + + + + + + + + + + + + + urn:mace:shibboleth:1.0:nameIdentifier + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + TestShib Two Identity Provider + TestShib Two + http://www.testshib.org/testshib-two/ + + + Nate + Klingenstein + ndk@internet2.edu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TestShib Test SP + TestShib SP. Log into this to test your machine. + Once logged in check that all attributes that you expected have been + released. + https://www.testshib.org/testshibtwo.jpg + + + + + + + + MIIEPjCCAyagAwIBAgIBADANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJVUzEV + MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMSIwIAYD + VQQKExlUZXN0U2hpYiBTZXJ2aWNlIFByb3ZpZGVyMRgwFgYDVQQDEw9zcC50ZXN0 + c2hpYi5vcmcwHhcNMDYwODMwMjEyNDM5WhcNMTYwODI3MjEyNDM5WjB3MQswCQYD + VQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1 + cmdoMSIwIAYDVQQKExlUZXN0U2hpYiBTZXJ2aWNlIFByb3ZpZGVyMRgwFgYDVQQD + Ew9zcC50ZXN0c2hpYi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB + AQDJyR6ZP6MXkQ9z6RRziT0AuCabDd3x1m7nLO9ZRPbr0v1LsU+nnC363jO8nGEq + sqkgiZ/bSsO5lvjEt4ehff57ERio2Qk9cYw8XCgmYccVXKH9M+QVO1MQwErNobWb + AjiVkuhWcwLWQwTDBowfKXI87SA7KR7sFUymNx5z1aoRvk3GM++tiPY6u4shy8c7 + vpWbVfisfTfvef/y+galxjPUQYHmegu7vCbjYP3On0V7/Ivzr+r2aPhp8egxt00Q + XpilNai12LBYV3Nv/lMsUzBeB7+CdXRVjZOHGuQ8mGqEbsj8MBXvcxIKbcpeK5Zi + JCVXPfarzuriM1G5y5QkKW+LAgMBAAGjgdQwgdEwHQYDVR0OBBYEFKB6wPDxwYrY + StNjU5P4b4AjBVQVMIGhBgNVHSMEgZkwgZaAFKB6wPDxwYrYStNjU5P4b4AjBVQV + oXukeTB3MQswCQYDVQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYD + VQQHEwpQaXR0c2J1cmdoMSIwIAYDVQQKExlUZXN0U2hpYiBTZXJ2aWNlIFByb3Zp + ZGVyMRgwFgYDVQQDEw9zcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN + BgkqhkiG9w0BAQUFAAOCAQEAc06Kgt7ZP6g2TIZgMbFxg6vKwvDL0+2dzF11Onpl + 5sbtkPaNIcj24lQ4vajCrrGKdzHXo9m54BzrdRJ7xDYtw0dbu37l1IZVmiZr12eE + Iay/5YMU+aWP1z70h867ZQ7/7Y4HW345rdiS6EW663oH732wSYNt9kr7/0Uer3KD + 9CuPuOidBacospDaFyfsaJruE99Kd6Eu/w5KLAGG+m0iqENCziDGzVA47TngKz2v + PVA+aokoOyoz3b53qeti77ijatSEoKjxheBWpO+eoJeGq/e49Um3M2ogIX/JAlMa + Inh+vYSYngQB2sx9LGkR9KHaMKNIGCDehk93Xla4pWJx1w== + + + + + + + + + + + + + + + + + + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:mace:shibboleth:1.0:nameIdentifier + + + + + + + + + + + + + + + + + + + + TestShib Two Service Provider + TestShib Two + http://www.testshib.org/testshib-two/ + + + Nate + Klingenstein + ndk@internet2.edu + + + + + + + diff --git a/test/samly_idp_data_test.exs b/test/samly_idp_data_test.exs new file mode 100644 index 0000000..a410b2c --- /dev/null +++ b/test/samly_idp_data_test.exs @@ -0,0 +1,161 @@ +defmodule SamlyIdpDataTest do + use ExUnit.Case + require Samly.Esaml + alias Samly.{Esaml, IdpData, SpData} + + @sp_config1 %{ + id: "sp1", + entity_id: "urn:test:sp1", + certfile: "test/data/test.crt", + keyfile: "test/data/test.pem" + } + + @idp_config1 %{ + id: "idp1", + sp_id: "sp1", + base_url: "http://samly.howto:4003/sso", + metadata_file: "test/data/idp_metadata.xml" + } + + setup context do + sp_data = SpData.load_provider(@sp_config1) + [sps: %{sp_data.id => sp_data}] |> Enum.into(context) + end + + test "valid-idp-config-1", %{sps: sps} do + %IdpData{} = idp_data = IdpData.load_provider(@idp_config1, sps) + assert idp_data.valid? + end + + # verify defaults + test "valid-idp-config-2", %{sps: sps} do + %IdpData{} = idp_data = IdpData.load_provider(@idp_config1, sps) + refute idp_data.use_redirect_for_req + assert idp_data.sign_requests + assert idp_data.sign_metadata + assert idp_data.signed_assertion_in_resp + assert idp_data.signed_envelopes_in_resp + end + + test "valid-idp-config-3", %{sps: sps} do + idp_config = + Map.merge(@idp_config1, %{ + use_redirect_for_req: false, + sign_requests: true, + sign_metadata: true, + signed_assertion_in_resp: true, + signed_envelopes_in_resp: true + }) + + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + refute idp_data.use_redirect_for_req + assert idp_data.sign_requests + assert idp_data.sign_metadata + assert idp_data.signed_assertion_in_resp + assert idp_data.signed_envelopes_in_resp + end + + test "valid-idp-config-4", %{sps: sps} do + idp_config = + Map.merge(@idp_config1, %{ + use_redirect_for_req: true, + sign_requests: false, + sign_metadata: false, + signed_assertion_in_resp: false, + signed_envelopes_in_resp: false + }) + + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.use_redirect_for_req + refute idp_data.sign_requests + refute idp_data.sign_metadata + refute idp_data.signed_assertion_in_resp + refute idp_data.signed_envelopes_in_resp + end + + test "valid-idp-config-5", %{sps: sps} do + idp_config = %{@idp_config1 | base_url: nil} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + assert idp_data.base_url == nil + end + + test "valid-idp-config-6", %{sps: sps} do + idp_config = Map.put(@idp_config1, :pre_session_create_pipeline, MyPipeline) + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + assert idp_data.pre_session_create_pipeline == MyPipeline + end + + test "valid-idp-config-7", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/azure_fed_metadata.xml"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + end + + test "valid-idp-config-8", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/onelogin_idp_metadata.xml"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + end + + test "valid-idp-config-9", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/shibboleth_idp_metadata.xml"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + end + + test "valid-idp-config-10", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/simplesaml_idp_metadata.xml"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + end + + test "valid-idp-config-11", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/testshib_metadata.xml"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + end + + test "url-test-1", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/shibboleth_idp_metadata.xml"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + + Esaml.esaml_idp_metadata( + login_location: sso_url, + logout_location: slo_url + ) = idp_data.esaml_idp_rec + + assert sso_url |> List.to_string() |> String.ends_with?("/SAML2/POST/SSO") + assert slo_url |> List.to_string() |> String.ends_with?("/SAML2/POST/SLO") + end + + test "url-test-2", %{sps: sps} do + idp_config = %{@idp_config1 | metadata_file: "test/data/shibboleth_idp_metadata.xml"} + idp_config = Map.put(idp_config, :use_redirect_for_req, true) + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + assert idp_data.valid? + + Esaml.esaml_idp_metadata( + login_location: sso_url, + logout_location: slo_url + ) = idp_data.esaml_idp_rec + + assert sso_url |> List.to_string() |> String.ends_with?("/SAML2/Redirect/SSO") + assert slo_url |> List.to_string() |> String.ends_with?("/SAML2/Redirect/SLO") + end + + @tag :skip + test "invalid-idp-config-1", %{sps: sps} do + idp_config = %{@idp_config1 | id: ""} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + refute idp_data.valid? + end + + test "invalid-idp-config-2", %{sps: sps} do + idp_config = %{@idp_config1 | sp_id: "unknown-sp"} + %IdpData{} = idp_data = IdpData.load_provider(idp_config, sps) + refute idp_data.valid? + end +end diff --git a/test/samly_sp_data_test.exs b/test/samly_sp_data_test.exs new file mode 100644 index 0000000..6cf5e0b --- /dev/null +++ b/test/samly_sp_data_test.exs @@ -0,0 +1,59 @@ +defmodule SamlySpDataTest do + use ExUnit.Case + alias Samly.SpData + + @sp_config1 %{ + id: "sp1", + entity_id: "urn:test:sp1", + certfile: "test/data/test.crt", + keyfile: "test/data/test.pem" + } + + test "valid-sp-config-1" do + %SpData{} = sp_data = SpData.load_provider(@sp_config1) + assert sp_data.valid? + end + + test "invalid-sp-config-1" do + sp_config = %{@sp_config1 | id: ""} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end + + test "invalid-sp-config-2" do + sp_config = %{@sp_config1 | certfile: ""} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end + + test "invalid-sp-config-3" do + sp_config = %{@sp_config1 | keyfile: ""} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end + + test "invalid-sp-config-4" do + sp_config = %{@sp_config1 | certfile: "non-existent.crt"} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end + + test "invalid-sp-config-5" do + sp_config = %{@sp_config1 | keyfile: "non-existent.pem"} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end + + test "invalid-sp-config-6" do + sp_config = %{@sp_config1 | certfile: "test/data/test.pem"} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end + + @tag :skip + test "invalid-sp-config-7" do + sp_config = %{@sp_config1 | keyfile: "test/data/test.crt"} + %SpData{} = sp_data = SpData.load_provider(sp_config) + refute sp_data.valid? + end +end diff --git a/test/samly_test.exs b/test/samly_test.exs deleted file mode 100644 index 9836ef8..0000000 --- a/test/samly_test.exs +++ /dev/null @@ -1,40 +0,0 @@ -defmodule SamlyTest do - use ExUnit.Case - doctest Samly - - @test_opts [ - certfile: "test/data/test.crt", - keyfile: "test/data/test.pem", - idp_metadata_file: "test/data/idp_metadata.xml", - base_url: "http://my.app:4000/sso" - ] - - test "valid sp and idp config" do - assert Samly.Provider.load_sp_idp_rec(@test_opts) - end - - test "missing sp certfile" do - opts = Keyword.drop(@test_opts, [:certfile]) - assert :error = Samly.Provider.load_sp_idp_rec(opts) - end - - test "missing sp keyfile" do - opts = Keyword.drop(@test_opts, [:keyfile]) - assert :error = Samly.Provider.load_sp_idp_rec(opts) - end - - test "missing idp metadata" do - opts = Keyword.drop(@test_opts, [:idp_metadata_file]) - assert :error = Samly.Provider.load_sp_idp_rec(opts) - end - - test "invalid certfile" do - opts = Keyword.put(@test_opts, :certfile, @test_opts[:keyfile]) - assert :error = Samly.Provider.load_sp_idp_rec(opts) - end - - test "invalid keyfile" do - opts = Keyword.put(@test_opts, :keyfile, @test_opts[:idp_metadata_file]) - assert :error = Samly.Provider.load_sp_idp_rec(opts) - end -end