./ct_report/coverage/acl.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : acl.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : ACL support
5 %%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(acl).
27 -author('alexey@process-one.net').
28
29 -export([match_rule/3, match_rule/4, match_rule/5,
30 merge_access_rules/2]).
31
32 -include("jlib.hrl").
33 -include("mongoose.hrl").
34
35 -export_type([rule_name/0]).
36
37 %% A rule consists of clauses matched from top to bottom
38 %% Each clause returns specific values (results) for specific acl's
39 %% There are two predefined rules: 'all' (always allow) and 'none' (always deny)
40 -type rule_name() :: all | none | atom().
41 -type rule_clause() :: #{acl := acl_name(), value := acl_result()}.
42
43 %% Rules can allow/deny access or return other (e.g. numerical) values
44 -type acl_result() :: allow | deny | term().
45
46 %% An acl is a union of specs that decide which users belong to it
47 %% There are two predefined acl's: 'all' (match everyone) and 'none' (match nobody)
48 -type acl_name() :: all | none | atom().
49 -type acl_spec() :: #{match := all | none | current_domain | any_hosted_domain,
50 acl_spec_key() => binary()}.
51 -type acl_spec_key() :: user | user_regexp | user_glob
52 | server | server_regexp | server_glob
53 | resource | resource_regexp | resource_glob.
54
55 %% Skips the domain check for the 'match => current_domain' condition
56 -spec match_rule(mongooseim:host_type_or_global(), rule_name(), jid:jid()) -> acl_result().
57 match_rule(HostType, RuleName, JID) ->
58 82 match_rule(HostType, JID#jid.lserver, RuleName, JID).
59
60 -spec match_rule(mongooseim:host_type_or_global(), jid:lserver(), rule_name(), jid:jid()) ->
61 acl_result().
62 match_rule(_HostType, _Domain, all, _JID) ->
63 37 allow;
64 match_rule(_HostType, _Domain, none, _JID) ->
65 5762 deny;
66 match_rule(HostType, Domain, RuleName, JID) ->
67 18088 match_rule(HostType, Domain, RuleName, JID, deny).
68
69 -spec match_rule(mongooseim:host_type_or_global(), jid:lserver(), rule_name(), jid:jid(),
70 Default :: acl_result()) ->
71 acl_result().
72 match_rule(HostType, Domain, RuleName, JID, Default) ->
73 20028 case mongoose_config:lookup_opt([{access, HostType}, RuleName]) of
74 {error, not_found} ->
75 4467 Default;
76 {ok, RuleSpec} ->
77 15561 match_rule_clauses(RuleSpec, JID, HostType, Domain)
78 end.
79
80 %% Merge host-type rules with global ones on startup
81 -spec merge_access_rules([rule_clause()], [rule_clause()]) -> [rule_clause()].
82 merge_access_rules(Global, HostLocal) ->
83
:-(
case lists:reverse(Global) of
84 [#{acl := all, value := allow} = AllowAll | Rest] ->
85
:-(
lists:reverse(Rest) ++ HostLocal ++ [AllowAll];
86 _ ->
87
:-(
Global ++ HostLocal
88 end.
89
90 -spec match_rule_clauses([rule_clause()], jid:jid(), mongooseim:host_type_or_global(),
91 jid:lserver()) ->
92 acl_result().
93 match_rule_clauses([], _, _HostType, _Domain) ->
94
:-(
deny;
95 match_rule_clauses([#{acl := ACLName, value := Value} | RemainingClauses], JID, HostType, Domain) ->
96 21294 case match_acl(ACLName, JID, HostType, Domain) of
97 true ->
98 15561 Value;
99 _ ->
100 5733 match_rule_clauses(RemainingClauses, JID, HostType, Domain)
101 end.
102
103 -spec match_acl(acl_name(), jid:jid(), mongooseim:host_type_or_global(), jid:lserver()) ->
104 boolean().
105 match_acl(all, _JID, _HostType, _Domain) ->
106 15509 true;
107 match_acl(none, _JID, _HostType, _Domain) ->
108
:-(
false;
109 match_acl(ACLName, JID, HostType, Domain) ->
110 5785 lists:any(fun(ACLSpec) -> match(ACLSpec, Domain, JID) end, get_acl_specs(ACLName, HostType)).
111
112 -spec get_acl_specs(acl_name(), mongooseim:host_type_or_global()) -> [acl_spec()].
113 get_acl_specs(ACLName, HostType) ->
114 5785 mongoose_config:get_opt([{acl, HostType}, ACLName], []).
115
116 %% @doc Check if all conditions from ACLSpec are satisfied by JID
117 -spec match(acl_spec(), jid:lserver(), jid:jid()) -> boolean().
118 match(ACLSpec, Domain, JID) when map_size(ACLSpec) > 0 ->
119 52 match_step(maps:next(maps:iterator(ACLSpec)), Domain, JID).
120
121 match_step({K, V, I}, Domain, JID) ->
122 53 check(K, V, Domain, JID) andalso match_step(maps:next(I), Domain, JID);
123 match_step(none, _Domain, _JID) ->
124 52 true.
125
126 -spec check(acl_spec_key(), binary(), jid:lserver(), jid:jid()) -> boolean().
127
:-(
check(match, all, _, _) -> true;
128
:-(
check(match, none, _, _) -> false;
129 check(match, any_hosted_domain, _, JID) ->
130
:-(
mongoose_domain_api:get_host_type(JID#jid.lserver) =/= {error, not_found};
131 52 check(match, current_domain, Domain, JID) -> JID#jid.lserver =:= Domain;
132 1 check(user, User, _, JID) -> JID#jid.luser =:= User;
133
:-(
check(user_regexp, Regexp, _, JID) -> is_regexp_match(JID#jid.luser, Regexp);
134
:-(
check(user_glob, Glob, _, JID) -> is_glob_match(JID#jid.luser, Glob);
135
:-(
check(server, Server, _, JID) -> JID#jid.lserver =:= Server;
136
:-(
check(server_regexp, Regexp, _, JID) -> is_regexp_match(JID#jid.lserver, Regexp);
137
:-(
check(server_glob, Glob, _, JID) -> is_glob_match(JID#jid.lserver, Glob);
138
:-(
check(resource, Resource, _, JID) -> JID#jid.lresource =:= Resource;
139
:-(
check(resource_regexp, Regexp, _, JID) -> is_regexp_match(JID#jid.lresource, Regexp);
140
:-(
check(resource_glob, Glob, _, JID) -> is_glob_match(JID#jid.lresource, Glob).
141
142 -spec is_regexp_match(binary(), RegExp :: iodata()) -> boolean().
143 is_regexp_match(String, RegExp) ->
144
:-(
try re:run(String, RegExp, [{capture, none}]) of
145 nomatch ->
146
:-(
false;
147 match ->
148
:-(
true
149 catch _:ErrDesc ->
150
:-(
?LOG_ERROR(#{what => acl_regexp_match_failed,
151
:-(
string => String, regex => RegExp, reason => ErrDesc}),
152
:-(
false
153 end.
154
155 -spec is_glob_match(binary(), Glob :: binary()) -> boolean().
156 is_glob_match(String, Glob) ->
157
:-(
is_regexp_match(String, xmerl_regexp:sh_to_awk(binary_to_list(Glob))).
Line Hits Source