Skip to content

Commit

Permalink
Store ACL specs in maps
Browse files Browse the repository at this point in the history
Also unify the patterns in specs:
  - allow more combinations, e.g. user + server + resource
  - check the domain always, not only for some patterns
    (e.g. user but not resource).
  • Loading branch information
chrzaszcz committed Jan 12, 2022
1 parent b9b34c2 commit 74c8d10
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 98 deletions.
118 changes: 57 additions & 61 deletions src/acl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,21 @@
match_rule_for_host_type/4,
match_rule_for_host_type/5]).

-export([prepare_spec/1]).

-ignore_xref([add/3, delete/3, match_rule/4]).

-include("jlib.hrl").
-include("mongoose.hrl").

-export_type([rule/0, domain_or_global/0, host_type_or_global/0]).

-type rule() :: 'all' | 'none' | atom().
-type domain_or_global() :: jid:lserver() | global.
-type host_type_or_global() :: mongooseim:host_type() | global.
-type regexp() :: iolist() | binary().
-type aclspec() :: all
| none
| {user, jid:user()}
| {user, jid:user(), jid:server()}
| {server, jid:server()}
| {resource, jid:resource()}
| {user_regexp, regexp()}
| {user_regexp, regexp(), jid:server()}
| {server_regexp, regexp()}
| {resource_regexp, regexp()}
| {node_regexp, regexp(), regexp()}
| {user_glob, regexp()}
| {user_glob, regexp(), jid:server()}
| {server_glob, regexp()}
| {resource_glob, regexp()}
| {node_glob, regexp(), regexp()}.
-type aclspec() :: #{match => all | none} | #{jid_part() => acl_pattern()}.
-type jid_part() :: user | server | resource.
-type acl_pattern() :: {value | regexp | glob, binary()}.

-type acl_result() :: allow | deny | term().

