diff --git a/apps/emqx_authz/src/emqx_authz_postgresql.erl b/apps/emqx_authz/src/emqx_authz_postgresql.erl index cab6a9f11..f101841c2 100644 --- a/apps/emqx_authz/src/emqx_authz_postgresql.erl +++ b/apps/emqx_authz/src/emqx_authz_postgresql.erl @@ -53,8 +53,6 @@ destroy(#{annotations := #{id := Id}}) -> dry_run(Source) -> emqx_resource:create_dry_run(emqx_connector_pgsql, Source). -parse_query(undefined) -> - undefined; parse_query(Sql) -> case re:run(Sql, ?RE_PLACEHOLDER, [global, {capture, all, list}]) of {match, Capured} -> diff --git a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl index 09974d5a5..7db042dd8 100644 --- a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl @@ -21,7 +21,6 @@ -include("emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). -define(MYSQL_HOST, "mysql"). @@ -184,6 +183,29 @@ t_create_invalid(_Config) -> [] = emqx_authz:lookup(). +t_nonbinary_values(_Config) -> + ClientInfo = #{clientid => clientid, + username => "username", + peerhost => {127,0,0,1}, + zone => default, + listener => {tcp, default} + }, + + + ok = init_table(), + ok = q(<<"INSERT INTO acl(clientid, username, topic, permission, action)" + "VALUES(?, ?, ?, ?, ?)">>, + [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + + ok = setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid} AND username = ${username}">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>}]). + %%------------------------------------------------------------------------------ %% Helpers %%------------------------------------------------------------------------------ diff --git a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl index 0a4001757..92c479f92 100644 --- a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl @@ -21,7 +21,11 @@ -include("emqx_authz.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). + + +-define(PGSQL_HOST, "pgsql"). +-define(PGSQL_PORT, 5432). +-define(PGSQL_RESOURCE, <<"emqx_authz_pgsql_SUITE">>). all() -> emqx_common_test_helpers:all(?MODULE). @@ -30,101 +34,266 @@ groups() -> []. init_per_suite(Config) -> - meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), - meck:expect(emqx_resource, create, fun(_, _, _) -> {ok, meck_data} end ), - meck:expect(emqx_resource, remove, fun(_) -> ok end ), - - ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), - - Rules = [#{<<"type">> => <<"postgresql">>, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }], - {ok, _} = emqx_authz:update(replace, Rules), - Config. + case emqx_authn_test_lib:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_PORT) of + true -> + ok = emqx_common_test_helpers:start_apps( + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), + ok = start_apps([emqx_resource, emqx_connector]), + {ok, _} = emqx_resource:create_local( + ?PGSQL_RESOURCE, + emqx_connector_pgsql, + pgsql_config()), + Config; + false -> + {skip, no_pgsql} + end. end_per_suite(_Config) -> - {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), - emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), - meck:unload(emqx_resource), - ok. + ok = emqx_authz_test_lib:restore_authorizers(), + ok = emqx_resource:remove_local(?PGSQL_RESOURCE), + ok = stop_apps([emqx_resource, emqx_connector]), + ok = emqx_common_test_helpers:stop_apps([emqx_authz]). + +init_per_testcase(Config) -> + ok = emqx_authz_test_lib:reset_authorizers(), + Config. set_special_configs(emqx_authz) -> - {ok, _} = emqx:update_config([authorization, cache, enable], false), - {ok, _} = emqx:update_config([authorization, no_match], deny), - {ok, _} = emqx:update_config([authorization, sources], []), - ok; -set_special_configs(_App) -> - ok. + ok = emqx_authz_test_lib:reset_authorizers(); --define(COLUMNS, [ {column, <<"action">>, meck, meck, meck, meck, meck, meck, meck} - , {column, <<"permission">>, meck, meck, meck, meck, meck, meck, meck} - , {column, <<"topic">>, meck, meck, meck, meck, meck, meck, meck} - ]). --define(SOURCE1, [{<<"all">>, <<"deny">>, <<"#">>}]). --define(SOURCE2, [{<<"all">>, <<"allow">>, <<"eq #">>}]). --define(SOURCE3, [{<<"subscribe">>, <<"allow">>, <<"test/", ?PH_CLIENTID/binary>>}]). --define(SOURCE4, [{<<"publish">>, <<"allow">>, <<"test/", ?PH_USERNAME/binary>>}]). +set_special_configs(_) -> + ok. %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ -t_authz(_) -> - ClientInfo1 = #{clientid => <<"test">>, - username => <<"test">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - ClientInfo2 = #{clientid => <<"test_clientid">>, - username => <<"test_username">>, - peerhost => {192,168,0,10}, - zone => default, - listener => {tcp, default} - }, - ClientInfo3 = #{clientid => <<"test_clientid">>, - username => <<"fake_username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, +t_topic_rules(_Config) -> + ClientInfo = #{clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127,0,0,1}, + zone => default, + listener => {tcp, default} + }, - meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, []} end), - ?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, subscribe, <<"#">>)), % nomatch - ?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, publish, <<"#">>)), % nomatch + ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), - meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, ?SOURCE1 ++ ?SOURCE2} end), - ?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, subscribe, <<"+">>)), - ?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, publish, <<"+">>)), + ok = emqx_authz_test_lib:test_allow_topic_rules(ClientInfo, fun setup_client_samples/2), - meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, ?SOURCE2 ++ ?SOURCE1} end), - ?assertEqual(allow, emqx_access_control:authorize(ClientInfo1, subscribe, <<"#">>)), - ?assertEqual(deny, emqx_access_control:authorize(ClientInfo2, subscribe, <<"+">>)), + ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, fun setup_client_samples/2). - meck:expect(emqx_resource, query, fun(_, _) -> {ok, ?COLUMNS, ?SOURCE3 ++ ?SOURCE4} end), - ?assertEqual(allow, emqx_access_control:authorize( - ClientInfo2, subscribe, <<"test/test_clientid">>)), - ?assertEqual(deny, emqx_access_control:authorize( - ClientInfo2, publish, <<"test/test_clientid">>)), - ?assertEqual(deny, emqx_access_control:authorize( - ClientInfo2, subscribe, <<"test/test_username">>)), - ?assertEqual(allow, emqx_access_control:authorize( - ClientInfo2, publish, <<"test/test_username">>)), - ?assertEqual(deny, emqx_access_control:authorize( - ClientInfo3, subscribe, <<"test">>)), % nomatch - ?assertEqual(deny, emqx_access_control:authorize( - ClientInfo3, publish, <<"test">>)), % nomatch + +t_lookups(_Config) -> + ClientInfo = #{clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127,0,0,1}, + zone => default, + listener => {tcp, default} + }, + + %% by clientid + + ok = init_table(), + ok = insert(<<"INSERT INTO acl(clientid, topic, permission, action)" + "VALUES($1, $2, $3, $4)">>, + [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + + ok = setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid}">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>}]), + + %% by peerhost + + ok = init_table(), + ok = insert(<<"INSERT INTO acl(peerhost, topic, permission, action)" + "VALUES($1, $2, $3, $4)">>, + [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + + ok = setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE peerhost = ${peerhost}">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>}]), + + %% by cn + + ok = init_table(), + ok = insert(<<"INSERT INTO acl(cn, topic, permission, action)" + "VALUES($1, $2, $3, $4)">>, + [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + + ok = setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE cn = ${cert_common_name}">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>}]), + + %% by dn + + ok = init_table(), + ok = insert(<<"INSERT INTO acl(dn, topic, permission, action)" + "VALUES($1, $2, $3, $4)">>, + [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + + ok = setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE dn = ${cert_subject}">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>}]). + +t_pgsql_error(_Config) -> + ClientInfo = #{clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127,0,0,1}, + zone => default, + listener => {tcp, default} + }, + + ok = setup_config( + #{<<"query">> => <<"SOME INVALID STATEMENT">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{deny, subscribe, <<"a">>}]). + + +t_create_invalid(_Config) -> + BadConfig = maps:merge( + raw_pgsql_authz_config(), + #{<<"server">> => <<"255.255.255.255:33333">>}), + {error, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]), + + [] = emqx_authz:lookup(). + +t_nonbinary_values(_Config) -> + ClientInfo = #{clientid => clientid, + username => "username", + peerhost => {127,0,0,1}, + zone => default, + listener => {tcp, default} + }, + + + ok = init_table(), + ok = insert(<<"INSERT INTO acl(clientid, username, topic, permission, action)" + "VALUES($1, $2, $3, $4, $5)">>, + [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + + ok = setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid} AND username = ${username}">>}), + + ok = emqx_authz_test_lib:test_samples( + ClientInfo, + [{allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>}]). + +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ + +raw_pgsql_authz_config() -> + #{ + <<"enable">> => <<"true">>, + + <<"type">> => <<"postgresql">>, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"root">>, + <<"password">> => <<"public">>, + + <<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE username = ${username}">>, + + <<"server">> => pgsql_server() + }. + +q(Sql) -> + emqx_resource:query( + ?PGSQL_RESOURCE, + {sql, Sql}). + +insert(Sql, Params) -> + {ok, _} = emqx_resource:query( + ?PGSQL_RESOURCE, + {sql, Sql, Params}), ok. + +init_table() -> + ok = drop_table(), + {ok, _, _} = q("CREATE TABLE acl( + username VARCHAR(255), + clientid VARCHAR(255), + peerhost VARCHAR(255), + cn VARCHAR(255), + dn VARCHAR(255), + topic VARCHAR(255), + permission VARCHAR(255), + action VARCHAR(255))"), + ok. + +drop_table() -> + {ok, _, _} = q("DROP TABLE IF EXISTS acl"), + ok. + +setup_client_samples(ClientInfo, Samples) -> + #{username := Username} = ClientInfo, + ok = init_table(), + ok = lists:foreach( + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:foreach( + fun(Topic) -> + insert(<<"INSERT INTO acl(username, topic, permission, action)" + "VALUES($1, $2, $3, $4)">>, + [Username, Topic, Permission, Action]) + end, + Topics) + end, + Samples), + setup_config( + #{<<"query">> => <<"SELECT permission, action, topic " + "FROM acl WHERE username = ${username}">>}). + +setup_config(SpecialParams) -> + emqx_authz_test_lib:setup_config( + raw_pgsql_authz_config(), + SpecialParams). + +pgsql_server() -> + iolist_to_binary( + io_lib:format( + "~s:~b", + [?PGSQL_HOST, ?PGSQL_PORT])). + +pgsql_config() -> + #{auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?PGSQL_HOST, ?PGSQL_PORT}, + ssl => #{enable => false} + }. + +start_apps(Apps) -> + lists:foreach(fun application:ensure_all_started/1, Apps). + +stop_apps(Apps) -> + lists:foreach(fun application:stop/1, Apps).