Skip to content

Commit

Permalink
Merge pull request #41 from CargoSense/interalize_auth
Browse files Browse the repository at this point in the history
Internalize authentication code for easier adaptation.
  • Loading branch information
benwilson512 committed Jun 26, 2015
2 parents 67d7832 + 55509f1 commit 034731b
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 47 deletions.
117 changes: 117 additions & 0 deletions lib/ex_aws/auth.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
defmodule ExAws.Auth do
import ExAws.Auth.Utils
alias Timex.DateFormat

def headers(http_method, url, service, config, headers, body) do
now = %{Timex.Date.now | ms: 0}
headers = [
{"host", URI.parse(url).host},
{"x-amz-date", amz_date(now)} |
headers
]

auth_header = auth_header(
config[:access_key_id],
config[:secret_access_key],
http_method |> method_string,
url,
config[:region],
service |> service_name,
headers,
body,
now)

[{"Authorization", auth_header} | headers ]
end

def auth_header(access_key, secret_key, http_method, url, region, service, headers, body, now) do
date = DateFormat.format!(now, "%Y%m%d", :strftime)
scope = "#{date}/#{region}/#{service}/aws4_request"

signing_key = build_signing_key(secret_key, date, region, service)

signature = http_method
|> build_canonical_request(url, headers, body)
|> build_string_to_sign(now, scope)
|> sign_with(signing_key)

[
"AWS4-HMAC-SHA256 Credential=", access_key, "/", scope, ",",
"SignedHeaders=", signed_headers(headers), ",",
"Signature=", signature
] |> IO.iodata_to_binary
end

def build_signing_key(secret_key, date, region, service) do
["AWS4", secret_key]
|> hmac_sha256(date)
|> hmac_sha256(region)
|> hmac_sha256(service)
|> hmac_sha256("aws4_request")
end

def build_string_to_sign(canonical_request, now, scope) do
timestamp = now |> ExAws.Auth.Utils.amz_date
hashed_canonical_request = ExAws.Auth.Utils.hash_sha256(canonical_request)

[
"AWS4-HMAC-SHA256", "\n",
timestamp, "\n",
scope, "\n",
hashed_canonical_request
] |> IO.iodata_to_binary
end

def sign_with(string_to_sign, signing_key) do
signing_key
|> hmac_sha256(string_to_sign)
|> bytes_to_hex
end

def signed_headers(headers) do
headers
|> Enum.map(fn({k, _}) -> String.downcase(k) end)
|> Enum.sort(&(&1 < &2))
|> Enum.join(";")
end

def build_canonical_request(http_method, url, headers, body) do
uri = URI.parse(url)
http_method = http_method |> String.upcase

query_params = uri.query |> canonical_query_params

headers = headers |> canonical_headers
header_string = headers
|> Enum.map(fn {k, v} -> "#{k}:#{v}" end)
|> Enum.join("\n")

signed_headers_list = headers
|> Keyword.keys
|> Enum.join(";")

[
http_method, "\n",
uri.path, "\n",
query_params, "\n",
header_string, "\n",
"\n",
signed_headers_list, "\n",
ExAws.Auth.Utils.hash_sha256(body)
] |> IO.iodata_to_binary
end

def canonical_query_params(nil), do: ""
def canonical_query_params(params) do
params
|> URI.query_decoder
|> Enum.sort(fn {k1, _}, {k2, _} -> k1 < k2 end)
|> URI.encode_query
end

def canonical_headers(headers) do
headers
|> Enum.map(fn {k, v} -> {String.downcase(k), String.strip(v)} end)
|> Enum.sort(fn {k1, _}, {k2, _} -> k1 < k2 end)
end
end
32 changes: 32 additions & 0 deletions lib/ex_aws/auth/utils.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule ExAws.Auth.Utils do
def amz_date(now) do
now
|> Timex.DateFormat.format!("{ISOz}")
|> String.replace("-", "")
|> String.replace(":", "")
end

