Current section
Files
Jump to
Current section
Files
lib/content_security_policy.ex
defmodule ContentSecurityPolicy do
@moduledoc """
Provides functions for interacting with Content Security Policies.
A Content Security Policy is a header which determines which assets the
browser is allowed to retrieve.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP for more in depth
documentation.
"""
alias ContentSecurityPolicy.Directive
alias ContentSecurityPolicy.Policy
@doc """
Converts a `ContentSecurityPolicy.Policy` struct to a valid content security
policy string.
## Examples
iex> policy = %ContentSecurityPolicy.Policy{default_src: ["'self'"]}
iex> ContentSecurityPolicy.serialize(policy)
"default-src 'self';"
"""
def serialize(%Policy{} = csp) do
csp
|> Map.from_struct()
|> filter_empty_sources
|> stringify_and_hyphenate_directives
|> join_sources_with_spaces
|> format_each_directive
|> join_directives_with_spaces
end
defp filter_empty_sources(policy) do
Enum.reject(policy, fn {_directive, source} -> is_empty(source) end)
end
defp is_empty(nil), do: true
defp is_empty([]), do: true
defp is_empty(""), do: true
defp is_empty(_), do: false
defp stringify_and_hyphenate_directives(policy) do
Enum.map(policy, fn {directive, source} ->
updated_directive =
directive
|> to_string
|> String.replace("_", "-")
{updated_directive, source}
end)
end
defp join_sources_with_spaces(policy) do
Enum.map(policy, fn {directive, sources} -> {directive, Enum.join(sources, " ")} end)
end
defp format_each_directive(policy) do
Enum.map(policy, fn {directive, sources} -> "#{directive} #{sources};" end)
end
defp join_directives_with_spaces(directives) when is_list(directives) do
Enum.join(directives, " ")
end
@doc """
Adds a single source value to a directive on the given policy.
"""
@spec add_source_value(Policy.t(), Directive.valid_directive(), String.t()) ::
Policy.t()
def add_source_value(policy, directive, source_value) do
Directive.validate_directive!(directive)
current_source_values = Map.get(policy, directive) || []
new_source_values = current_source_values ++ [source_value]
|> Enum.uniq
Map.put(policy, directive, new_source_values)
end
@doc """
Generates a random base 64 encoded string for use in Content Security Policy
nonces.
"""
@spec generate_nonce(bytes :: pos_integer()) :: String.t()
def generate_nonce(bytes \\ 32) do
bytes
|> :crypto.strong_rand_bytes
|> Base.encode64(padding: false)
end
end