Packages
samly
0.6.0
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.10.1
retired
0.10.0
retired
0.9.3
retired
0.9.2
retired
0.9.1
retired
0.9.0
retired
0.8.4
retired
0.8.3
retired
0.8.2
retired
0.8.1
retired
0.8.0
retired
0.7.2
retired
0.7.1
retired
0.7.0
retired
0.6.3
retired
0.6.2
retired
0.6.1
retired
0.6.0
retired
0.5.0
retired
0.4.0
retired
0.1.2
retired
SAML Single-Sign-On Authentication for Plug/Phoenix Applications
Retired package: Use versions >= 1.0.0
Current section
Files
Jump to
Current section
Files
lib/samly/provider.ex
defmodule Samly.Provider do
@moduledoc false
use GenServer
require Logger
require Samly.Esaml
alias Samly.{Esaml, Helper, State}
def start_link(gs_opts \\ []) do
GenServer.start_link(__MODULE__, [], gs_opts)
end
def init([]) do
opts = Application.get_env(:samly, Samly.Provider, [])
pipeline = opts[:pre_session_create_pipeline]
sp_certfile = get_opt(opts, :certfile, "SAMLY_CERTFILE", "samly.crt")
sp_keyfile = get_opt(opts, :keyfile, "SAMLY_KEYFILE", "samly.pem")
idp_metadata_file = get_opt(opts, :idp_metadata_file,
"SAMLY_IDP_METADATA_FILE", "idp_metadata.xml")
sp_base_url = get_opt(opts, :base_url, "SAMLY_BASE_URL", "")
State.init()
with {:idp_metadata_file, {:ok, metadata_xml}} <-
{:idp_metadata_file, File.read(idp_metadata_file)},
{:idp_metadata_parsing, {:ok, idp_metadata}} <-
{:idp_metadata_parsing, idp_metadata_from_xml(metadata_xml)},
trusted_fingerprints = idp_cert_fingerprints(idp_metadata),
{:ok, sp} <- create_sp(sp_certfile, sp_keyfile, sp_base_url, trusted_fingerprints)
do
Application.put_env(:samly, :sp, sp)
Application.put_env(:samly, :idp_metadata, idp_metadata)
if pipeline do
Application.put_env(:samly, :pre_session_create_pipeline, pipeline)
end
else
{:idp_metadata_file, {:error, reason}} ->
Logger.error("Failed to read IDP metadata XML file: #{reason}")
{:idp_metadata_parsing, {:error, reason}} ->
Logger.error("Invalid IDP metadata XML: #{reason}")
error ->
Logger.error("Failed in Samly.Provider: #{error}")
end
{:ok, %{}}
end
defp get_opt(opts, attr, env_var, default) do
value = Keyword.get(opts, attr) || System.get_env(env_var)
if value do
value
else
Logger.error("#{env_var} undefined. Using: #{default}")
default
end
end
defp load_sp_priv_key(file) do
file |> :esaml_util.load_private_key()
end
defp load_sp_cert(file) do
file |> :esaml_util.load_certificate()
end
defp create_sp(certfile, keyfile, sp_base_url, trusted_fingerprints) do
certfile = String.to_charlist(certfile)
keyfile = String.to_charlist(keyfile)
sp_base_url = String.to_charlist(sp_base_url)
cert = load_sp_cert(certfile)
key = load_sp_priv_key(keyfile)
sp_rec = Esaml.esaml_sp(
key: key,
certificate: cert,
sp_sign_requests: true,
sp_sign_metadata: true,
trusted_fingerprints: trusted_fingerprints,
metadata_uri: Helper.get_metadata_uri(sp_base_url),
consume_uri: Helper.get_consume_uri(sp_base_url),
logout_uri: Helper.get_logout_uri(sp_base_url),
# TODO: get this from config
org: Esaml.esaml_org(
name: 'Samly SP',
displayname: 'Samly SP',
url: sp_base_url
),
tech: Esaml.esaml_contact(
name: 'Samly SP Admin',
email: 'admin@samly'
)
)
{:ok, sp_rec}
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)
:esaml.decode_idp_metadata(xml)
rescue
_ -> {:error, :invalid_metadata_xml}
end
end
defp idp_cert_fingerprints(idp_metadata) do
fingerprint = idp_metadata
|> Esaml.esaml_idp_metadata(:certificate)
|> cert_fingerprint()
|> String.to_charlist()
[fingerprint] |> :esaml_util.convert_fingerprints()
end
defp cert_fingerprint(dercert) do
"sha256:" <> (:sha256 |> :crypto.hash(dercert) |> Base.encode64())
end
end