emqx_authz_postgresql.erl 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  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(emqx_authz_postgresql).
  17. -include_lib("emqx/include/logger.hrl").
  18. -include_lib("emqx/include/emqx_placeholder.hrl").
  19. -include_lib("epgsql/include/epgsql.hrl").
  20. -behaviour(emqx_authz_source).
  21. %% AuthZ Callbacks
  22. -export([
  23. description/0,
  24. create/1,
  25. update/1,
  26. destroy/1,
  27. authorize/4
  28. ]).
  29. -ifdef(TEST).
  30. -compile(export_all).
  31. -compile(nowarn_export_all).
  32. -endif.
  33. -define(ALLOWED_VARS, [
  34. ?VAR_USERNAME,
  35. ?VAR_CLIENTID,
  36. ?VAR_PEERHOST,
  37. ?VAR_CERT_CN_NAME,
  38. ?VAR_CERT_SUBJECT
  39. ]).
  40. description() ->
  41. "AuthZ with PostgreSQL".
  42. create(#{query := SQL0} = Source) ->
  43. {SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?ALLOWED_VARS),
  44. ResourceID = emqx_authz_utils:make_resource_id(emqx_postgresql),
  45. {ok, _Data} = emqx_authz_utils:create_resource(
  46. ResourceID,
  47. emqx_postgresql,
  48. Source#{prepare_statement => #{ResourceID => SQL}}
  49. ),
  50. Source#{annotations => #{id => ResourceID, placeholders => PlaceHolders}}.
  51. update(#{query := SQL0, annotations := #{id := ResourceID}} = Source) ->
  52. {SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?ALLOWED_VARS),
  53. case
  54. emqx_authz_utils:update_resource(
  55. emqx_postgresql,
  56. Source#{prepare_statement => #{ResourceID => SQL}}
  57. )
  58. of
  59. {error, Reason} ->
  60. error({load_config_error, Reason});
  61. {ok, Id} ->
  62. Source#{annotations => #{id => Id, placeholders => PlaceHolders}}
  63. end.
  64. destroy(#{annotations := #{id := Id}}) ->
  65. emqx_authz_utils:remove_resource(Id).
  66. authorize(
  67. Client,
  68. Action,
  69. Topic,
  70. #{
  71. annotations := #{
  72. id := ResourceID,
  73. placeholders := Placeholders
  74. }
  75. }
  76. ) ->
  77. Vars = emqx_authz_utils:vars_for_rule_query(Client, Action),
  78. RenderedParams = emqx_authz_utils:render_sql_params(Placeholders, Vars),
  79. case
  80. emqx_resource:simple_sync_query(ResourceID, {prepared_query, ResourceID, RenderedParams})
  81. of
  82. {ok, Columns, Rows} ->
  83. do_authorize(Client, Action, Topic, column_names(Columns), Rows);
  84. {error, Reason} ->
  85. ?SLOG(error, #{
  86. msg => "query_postgresql_error",
  87. reason => Reason,
  88. params => RenderedParams,
  89. resource_id => ResourceID
  90. }),
  91. nomatch
  92. end.
  93. do_authorize(_Client, _Action, _Topic, _ColumnNames, []) ->
  94. nomatch;
  95. do_authorize(Client, Action, Topic, ColumnNames, [Row | Tail]) ->
  96. try
  97. emqx_authz_rule:match(
  98. Client, Action, Topic, emqx_authz_utils:parse_rule_from_row(ColumnNames, Row)
  99. )
  100. of
  101. {matched, Permission} -> {matched, Permission};
  102. nomatch -> do_authorize(Client, Action, Topic, ColumnNames, Tail)
  103. catch
  104. error:Reason:Stack ->
  105. ?SLOG(error, #{
  106. msg => "match_rule_error",
  107. reason => Reason,
  108. rule => Row,
  109. stack => Stack
  110. }),
  111. do_authorize(Client, Action, Topic, ColumnNames, Tail)
  112. end.
  113. column_names(Columns) ->
  114. lists:map(
  115. fun(#column{name = Name}) -> Name end,
  116. Columns
  117. ).