From 74c8d109c400849346703f8fbea87dc29fe56ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chrz=C4=85szcz?= Date: Wed, 12 Jan 2022 15:41:32 +0100 Subject: [PATCH] Store ACL specs in maps 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). --- src/acl.erl | 118 ++++++++++++++-------------- src/config/mongoose_config_spec.erl | 39 +-------- 2 files changed, 59 insertions(+), 98 deletions(-) diff --git a/src/acl.erl b/src/acl.erl index d9eeb43fe4..95b2189bc4 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -31,8 +31,11 @@ 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]). @@ -40,23 +43,9 @@ -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(). @@ -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(), @@ -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) -> @@ -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 -> @@ -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))). diff --git a/src/config/mongoose_config_spec.erl b/src/config/mongoose_config_spec.erl index 6000440dad..2ba0f8d9a5 100644 --- a/src/config/mongoose_config_spec.erl +++ b/src/config/mongoose_config_spec.erl @@ -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, @@ -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 @@ -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}.