Expand Down Expand Up @@ -87,8 +76,8 @@ match_rule(Domain, Rule, JID, Default) ->
%% HostType determines which rules and ACLs are checked:
%% - 'global' - only global ones
%% - a specific host type - both global and per-host-type ones
%% Domain is only used for validating the user's domain name in the {user, U} pattern:
%% - 'global' - any domain is accepted
%% Domain is only used for validating the user's domain name:
%% - 'global' - any domain hosted by the server is accepted
%% - a specific domain name - only the provided name is accepted
-spec match_rule_for_host_type(HostType :: host_type_or_global(),
Domain :: domain_or_global(),
Expand Down Expand Up @@ -151,13 +140,12 @@ match_acl(all, _JID, _HostType, _Domain) ->
match_acl(none, _JID, _HostType, _Domain) ->
false;
match_acl(Rule, JID, HostType, Domain) ->
LJID = jid:to_lower(JID),
AllSpecs = case HostType of
global -> get_acl_specs(Rule, global);
_ -> get_acl_specs(Rule, HostType) ++ get_acl_specs(Rule, global)
end,
Pred = fun(ACLSpec) -> match(ACLSpec, LJID, Domain) end,
lists:any(Pred, AllSpecs).
Pred = fun(ACLSpec) -> match(ACLSpec, JID) end,
is_server_valid(Domain, JID#jid.lserver) andalso lists:any(Pred, AllSpecs).

-spec get_acl_specs(rule(), host_type_or_global()) -> [aclspec()].
get_acl_specs(Rule, HostType) ->
Expand All @@ -176,45 +164,53 @@ is_server_valid(global, JIDServer) ->
is_server_valid(_Domain, _JIDServer) ->
false.

-spec match(aclspec(), jid:simple_jid(), domain_or_global()) -> boolean().
match(all, _LJID, _Domain) ->
true;
match({user, U}, {User, Server, _Resource}, Domain) ->
U == User andalso is_server_valid(Domain, Server);
match({user, U, S}, {User, Server, _Resource}, _Domain) ->
U == User andalso S == Server;
match({server, S}, {_User, Server, _Resource}, _Domain) ->
S == Server;
match({resource, Res}, {_User, _Server, Resource}, _Domain) ->
Resource == Res;
match({user_regexp, UserReg}, {User, Server, _Resource}, Domain) ->
is_server_valid(Domain, Server) andalso is_regexp_match(User, UserReg);
match({user_regexp, UserReg, MServer}, {User, Server, _Resource}, _Domain) ->
MServer == Server andalso is_regexp_match(User, UserReg);
match({server_regexp, ServerReg}, {_User, Server, _Resource}, _Domain) ->
is_regexp_match(Server, ServerReg);
match({resource_regexp, ResourceReg}, {_User, _Server, Resource}, _Domain) ->
is_regexp_match(Resource, ResourceReg);
match({node_regexp, UserReg, ServerReg}, {User, Server, _Resource}, _Domain) ->
is_regexp_match(Server, ServerReg) andalso
is_regexp_match(User, UserReg);
match({user_glob, UserGlob}, {User, Server, _Resource}, Domain) ->
is_server_valid(Domain, Server) andalso is_glob_match(User, UserGlob);
match({user_glob, UserGlob, MServer}, {User, Server, _Resource}, _Domain) ->
MServer == Server andalso is_glob_match(User, UserGlob);
match({server_glob, ServerGlob}, {_User, Server, _Resource}, _Domain) ->
is_glob_match(Server, ServerGlob);
match({resource_glob, ResourceGlob}, {_User, _Server, Resource}, _Domain) ->
is_glob_match(Resource, ResourceGlob);
match({node_glob, UserGlob, ServerGlob}, {User, Server, _Resource}, _Domain) ->
is_glob_match(Server, ServerGlob) andalso is_glob_match(User, UserGlob);
match(WrongSpec, _LJID, _Domain) ->
?LOG_ERROR(#{what => wrong_acl_expression,
text => <<"Wrong ACL expression in the configuration file">>,
wrong_spec => WrongSpec}),
false.
-spec match(aclspec(), jid:jid()) -> boolean().
match(#{match := Match}, _JID) ->
Match =:= all;
match(M, JID) ->
lists:all(fun({K, V}) -> check(K, V, JID) end, maps:to_list(M)).

-spec check(jid_part(), acl_pattern(), jid:jid()) -> boolean().
check(user, Spec, #jid{luser = LUser}) -> check(Spec, LUser);
check(server, Spec, #jid{lserver = LServer}) -> check(Spec, LServer);
check(resource, Spec, #jid{lresource = LResource}) -> check(Spec, LResource).

-spec check(acl_pattern(), binary()) -> boolean().
check({regexp, Regexp}, Value) -> is_regexp_match(Value, Regexp);
check({glob, Glob}, Value) -> is_glob_match(Value, Glob);
check({value, ExpValue}, Value) -> ExpValue =:= Value.

-spec prepare_spec(map()) -> aclspec().
prepare_spec(M) ->
KVs = [prepare_check(K, V) || {K, V} <- maps:to_list(M)],
case conflicts(proplists:get_keys(KVs)) of
[] -> maps:from_list(KVs);
Conflicts ->
error(#{what => wrong_acl_expression,
text => <<"Wrong ACL expression in the configuration file">>,
wrong_spec => M,
conflicts => Conflicts})
end.

-spec conflicts([atom()]) -> [atom()].
conflicts(Keys) ->
case lists:member(match, Keys) of
true -> Keys -- [match];
false -> Keys -- [user, server, resource]
end.

-spec is_regexp_match(binary(), Regex :: regexp()) -> boolean().
prepare_check(user, User) -> {user, {value, User}};
prepare_check(server, Server) -> {server, {value, Server}};
prepare_check(resource, Resource) -> {resource, {value, Resource}};
prepare_check(user_regexp, Regexp) -> {user, {regexp, Regexp}};
prepare_check(server_regexp, Regexp) -> {server, {regexp, Regexp}};
prepare_check(resource_regexp, Regexp) -> {resource, {regexp, Regexp}};
prepare_check(user_glob, Glob) -> {user, {glob, Glob}};
prepare_check(server_glob, Glob) -> {server, {glob, Glob}};
prepare_check(resource_glob, Glob) -> {resource, {glob, Glob}};
prepare_check(match, Value) -> {match, Value}.

-spec is_regexp_match(binary(), RegExp :: iodata()) -> boolean().
is_regexp_match(String, RegExp) ->
try re:run(String, RegExp, [{capture, none}]) of
nomatch ->
Expand All @@ -227,6 +223,6 @@ is_regexp_match(String, RegExp) ->
false
end.

-spec is_glob_match(binary(), Glob :: regexp()) -> boolean().
-spec is_glob_match(binary(), Glob :: binary()) -> boolean().
is_glob_match(String, Glob) ->
is_regexp_match(String, xmerl_regexp:sh_to_awk(binary_to_list(Glob))).
39 changes: 2 additions & 37 deletions src/config/mongoose_config_spec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
process_riak_credentials/1,
process_iqdisc/1,
process_shaper/1,
process_acl_item/1,
process_access_rule_item/1,
process_s2s_address_family/1,
process_s2s_host_policy/1,
Expand Down Expand Up @@ -839,7 +838,8 @@ acl_item() ->
<<"resource_glob">> => #option{type = binary}
},
validate_keys = non_empty,
process = fun ?MODULE:process_acl_item/1
format_items = map,
process = fun acl:prepare_spec/1
}.

%% path: (host_config[].)access
Expand Down Expand Up @@ -1235,41 +1235,6 @@ wpool_strategy_values() ->
process_shaper([MaxRate]) ->
MaxRate.

process_acl_item([{match, V}]) -> V;
process_acl_item(KVs) ->
{AclName, AclKeys} = find_acl(KVs, lists:sort(proplists:get_keys(KVs)), acl_keys()),
list_to_tuple([AclName | lists:map(fun(K) ->
prepare_acl_value(proplists:get_value(K, KVs))
end, AclKeys)]).

find_acl(KVs, SortedKeys, [{AclName, AclKeys}|Rest]) ->
case lists:sort(AclKeys) of
SortedKeys -> {AclName, AclKeys};
_ -> find_acl(KVs, SortedKeys, Rest)
end.

acl_keys() ->
[{user, [user, server]},
{user, [user]},
{server, [server]},
{resource, [resource]},
{user_regexp, [user_regexp, server]},
{node_regexp, [user_regexp, server_regexp]},
{user_regexp, [user_regexp]},
{server_regexp, [server_regexp]},
{resource_regexp, [resource_regexp]},
{user_glob, [user_glob, server]},
{node_glob, [user_glob, server_glob]},
{user_glob, [user_glob]},
{server_glob, [server_glob]},
{resource_glob, [resource_glob]}
].

prepare_acl_value(Value) ->
Node = jid:nodeprep(Value),
true = Node =/= error,
Node.

process_access_rule_item(KVs) ->
{[[{acl, Acl}], [{value, Value}]], []} = proplists:split(KVs, [acl, value]),
{Value, Acl}.
Expand Down

0 comments on commit 74c8d10

Please sign in to comment.