def hash_sha256(data) do
:sha256
|> :crypto.hash(data)
|> bytes_to_hex
end

def hmac_sha256(key, data) do
:crypto.hmac(:sha256, key, data)
end

def bytes_to_hex(bytes) do
bytes
|> Base.encode16
|> String.downcase
end

def service_name(service), do: service |> Atom.to_string

def method_string(method) do
method
|> Atom.to_string
|> String.upcase
end
end
40 changes: 1 addition & 39 deletions lib/ex_aws/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,10 @@ defmodule ExAws.Request do
_ -> config[:json_codec].encode!(data)
end

headers = headers(http_method, url, service, config, headers, body)
headers = ExAws.Auth.headers(http_method, url, service, config, headers, body)
request_and_retry(http_method, url, service, config, headers, body, {:attempt, 1})
end

def headers(http_method, url, service, config, headers, body) do
now = %{Timex.Date.now | ms: 0}
amz_date = Timex.DateFormat.format!(now, "{ISOz}")
|> String.replace("-", "")
|> String.replace(":", "")

headers = [
{"host", URI.parse(url).host},
{"x-amz-date", amz_date} |
headers
]

auth_header = AWSAuth.sign_authorization_header(
config[:access_key_id],
config[:secret_access_key],
http_method |> method_string,
url,
config[:region],
service |> service_name,
headers |> Enum.into(%{}),
body,
now)

[{"Authorization", auth_header} | headers ]
end

def service_name(service), do: service |> Atom.to_string

def binary_headers(headers) do
headers |> Enum.map(fn({k, v}) -> {List.to_string(k), List.to_string(v)} end)
end

@doc false
def request_and_retry(_method, _url, _service, _config, _headers, _req_body, {:error, reason}), do: {:error, reason}

Expand Down Expand Up @@ -122,10 +90,4 @@ defmodule ExAws.Request do
def backoff(attempt) do
:timer.sleep(attempt * 1000)
end

defp method_string(method) do
method
|> Atom.to_string
|> String.upcase
end
end
2 changes: 1 addition & 1 deletion lib/ex_aws/s3/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule ExAws.S3.Request do
|> url(bucket, path)
|> add_query(resource, query)

hashed_payload = AWSAuth.Utils.hash_sha256(body)
hashed_payload = ExAws.Auth.Utils.hash_sha256(body)

headers = [
{"x-amz-content-sha256", hashed_payload} |
Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ defmodule ExAws.Mixfile do
end

def application do
[applications: [:logger],
[applications: [:logger, :timex, :crypto],
mod: {ExAws, []}]
end

defp deps do
[
{:aws_auth, "~> 0.2.3"} |
{:timex, "~> 0.13.4"} |
deps(:test_dev)
]
end
Expand Down
9 changes: 4 additions & 5 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
%{"aws_auth": {:hex, :aws_auth, "0.2.3"},
"earmark": {:hex, :earmark, "0.1.15"},
"ex_doc": {:hex, :ex_doc, "0.7.2"},
%{"earmark": {:hex, :earmark, "0.1.17"},
"ex_doc": {:hex, :ex_doc, "0.7.3"},
"hackney": {:hex, :hackney, "1.1.0"},
"httpoison": {:hex, :httpoison, "0.6.2"},
"httpotion": {:hex, :httpotion, "2.0.0"},
"ibrowse": {:git, "git://github.com/cmullaparthi/ibrowse.git", "d2e369ff42666c3574b8b7ec26f69027895c4d94", [tag: "v4.1.1"]},
"idna": {:hex, :idna, "1.0.2"},
"jsx": {:hex, :jsx, "2.5.3"},
"mixunit": {:hex, :mixunit, "0.9.1"},
"mixunit": {:hex, :mixunit, "0.9.2"},
"poison": {:hex, :poison, "1.2.1"},
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.4"},
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"},
"sweet_xml": {:hex, :sweet_xml, "0.2.1"},
"timex": {:hex, :timex, "0.13.4"}}

0 comments on commit 034731b

Please sign in to comment.