Current section

Files

Jump to
azure lib storage shared_access_signature.ex
Raw

lib/storage/shared_access_signature.ex

defmodule Azure.Storage.SharedAccessSignature do
@moduledoc """
SharedAccessSignature
"""
alias Azure.Storage
import Azure.Storage.Utilities, only: [add_to: 3, set_to_string: 2]
# https://docs.microsoft.com/en-us/rest/api/storageservices/delegating-access-with-a-shared-access-signature
# https://docs.microsoft.com/en-us/azure/storage/common/storage-dotnet-shared-access-signature-part-1
# https://github.com/chgeuer/private_gists/blob/76db1345142d25d3359af6ce4ba7b9eef1aeb769/azure/AccountSAS/AccountSas.cs
defstruct [
:service_version,
:target_scope,
:services,
:resource_type,
:permissions,
:start_time,
:expiry_time,
:canonicalized_resource,
:resource,
:ip_range,
:protocol,
:cache_control,
:content_disposition,
:content_encoding,
:content_language,
:content_type
]
def new, do: %__MODULE__{}
def for_storage_account(v = %__MODULE__{target_scope: nil}),
do: v |> Map.put(:target_scope, :account)
def for_blob_service(v = %__MODULE__{target_scope: nil}), do: v |> Map.put(:target_scope, :blob)
def for_table_service(v = %__MODULE__{target_scope: nil}),
do: v |> Map.put(:target_scope, :table)
def for_queue_service(v = %__MODULE__{target_scope: nil}),
do: v |> Map.put(:target_scope, :queue)
# https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas#specifying-account-sas-parameters
@services_map %{blob: "b", queue: "q", table: "t", file: "f"}
def add_service_blob(v = %__MODULE__{target_scope: :account}), do: v |> add_to(:services, :blob)
def add_service_queue(v = %__MODULE__{target_scope: :account}),
do: v |> add_to(:services, :queue)
def add_service_table(v = %__MODULE__{target_scope: :account}),
do: v |> add_to(:services, :table)
def add_service_file(v = %__MODULE__{target_scope: :account}), do: v |> add_to(:services, :file)
@resource_types_map %{service: "s", object: "o", container: "c"}
def add_resource_type_service(v = %__MODULE__{target_scope: :account}),
do: v |> add_to(:resource_type, :service)
def add_resource_type_container(v = %__MODULE__{target_scope: :account}),
do: v |> add_to(:resource_type, :container)
def add_resource_type_object(v = %__MODULE__{target_scope: :account}),
do: v |> add_to(:resource_type, :object)
@resource_map %{
# https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas#specifying-the-signed-resource-blob-service-only
container: "c",
blob: "b",
# https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas#specifying-the-signed-resource-file-service-only
share: "s",
file: "f"
}
def add_resource_blob_container(v = %__MODULE__{}), do: v |> add_to(:resource, :container)
def add_resource_blob_blob(v = %__MODULE__{}), do: v |> add_to(:resource, :blob)
@permissions_map %{
read: "r",
write: "w",
delete: "d",
list: "l",
add: "a",
create: "c",
update: "u",
process: "p"
}
def add_permission_read(v = %__MODULE__{}), do: add_to(v, :permissions, :read)
def add_permission_write(v = %__MODULE__{}), do: add_to(v, :permissions, :write)
def add_permission_delete(v = %__MODULE__{}), do: add_to(v, :permissions, :delete)
def add_permission_list(v = %__MODULE__{}), do: add_to(v, :permissions, :list)
def add_permission_add(v = %__MODULE__{}), do: add_to(v, :permissions, :add)
def add_permission_create(v = %__MODULE__{}), do: add_to(v, :permissions, :create)
def add_permission_update(v = %__MODULE__{}), do: add_to(v, :permissions, :update)
def add_permission_process(v = %__MODULE__{}), do: add_to(v, :permissions, :process)
def add_canonicalized_resource(v = %__MODULE__{}, resource_name) do
%{v | canonicalized_resource: resource_name}
end
def as_time(t), do: t |> Timex.format!("{YYYY}-{0M}-{0D}T{0h24}:{0m}:{0s}Z")
def service_version(v = %__MODULE__{}, service_version),
do: %{v | service_version: service_version}
def start_time(v = %__MODULE__{}, start_time), do: %{v | start_time: start_time}
def expiry_time(v = %__MODULE__{}, expiry_time), do: %{v | expiry_time: expiry_time}
def resource(v = %__MODULE__{}, resource), do: %{v | resource: resource}
def ip_range(v = %__MODULE__{}, ip_range), do: %{v | ip_range: ip_range}
def protocol(v = %__MODULE__{}, protocol), do: %{v | protocol: protocol}
def cache_control(v = %__MODULE__{}, cache_control), do: %{v | cache_control: cache_control}
def content_disposition(v = %__MODULE__{}, content_disposition),
do: %{v | content_disposition: content_disposition}
def content_encoding(v = %__MODULE__{}, content_encoding),
do: %{v | content_encoding: content_encoding}
def content_language(v = %__MODULE__{}, content_language),
do: %{v | content_language: content_language}
def content_type(v = %__MODULE__{}, content_type), do: %{v | content_type: content_type}
def encode({:service_version, value}), do: {"sv", value}
def encode({:start_time, value}), do: {"st", value |> as_time()}
def encode({:expiry_time, value}), do: {"se", value |> as_time()}
def encode({:canonicalized_resource, value}), do: {"cr", value}
def encode({:resource, value}), do: {"sr", value |> set_to_string(@resource_map)}
def encode({:ip_range, value}), do: {"sip", value}
def encode({:protocol, value}), do: {"spr", value}
def encode({:services, value}), do: {"ss", value |> set_to_string(@services_map)}
def encode({:resource_type, value}), do: {"srt", value |> set_to_string(@resource_types_map)}
def encode({:permissions, value}), do: {"sp", value |> set_to_string(@permissions_map)}
def encode({:cache_control, value}), do: {"rscc", value}
def encode({:content_disposition, value}), do: {"rscd", value}
def encode({:content_encoding, value}), do: {"rsce", value}
def encode({:content_language, value}), do: {"rscl", value}
def encode({:content_type, value}), do: {"rsct", value}
def encode(_), do: {nil, nil}
# https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas#version-2018-11-09-and-later
# StringToSign = signedPermissions + "\n" +
# signedStart + "\n" +
# signedExpiry + "\n" +
# canonicalizedResource + "\n" +
# signedIdentifier + "\n" +
# signedIP + "\n" +
# signedProtocol + "\n" +
# signedVersion + "\n" +
# signedResource + "\n"
# signedSnapshotTime + "\n" +
# rscc + "\n" +
# rscd + "\n" +
# rsce + "\n" +
# rscl + "\n" +
# rsct
defp string_to_sign(values, _account_name, :blob) do
[
# permissions
values |> Map.get("sp", ""),
# start date
values |> Map.get("st", ""),
# expiry date
values |> Map.get("se", ""),
# canonicalized resource
values |> Map.get("cr", ""),
# identifier
"",
# IP address
values |> Map.get("sip", ""),
# Protocol
values |> Map.get("spr", ""),
# Version
values |> Map.get("sv", ""),
# resource
values |> Map.get("sr"),
# snapshottime
"",
# rscc - Cache-Control
values |> Map.get("rscc", ""),
# rscd - Content-Disposition
values |> Map.get("rscd", ""),
# rsce - Content-Encoding
values |> Map.get("rsce", ""),
# rscl - Content-Language
values |> Map.get("rscl", ""),
# rsct - Content-Type
values |> Map.get("rsct", "")
]
|> Enum.join("\n")
end
defp string_to_sign(values, account_name, _) do
[
account_name,
values |> Map.get("sp", ""),
values |> Map.get("ss", ""),
values |> Map.get("srt", ""),
values |> Map.get("st", ""),
values |> Map.get("se", ""),
values |> Map.get("sip", ""),
values |> Map.get("spr", ""),
values |> Map.get("sv", ""),
""
]
|> Enum.join("\n")
end
def sign(
sas = %__MODULE__{target_scope: target_scope},
%Storage{account_name: account_name, account_key: account_key}
)
when is_atom(target_scope) and target_scope != nil do
# https://docs.microsoft.com/en-us/rest/api/storageservices/service-sas-examples
values =
sas
|> Map.from_struct()
|> Enum.filter(fn {_, val} -> val != nil end)
|> Enum.map(&__MODULE__.encode/1)
|> Enum.filter(fn {_, val} -> val != nil end)
|> Map.new()
string_to_sign = string_to_sign(values, account_name, target_scope)
signature =
Storage.Crypto.hmac(:sha256, account_key |> Base.decode64!(), string_to_sign)
|> Base.encode64()
values
|> Map.put("sig", signature)
|> Map.drop(["cr"])
|> URI.encode_query()
end
end