# Cheatsheet
## Creating a Wrapper
```
defmodule MySecretData do
use SensitiveData.Wrapper
end
```
## Getting Data into a Wrapper
{:.col-2}
### SensitiveData `exec/2`
```
SensitiveData.exec(
fn -> System.fetch_env!("DB_URI") end,
into: MySecretData)
```
### Wrapper `from/2`
```
MySecretData.from(fn -> System.fetch_env!("DB_URI") end)
```
### From `:stdio`
```
SensitiveData.gets_sensitive("Your password: ",
into: MySecretData)
```
or alternatively
```
MySecretData.from(fn ->
SensitiveData.gets_sensitive("Your password: ")
end)
```
## Interacting with Wrapped Data
{:.col-2}
### `map/3`
The sensitive data within a container can easily be modified using `c:SensitiveData.Wrapper.map/3`:
```
# get authentication data in Basic Authentication format
basic_credentials = MySecretData.from(fn ->
System.fetch_env!("BASIC_AUTH") end)
# later, these credentials need to be converted
# to username & password
map_credentials =
MySecretData.map(basic_credentials, fn basic ->
[username, password] =
basic
|> Base.decode64!()
|> String.split(":")
end)
```
### `exec/3`
Executing functions requiring sensitive data located in a wrapper can be
accomplished with `c:SensitiveData.Wrapper.exec/3`:
```
# get authentication data
credentials = MySecretData.from(fn ->
System.fetch_env!("BASIC_AUTH") end)
# later, make an API request using these credentials
{:ok, data} =
MySecretData.exec(credentials, fn basic_auth ->
get_api_results("/some/endpoint", basic_auth)
end)
```
## Identifying Wrapper Contents
It is often useful to be able to determine some information about the sensitive
data contained within wrappers, both for programmatic and human debuggability
reasons.
### Guards and Utilities
Functions from the `SensitiveData.Guards` module and utility functions from
the `SensitiveData.Wrapper` module can help with branching logic depending
(somewhat) on the sensitive data within the wrappers:
```
require SensitiveData.Guards
alias SensitiveData.Guards
alias SensitiveData.Wrapper
data = MySecretData.from(fn ->
%{foo: :yes, bar: :no} end)
case data do
map when Guards.is_sensitive_map(map) ->
IO.puts("It's a map with #{Wrapper.sensitive_map_size(map)} items")
_ ->
IO.puts("It's not a map")
end
```
### Redaction
It can be useful to have a redacted version of sensitive data, which can be defined at the module
level with the `redactor` option given to `use SensitiveData.Wrapper`, which can be given as:
- an atom - the name of the redactor function located in the same module
- a `{Module, func}` tuple - the redactor function `func` from module `Module` will be used for redaction
In either case, the redaction function will be called with a single argument: the sensitive term.
⚠️️ **BEWARE** ⚠️ If you use a custom redaction strategy, you must ensure it won't leak any
sensitive data under any circumstances.
```
defmodule CreditCard do
use SensitiveData.Wrapper, redactor: :redactor
def redactor(card_number) when is_binary(card_number) do
{<<first_number::binary-1, to_mask::binary>>, last_four} = String.split_at(card_number, -4)
IO.iodata_to_binary([first_number, List.duplicate("*", String.length(to_mask)), last_four])
end
end
```
Alternatively, the same result can be achieved with:
```
defmodule MyApp.Redaction do
def redact_credit_card(card_number) when is_binary(card_number) do
{<<first_number::binary-1, to_mask::binary>>, last_four} = String.split_at(card_number, -4)
IO.iodata_to_binary([first_number, List.duplicate("*", String.length(to_mask)), last_four])
end
end
defmodule CreditCard do
use SensitiveData.Wrapper, redactor: {MyApp.Redaction, :redact_credit_card}
end
```
In use (either implementation):
```shell
iex(1)> cc = CreditCard.from(fn -> "5105105105105100" end)
#CreditCard<redacted: "5***********5100", ...>
iex(2)> cc.redacted
"5***********5100"
```
### Labeling
It can be useful to label sensitive data, for example to give scope to the given data. Labeling
must be explicitly enabled via the `allow_label: true` option given to `use SensitiveData.Wrapper`.
Once enabled at the module level, each instance of sensitive data can be labeled via the `:label` option.
⚠️ **BEWARE** ⚠️ If you allow labels, you must ensure call sites aren't leaking
sensitive data via label values.
```
defmodule DatabaseCredentials do
use SensitiveData.Wrapper, allow_label: true
end
```
In use:
```
iex(1)> db = DatabaseCredentials.from(fn -> System.fetch_env("PROD_DB_CREDS") end, label: :prod)
#DatabaseCredentials<label: :prod, ...>
iex(2)> db.label
:prod
iex(3)> DatabaseCredentials.from(fn -> System.fetch_env("PROD_CI_CREDS") end, label: :integration)
#DatabaseCredentials<label: :integration, ...>
```
If labels aren't allowed but one is passed as an option, the label will be ignored
and a warning will be logged.
## One-Off Executions
In certain cases, there's is only a transient need for sensitive data, such as
when connecting to a system: once the connection is made, the credentials
aren't needed anymore.
```
{:ok, pid} =
SensitiveData.exec(fn ->
"DATABASE_CONNECTION_URI"
|> System.fetch_env!()
|> parse_postgres_uri()
|> Postgrex.start_link()
end)
```
## Multiple Wrapper Modules
It can be useful to use several module implementing the `SensitiveData.Wrapper`
behaviour, for example to benefit from different redaction capabilities.
Let's take as an example a credit card number which will be converted into a
payment token which can then be used to charge the user. It would be useful
to be able to "peek" at non-sensitive data (to display to the user in the UI,
for debugging, and so on): this can be achieved via redaction. Naturally, as
the credit card and associated token are different, they'll require different
redaction implementations:
```elixir
defmodule CreditCard do
use SensitiveData.Wrapper, redactor: :redactor
def redactor(card_number) when is_binary(card_number) do
{<<first_number::binary-1, to_mask::binary>>, last_four} = String.split_at(card_number, -4)
IO.iodata_to_binary([first_number, List.duplicate("*", String.length(to_mask)), last_four])
end
end
defmodule PaymentToken do
use SensitiveData.Wrapper, redactor: :redactor
def redactor(token) when is_binary(token) do
# the first 10 characters aren't considered sensitive
{not_sensitive, rest} = String.split_at(token, 10)
IO.iodata_to_binary([not_sensitive, List.duplicate("*", String.length(rest))])
end
end
```
For completeness, let's use the following for tokenization logic:
```elixir
defmodule Tokenizer do
def tokenize(card_number) when is_binary(card_number) do
"TOK_" <> random_string(20)
end
defp random_string(length) do
:crypto.strong_rand_bytes(length)
|> Base.url_encode64
|> binary_part(0, length)
end
end
```
Note how the `Tokenizer` module contains only "normal" code: it has no knowledge
of the existence of any wrapped values.
We can now easily do the following:
1. obtain sensitive data from the user (their credit card number)
1. store the sensitive data in a wrapper that still allows us to know
enough about the wrapped value
1. convert the sensitive data to a separate type (and different wrapper)
while retaining the ability to inspect non-sensitive portions
```elixir
"Please enter your credit card number: "
SensitiveData.gets_sensitive(into: CreditCard)
|> IO.inspect()
|> CreditCard.exec(&Tokenizer.tokenize/1, into: PaymentToken)
```
Which will output:
```elixir
#CreditCard<redacted: "5***********5100", ...>
#PaymentToken<redacted: "TOK_QTzY5m**************", ...>
```
And of course we can use guards in our code if we want to ensure only wrapped
data of the proper type is used:
```elixir
import SensitiveData.Guards, only: [is_sensitive: 2]
def pay(token) when is_sensitive(token, PaymentToken) do
# make the payment
end
def pay(credit_card) when is_sensitive(credit_card, CreditCard) do
credit_card
|> CreditCard.exec(& &1, into: PaymentToken)
|> pay()
end
```