emqttd_access_rule.erl 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io)
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqttd_access_rule).
  17. -author("Feng Lee <feng@emqtt.io>").
  18. -include("emqttd.hrl").
  19. -type(who() :: all | binary() |
  20. {ipaddr, esockd_cidr:cidr_string()} |
  21. {client, binary()} |
  22. {user, binary()}).
  23. -type(access() :: subscribe | publish | pubsub).
  24. -type(topic() :: binary()).
  25. -type(rule() :: {allow, all} |
  26. {allow, who(), access(), list(topic())} |
  27. {deny, all} |
  28. {deny, who(), access(), list(topic())}).
  29. -export_type([rule/0]).
  30. -export([compile/1, match/3]).
  31. -define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= deny))).
  32. %% @doc Compile Access Rule.
  33. compile({A, all}) when ?ALLOW_DENY(A) ->
  34. {A, all};
  35. compile({A, Who, Access, Topic}) when ?ALLOW_DENY(A) andalso is_binary(Topic) ->
  36. {A, compile(who, Who), Access, [compile(topic, Topic)]};
  37. compile({A, Who, Access, TopicFilters}) when ?ALLOW_DENY(A) ->
  38. {A, compile(who, Who), Access, [compile(topic, Topic) || Topic <- TopicFilters]}.
  39. compile(who, all) ->
  40. all;
  41. compile(who, {ipaddr, CIDR}) ->
  42. {ipaddr, esockd_cidr:parse(CIDR, true)};
  43. compile(who, {client, all}) ->
  44. {client, all};
  45. compile(who, {client, ClientId}) ->
  46. {client, bin(ClientId)};
  47. compile(who, {user, all}) ->
  48. {user, all};
  49. compile(who, {user, Username}) ->
  50. {user, bin(Username)};
  51. compile(who, {'and', Conds}) when is_list(Conds) ->
  52. {'and', [compile(who, Cond) || Cond <- Conds]};
  53. compile(who, {'or', Conds}) when is_list(Conds) ->
  54. {'or', [compile(who, Cond) || Cond <- Conds]};
  55. compile(topic, {eq, Topic}) ->
  56. {eq, emqttd_topic:words(bin(Topic))};
  57. compile(topic, Topic) ->
  58. Words = emqttd_topic:words(bin(Topic)),
  59. case 'pattern?'(Words) of
  60. true -> {pattern, Words};
  61. false -> Words
  62. end.
  63. 'pattern?'(Words) ->
  64. lists:member(<<"%u">>, Words)
  65. orelse lists:member(<<"%c">>, Words).
  66. bin(L) when is_list(L) ->
  67. list_to_binary(L);
  68. bin(B) when is_binary(B) ->
  69. B.
  70. %% @doc Match Access Rule
  71. -spec(match(mqtt_client(), topic(), rule()) -> {matched, allow} | {matched, deny} | nomatch).
  72. match(_Client, _Topic, {AllowDeny, all}) when (AllowDeny =:= allow) orelse (AllowDeny =:= deny) ->
  73. {matched, AllowDeny};
  74. match(Client, Topic, {AllowDeny, Who, _PubSub, TopicFilters})
  75. when (AllowDeny =:= allow) orelse (AllowDeny =:= deny) ->
  76. case match_who(Client, Who) andalso match_topics(Client, Topic, TopicFilters) of
  77. true -> {matched, AllowDeny};
  78. false -> nomatch
  79. end.
  80. match_who(_Client, all) ->
  81. true;
  82. match_who(_Client, {user, all}) ->
  83. true;
  84. match_who(_Client, {client, all}) ->
  85. true;
  86. match_who(#mqtt_client{client_id = ClientId}, {client, ClientId}) ->
  87. true;
  88. match_who(#mqtt_client{username = Username}, {user, Username}) ->
  89. true;
  90. match_who(#mqtt_client{peername = undefined}, {ipaddr, _Tup}) ->
  91. false;
  92. match_who(#mqtt_client{peername = {IP, _}}, {ipaddr, CIDR}) ->
  93. esockd_cidr:match(IP, CIDR);
  94. match_who(Client, {'and', Conds}) when is_list(Conds) ->
  95. lists:foldl(fun(Who, Allow) ->
  96. match_who(Client, Who) andalso Allow
  97. end, true, Conds);
  98. match_who(Client, {'or', Conds}) when is_list(Conds) ->
  99. lists:foldl(fun(Who, Allow) ->
  100. match_who(Client, Who) orelse Allow
  101. end, false, Conds);
  102. match_who(_Client, _Who) ->
  103. false.
  104. match_topics(_Client, _Topic, []) ->
  105. false;
  106. match_topics(Client, Topic, [{pattern, PatternFilter}|Filters]) ->
  107. TopicFilter = feed_var(Client, PatternFilter),
  108. case match_topic(emqttd_topic:words(Topic), TopicFilter) of
  109. true -> true;
  110. false -> match_topics(Client, Topic, Filters)
  111. end;
  112. match_topics(Client, Topic, [TopicFilter|Filters]) ->
  113. case match_topic(emqttd_topic:words(Topic), TopicFilter) of
  114. true -> true;
  115. false -> match_topics(Client, Topic, Filters)
  116. end.
  117. match_topic(Topic, {eq, TopicFilter}) ->
  118. Topic =:= TopicFilter;
  119. match_topic(Topic, TopicFilter) ->
  120. emqttd_topic:match(Topic, TopicFilter).
  121. feed_var(Client, Pattern) ->
  122. feed_var(Client, Pattern, []).
  123. feed_var(_Client, [], Acc) ->
  124. lists:reverse(Acc);
  125. feed_var(Client = #mqtt_client{client_id = undefined}, [<<"%c">>|Words], Acc) ->
  126. feed_var(Client, Words, [<<"%c">>|Acc]);
  127. feed_var(Client = #mqtt_client{client_id = ClientId}, [<<"%c">>|Words], Acc) ->
  128. feed_var(Client, Words, [ClientId |Acc]);
  129. feed_var(Client = #mqtt_client{username = undefined}, [<<"%u">>|Words], Acc) ->
  130. feed_var(Client, Words, [<<"%u">>|Acc]);
  131. feed_var(Client = #mqtt_client{username = Username}, [<<"%u">>|Words], Acc) ->
  132. feed_var(Client, Words, [Username|Acc]);
  133. feed_var(Client, [W|Words], Acc) ->
  134. feed_var(Client, Words, [W|Acc]).