Current section
Files
Jump to
Current section
Files
lib/lti.ex
defmodule LTI do
@moduledoc """
A library to launch a LTI request
"""
alias LTI.{Credentials, OAuthData, LaunchParams}
def credentials(url, key, secret) do
%Credentials{url: url, key: key, secret: secret}
end
def oauth_params(%Credentials{key: key}) do
%OAuthData{
oauth_callback: "about:blank",
oauth_consumer_key: key,
oauth_version: "1.0",
oauth_nonce: nonce(),
oauth_timestamp: timestamp(),
oauth_signature_method: "HMAC-SHA1"
}
end
def launch_data(%OAuthData{} = oauth, %LaunchParams{} = launch_params) do
struct_to_list(launch_params) ++ struct_to_list(oauth)
end
def launch_query(%OAuthData{} = oauth, %LaunchParams{} = launch_params) do
oauth
|> launch_data(launch_params)
|> Enum.reduce(%{}, fn {key, value}, acc ->
Map.put(acc, key, value)
end)
|> Enum.reduce([], fn {key, value}, acc ->
acc ++ ["#{key}=#{percent_encode(value)}"]
end)
end
def signature(
%Credentials{secret: secret} = creds,
%OAuthData{} = oauth_params,
%LaunchParams{} = launch_params
) do
:sha
|> :crypto.hmac(
encode_secret(secret),
base_string(creds, oauth_params, launch_params)
)
|> Base.encode64()
end
defp encode_secret(secret) do
"#{percent_encode(secret)}&"
end
defp base_string(%Credentials{url: url}, oauth_params, launch_params) do
query =
oauth_params
|> launch_query(launch_params)
|> Enum.join("&")
|> percent_encode()
"POST&#{percent_encode(url)}&#{query}"
end
defp timestamp do
{megasec, sec, _mcs} = :os.timestamp()
"#{megasec * 1_000_000 + sec}"
end
defp percent_encode({key, value}) do
{percent_encode(key), percent_encode(value)}
end
defp percent_encode(other) do
other
|> to_string()
|> URI.encode(&URI.char_unreserved?/1)
end
defp struct_to_list(struct),
do:
struct
|> Map.from_struct()
|> Map.to_list()
|> strip_nil()
defp strip_nil(list) do
Enum.reduce(list, [], fn {_, value} = item, acc ->
if is_nil(value),
do: acc,
else: acc ++ [item]
end)
end
defp nonce do
24
|> :crypto.strong_rand_bytes()
|> Base.encode64()
end
end