%%-------------- Default ACL rules -------------------------------------------------------

{allow, {username, {re, "^dashboard$"}}, subscribe, ["$SYS/#"]}.

{allow, {ipaddr, "127.0.0.1"}, all, ["$SYS/#", "#"]}.

{deny, all, subscribe, ["$SYS/#", {eq, "#"}, {eq, "+/#"}]}.

{allow, all}.
%% NOTE! when deploy in production:
%% - Change the last rule to `{deny, all}.`
%% - Set config `authorization.no_match = deny`

%% See docs below
%%
%% ------------ The formal spec ----------------------------------------------------------
%%
%% -type ipaddr() :: {ipaddr, string()}.
%% -type ipaddrs() :: {ipaddrs, [string()]}.
%% -type username() :: {user | username, string()} | {user | username, {re, regex()}}.
%% -type clientid() :: {client | clientid, string()} | {client | clientid, {re, regex()}}.
%% -type client_attr() :: {client_attr, string(), string()} | {client_attr, string(), {re, regex()}}.
%% -type who() :: all |
%%                ipaddr() | ipaddrs() | username() | clientid() | client_attr() |
%%                {'and', [ipaddr() | ipaddrs() | username() | clientid() | client_attr()]} |
%%                {'or',  [ipaddr() | ipaddrs() | username() | clientid() | client_attr()]}.
%% -type simple_action() :: subscribe | publish | all.
%% -type complex_action() :: {simple_action(), [{qos, 0..2}, {retain, true|false|all}]}.
%% -type action() :: simple_action() | complex_action().
%% -type topic() :: string().
%% -type topic_filter() :: string().
%% -type topic_match() :: topic() | topic_filter() | {eq, topic() | topic_filter()}.
%% -type perm() :: allow | deny.
%% -type rule() :: {perm(), who(), action(), [topic_match()]} | {perm(), all}.

%%-------------- Viusal aid for the spec -------------------------------------------------
%%
%% rule()
%% ├── {perm(), who(), action(), [topic_match()]}
%% │    │       │      │          ├── topic() :: string()
%% │    │       │      │          ├── topic_filter() :: string()
%% │    │       │      │          └── {eq, topic() | topic_filter()}
%% │    │       │      │
%% │    │       │      ├── simple_action()
%% │    │       │      │   ├── publish
%% │    │       │      │   ├── subscribe
%% │    │       │      │   └── all
%% │    │       │      └── {simple_action(), [{qos,0..2},{retain,true|false|all}]}
%% │    │       │
%% │    │       ├── ipaddr()
%% │    │       │   └── {ipaddr, string()}
%% │    │       ├── ipaddrs()
%% │    │       │   └── {ipaddrs, [string()]}
%% │    │       ├── username()
%% │    │       │   ├── {user | username, string()}
%% │    │       │   └── {user | username, {re, regex()}}
%% │    │       ├── clientid()
%% │    │       │   ├── {client | clientid, string()}
%% │    │       │   └── {client | clientid, {re, regex()}}
%% │    │       ├── client_attr()
%% │    │       │   ├── {client_attr, Name :: string(), Value :: string()}
%% │    │       │   └── {client_attr, Name :: string(), ValuePattern :: {re, regex()}}
%% │    │       ├── {'and', [ipaddr() | ipaddrs() | username() | clientid() | client_attr()]}
%% │    │       ├── {'or', [ipaddr() | ipaddrs() | username() | clientid() | client_attr()]}
%% │    │       └── all
%% │    │
%% │    ├── allow
%% │    └── deny
%% │
%% └── {perm(), all}
%%

%% This file defines a set of ACL rules for MQTT client pub/sub authorization.
%% The content is of Erlang-term format.
%% Each Erlang-term is a tuple `{...}` terminated by dot `.`
%%
%% NOTE: When deploy to production, the last rule should be changed to {deny, all}.
%%
%% NOTE: It's a good practice to keep the nubmer of rules small, because in worst case
%%       scenarios, all rules have to be traversed for each message publish.
%%
%% A rule is a 4-element tuple.
%% For example, `{allow, {username, "Jon"}, subscribe, ["#"]}` allows Jon to subscribe to
%% any topic they want.
%%
%% Below is an explanation:
%%
%% - `perm()`: The permission.
%%   Defines whether this is an `allow` or `deny` rule.
%%
%% - `who()`: The MQTT client matching condition.
%%   - `all`: A rule which applies to all clients.
%%   - `{ipaddr, IpAddress}`: Matches a client by source IP address. CIDR notation is allowed.
%%   - `{ipaddrs, [IpAddress]}`: Matches clients by a set of IP addresses. CIDR notation is allowed.
%%   - `{clientid, ClientID}`: Matches a client by ID.
%%   - `{username, Username}`: Matches a client by username.
%%   - `{client_attr, AttrName, AttrValue}`: Matches a client by client-attribute.
%%   - `{..., {re, ..}}`: Regular expression to match either clientid or username.
%%   - `{'and', [...]}`: Combines a list of matching conditions.
%%   - `{'or', [...]}`: Combines a list of matching conditions.
%%
%% - `action()`: Matches publish or subscribe actions (or both).
%%   Applies the rule to `publish` or `subscribe` actions.
%%   The special value `all` denotes allowing or denying both `publish` and `subscribe`.
%%   It can also be associated with `qos` and `retain` flags to match the action with
%%   more specifics. For example, `{publish, [{qos,0},{retain,false}]}` should only
%%   match the `publish` action when the message has QoS 0, and without retained flag set.
%%
%% - `[topic_match()]`:
%%   A list of topics, topic-filters, or template rendering to match the topic being
%%   subscribed to or published.
%%   For example, `{allow, {username, "Jan"}, publish, ["jan/#"]}` permits Jan to publish
%%   to any topic matching the wildcard pattern "jan/#".
%%   A special tuple `{eq, topic_match()}` is useful to allow or deny the specific wildcard
%%   subscription instead of performing a topic match.
%%   A special value `all` is like topic filter `"#"`, but also matches `$` prefixed topics.
%%   A `topic_match()` can also contain a placeholder rendered with actual value at runtime,
%%   for example, `{allow, all, publish, "${clientid}/#"}` allows all clients to publish to
%%   topics prefixed by their own client ID.
%%
%%   Supported placeholders are:
%%   - `${username}`: The username.
%%   - `${clientid}`: The client ID.
%%   - `${cert_common_name}`: TLS certificate common name.
%%   - `${client_attrs.NAME}`: A client attribute named `NAME`, which can be initialized by
%%     `mqtt.client_attrs_init` config or extended by certain authentication backends.
%%   NOTE: Placeholder is not rendered as empty string if the referencing value is not
%%         foud. For example, `${client_attrs.group}/#` is not rendered as `/#` if the
%%         client does not have a `group` attribute.
