fix: generate palce holder (#6250)

* fix: generate place holder

* style: whitespace cleanup

* refactor(authz): placeholder for athuz

* test: authz test suite for placeholder

* fix: lw place holder suite

* fix: auth n redis suite

Co-authored-by: JimMoen <LnJimMoen@outlook.com>
This commit is contained in:
DDDHuang 2021-11-23 10:56:43 +08:00 committed by GitHub
parent 6fb464fc05
commit 21bd9bba55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 270 additions and 173 deletions

View File

@ -86,8 +86,8 @@ listeners.tcp.default {
## Set to "" to disable the feature. ## Set to "" to disable the feature.
## ##
## Variables in mountpoint string: ## Variables in mountpoint string:
## - %c: clientid ## - ${clientid}: clientid
## - %u: username ## - ${username}: username
## ##
## @doc listeners.tcp.<name>.mountpoint ## @doc listeners.tcp.<name>.mountpoint
## ValueType: String ## ValueType: String
@ -185,8 +185,8 @@ listeners.ssl.default {
## Set to "" to disable the feature. ## Set to "" to disable the feature.
## ##
## Variables in mountpoint string: ## Variables in mountpoint string:
## - %c: clientid ## - ${clientid}: clientid
## - %u: username ## - ${username}: username
## ##
## @doc listeners.ssl.<name>.mountpoint ## @doc listeners.ssl.<name>.mountpoint
## ValueType: String ## ValueType: String
@ -278,8 +278,8 @@ listeners.quic.default {
## Set to "" to disable the feature. ## Set to "" to disable the feature.
## ##
## Variables in mountpoint string: ## Variables in mountpoint string:
## - %c: clientid ## - ${clientid}: clientid
## - %u: username ## - ${username}: username
## ##
## @doc listeners.quic.<name>.mountpoint ## @doc listeners.quic.<name>.mountpoint
## ValueType: String ## ValueType: String
@ -372,8 +372,8 @@ listeners.ws.default {
## Set to "" to disable the feature. ## Set to "" to disable the feature.
## ##
## Variables in mountpoint string: ## Variables in mountpoint string:
## - %c: clientid ## - ${clientid}: clientid
## - %u: username ## - ${username}: username
## ##
## @doc listeners.ws.<name>.mountpoint ## @doc listeners.ws.<name>.mountpoint
## ValueType: String ## ValueType: String
@ -475,8 +475,8 @@ listeners.wss.default {
## Set to "" to disable the feature. ## Set to "" to disable the feature.
## ##
## Variables in mountpoint string: ## Variables in mountpoint string:
## - %c: clientid ## - ${clientid}: clientid
## - %u: username ## - ${username}: username
## ##
## @doc listeners.wss.<name>.mountpoint ## @doc listeners.wss.<name>.mountpoint
## ValueType: String ## ValueType: String

View File

@ -17,60 +17,96 @@
-ifndef(EMQ_X_PLACEHOLDER_HRL). -ifndef(EMQ_X_PLACEHOLDER_HRL).
-define(EMQ_X_PLACEHOLDER_HRL, true). -define(EMQ_X_PLACEHOLDER_HRL, true).
-define(PH(Type), <<"${", Type/binary, "}">>). -define(PH(Type), <<"${", Type/binary, "}">> ).
%% action: publish/subscribe/all %% action: publish/subscribe/all
-define(PH_ACTION, ?PH(<<"action">>)). -define(PH_ACTION, <<"${action}">> ).
%% cert %% cert
-define(PH_CRET_SUBJECT, ?PH(<<"cert_subject">>)). -define(PH_CERT_SUBJECT, <<"${cert_subject}">> ).
-define(PH_CRET_CN_NAME, ?PH(<<"cert_common_name">>)). -define(PH_CERT_CN_NAME, <<"${cert_common_name}">> ).
%% MQTT %% MQTT
-define(PH_PASSWORD, ?PH(<<"password">>)). -define(PH_PASSWORD, <<"${password}">> ).
-define(PH_CLIENTID, ?PH(<<"clientid">>)). -define(PH_CLIENTID, <<"${clientid}">> ).
-define(PH_FROM_CLIENTID, ?PH(<<"from_clienid">>)). -define(PH_FROM_CLIENTID, <<"${from_clientid}">> ).
-define(PH_USERNAME, ?PH(<<"username">>)). -define(PH_USERNAME, <<"${username}">> ).
-define(PH_FROM_USERNAME, ?PH(<<"from_username">>)). -define(PH_FROM_USERNAME, <<"${from_username}">> ).
-define(PH_TOPIC, ?PH(<<"topic">>)). -define(PH_TOPIC, <<"${topic}">> ).
%% MQTT payload %% MQTT payload
-define(PH_PAYLOAD, ?PH(<<"payload">>)). -define(PH_PAYLOAD, <<"${payload}">> ).
%% client IPAddress %% client IPAddress
-define(PH_PEERHOST, ?PH(<<"peerhost">>)). -define(PH_PEERHOST, <<"${peerhost}">> ).
%% ip & port
-define(PH_HOST, <<"${host}">> ).
-define(PH_PORT, <<"${port}">> ).
%% Enumeration of message QoS 0,1,2 %% Enumeration of message QoS 0,1,2
-define(PH_QOS, ?PH(<<"qos">>)). -define(PH_QOS, <<"${qos}">> ).
-define(PH_FLAGS, ?PH(<<"flags">>)). -define(PH_FLAGS, <<"${flags}">> ).
%% Additional data related to process within the MQTT message %% Additional data related to process within the MQTT message
-define(PH_HEADERS, ?PH(<<"hearders">>)). -define(PH_HEADERS, <<"${headers}">> ).
%% protocol name %% protocol name
-define(PH_PROTONAME, ?PH(<<"proto_name">>)). -define(PH_PROTONAME, <<"${proto_name}">> ).
%% protocol version %% protocol version
-define(PH_PROTOVER, ?PH(<<"proto_ver">>)). -define(PH_PROTOVER, <<"${proto_ver}">> ).
%% MQTT keepalive interval %% MQTT keepalive interval
-define(PH_KEEPALIVE, ?PH(<<"keepalive">>)). -define(PH_KEEPALIVE, <<"${keepalive}">> ).
%% MQTT clean_start %% MQTT clean_start
-define(PH_CLEAR_START, ?PH(<<"clean_start">>)). -define(PH_CLEAR_START, <<"${clean_start}">> ).
%% MQTT Session Expiration time %% MQTT Session Expiration time
-define(PH_EXPIRY_INTERVAL, ?PH(<<"expiry_interval">>)). -define(PH_EXPIRY_INTERVAL, <<"${expiry_interval}">> ).
%% Time when PUBLISH message reaches Broker (ms) %% Time when PUBLISH message reaches Broker (ms)
-define(PH_PUBLISH_RECEIVED_AT, ?PH(<<"publish_received_at">>)). -define(PH_PUBLISH_RECEIVED_AT, <<"${publish_received_at}">>).
%% Mountpoint for bridging messages %% Mountpoint for bridging messages
-define(PH_MOUNTPOINT, ?PH(<<"mountpoint">>)). -define(PH_MOUNTPOINT, <<"${mountpoint}">> ).
%% IPAddress and Port of terminal %% IPAddress and Port of terminal
-define(PH_PEERNAME, ?PH(<<"peername">>)). -define(PH_PEERNAME, <<"${peername}">> ).
%% IPAddress and Port listened by emqx %% IPAddress and Port listened by emqx
-define(PH_SOCKNAME, ?PH(<<"sockname">>)). -define(PH_SOCKNAME, <<"${sockname}">> ).
%% whether it is MQTT bridge connection %% whether it is MQTT bridge connection
-define(PH_IS_BRIDGE, ?PH(<<"is_bridge">>)). -define(PH_IS_BRIDGE, <<"${is_bridge}">> ).
%% Terminal connection completion time (s) %% Terminal connection completion time (s)
-define(PH_CONNECTED_AT, ?PH(<<"connected_at">>)). -define(PH_CONNECTED_AT, <<"${connected_at}">> ).
%% Event trigger time(millisecond) %% Event trigger time(millisecond)
-define(PH_TIMESTAMP, ?PH(<<"timestamp">>)). -define(PH_TIMESTAMP, <<"${timestamp}">> ).
%% Terminal disconnection completion time (s) %% Terminal disconnection completion time (s)
-define(PH_DISCONNECTED_AT, ?PH(<<"disconnected_at">>)). -define(PH_DISCONNECTED_AT, <<"${disconnected_at}">> ).
-define(PH_NODE, ?PH(<<"node">>)). -define(PH_NODE, <<"${node}">> ).
-define(PH_REASON, ?PH(<<"reason">>)). -define(PH_REASON, <<"${reason}">> ).
%% sync change these place holder with binary def.
-define(PH_S_ACTION, "${action}" ).
-define(PH_S_CERT_SUBJECT, "${cert_subject}" ).
-define(PH_S_CERT_CN_NAME, "${cert_common_name}" ).
-define(PH_S_PASSWORD, "${password}" ).
-define(PH_S_CLIENTID, "${clientid}" ).
-define(PH_S_FROM_CLIENTID, "${from_clientid}" ).
-define(PH_S_USERNAME, "${username}" ).
-define(PH_S_FROM_USERNAME, "${from_username}" ).
-define(PH_S_TOPIC, "${topic}" ).
-define(PH_S_PAYLOAD, "${payload}" ).
-define(PH_S_PEERHOST, "${peerhost}" ).
-define(PH_S_HOST, "${host}" ).
-define(PH_S_PORT, "${port}" ).
-define(PH_S_QOS, "${qos}" ).
-define(PH_S_FLAGS, "${flags}" ).
-define(PH_S_HEADERS, "${headers}" ).
-define(PH_S_PROTONAME, "${proto_name}" ).
-define(PH_S_PROTOVER, "${proto_ver}" ).
-define(PH_S_KEEPALIVE, "${keepalive}" ).
-define(PH_S_CLEAR_START, "${clean_start}" ).
-define(PH_S_EXPIRY_INTERVAL, "${expiry_interval}" ).
-define(PH_S_PUBLISH_RECEIVED_AT, "${publish_received_at}" ).
-define(PH_S_MOUNTPOINT, "${mountpoint}" ).
-define(PH_S_PEERNAME, "${peername}" ).
-define(PH_S_SOCKNAME, "${sockname}" ).
-define(PH_S_IS_BRIDGE, "${is_bridge}" ).
-define(PH_S_CONNECTED_AT, "${connected_at}" ).
-define(PH_S_TIMESTAMP, "${timestamp}" ).
-define(PH_S_DISCONNECTED_AT, "${disconnected_at}" ).
-define(PH_S_NODE, "${node}" ).
-define(PH_S_REASON, "${reason}" ).
-endif. -endif.

View File

@ -17,6 +17,7 @@
-module(emqx_mountpoint). -module(emqx_mountpoint).
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_placeholder.hrl").
-include("types.hrl"). -include("types.hrl").
-export([ mount/2 -export([ mount/2
@ -68,12 +69,12 @@ replvar(undefined, _Vars) ->
undefined; undefined;
replvar(MountPoint, #{clientid := ClientId, username := Username}) -> replvar(MountPoint, #{clientid := ClientId, username := Username}) ->
lists:foldl(fun feed_var/2, MountPoint, lists:foldl(fun feed_var/2, MountPoint,
[{<<"%c">>, ClientId}, {<<"%u">>, Username}]). [{?PH_CLIENTID, ClientId}, {?PH_USERNAME, Username}]).
feed_var({<<"%c">>, ClientId}, MountPoint) -> feed_var({?PH_CLIENTID, ClientId}, MountPoint) ->
emqx_topic:feed_var(<<"%c">>, ClientId, MountPoint); emqx_topic:feed_var(?PH_CLIENTID, ClientId, MountPoint);
feed_var({<<"%u">>, undefined}, MountPoint) -> feed_var({?PH_USERNAME, undefined}, MountPoint) ->
MountPoint; MountPoint;
feed_var({<<"%u">>, Username}, MountPoint) -> feed_var({?PH_USERNAME, Username}, MountPoint) ->
emqx_topic:feed_var(<<"%u">>, Username, MountPoint). emqx_topic:feed_var(?PH_USERNAME, Username, MountPoint).

View File

@ -55,12 +55,12 @@ t_unmount(_) ->
t_replvar(_) -> t_replvar(_) ->
?assertEqual(undefined, replvar(undefined, #{})), ?assertEqual(undefined, replvar(undefined, #{})),
?assertEqual(<<"mount/user/clientid/">>, ?assertEqual(<<"mount/user/clientid/">>,
replvar(<<"mount/%u/%c/">>, replvar(<<"mount/${username}/${clientid}/">>,
#{clientid => <<"clientid">>, #{clientid => <<"clientid">>,
username => <<"user">> username => <<"user">>
})), })),
?assertEqual(<<"mount/%u/clientid/">>, ?assertEqual(<<"mount/${username}/clientid/">>,
replvar(<<"mount/%u/%c/">>, replvar(<<"mount/${username}/${clientid}/">>,
#{clientid => <<"clientid">>, #{clientid => <<"clientid">>,
username => undefined username => undefined
})). })).

View File

@ -20,6 +20,7 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-import(hoconsc, [mk/2, ref/1]). -import(hoconsc, [mk/2, ref/1]).
-import(emqx_dashboard_swagger, [error_codes/2]). -import(emqx_dashboard_swagger, [error_codes/2]).
@ -969,8 +970,8 @@ authenticator_examples() ->
<<"content-type">> => <<"application/json">> <<"content-type">> => <<"application/json">>
}, },
body => #{ body => #{
<<"username">> => <<"${mqtt-username}">>, <<"username">> => ?PH_USERNAME,
<<"password">> => <<"${mqtt-password}">> <<"password">> => ?PH_PASSWORD
}, },
pool_size => 8, pool_size => 8,
connect_timeout => 5000, connect_timeout => 5000,
@ -988,7 +989,7 @@ authenticator_examples() ->
secret => <<"mysecret">>, secret => <<"mysecret">>,
secret_base64_encoded => false, secret_base64_encoded => false,
verify_claims => #{ verify_claims => #{
<<"username">> => <<"${mqtt-username}">> <<"username">> => ?PH_USERNAME
} }
} }
}, },
@ -1001,7 +1002,7 @@ authenticator_examples() ->
database => example, database => example,
collection => users, collection => users,
selector => #{ selector => #{
username => <<"${mqtt-username}">> username => ?PH_USERNAME
}, },
password_hash_field => <<"password_hash">>, password_hash_field => <<"password_hash">>,
salt_field => <<"salt">>, salt_field => <<"salt">>,
@ -1017,7 +1018,7 @@ authenticator_examples() ->
backend => <<"redis">>, backend => <<"redis">>,
server => <<"127.0.0.1:6379">>, server => <<"127.0.0.1:6379">>,
database => 0, database => 0,
query => <<"HMGET ${mqtt-username} password_hash salt">>, query => <<"HMGET ${username} password_hash salt">>,
password_hash_algorithm => <<"sha256">>, password_hash_algorithm => <<"sha256">>,
salt_position => <<"prefix">> salt_position => <<"prefix">>
} }

View File

@ -16,6 +16,8 @@
-module(emqx_authn_utils). -module(emqx_authn_utils).
-include_lib("emqx/include/emqx_placeholder.hrl").
-export([ replace_placeholders/2 -export([ replace_placeholders/2
, replace_placeholder/2 , replace_placeholder/2
, check_password/3 , check_password/3
@ -47,17 +49,17 @@ replace_placeholders([Placeholder | More], Credential, Acc) ->
replace_placeholders(More, Credential, [convert_to_sql_param(V) | Acc]) replace_placeholders(More, Credential, [convert_to_sql_param(V) | Acc])
end. end.
replace_placeholder(<<"${mqtt-username}">>, Credential) -> replace_placeholder(?PH_USERNAME, Credential) ->
maps:get(username, Credential, undefined); maps:get(username, Credential, undefined);
replace_placeholder(<<"${mqtt-clientid}">>, Credential) -> replace_placeholder(?PH_CLIENTID, Credential) ->
maps:get(clientid, Credential, undefined); maps:get(clientid, Credential, undefined);
replace_placeholder(<<"${mqtt-password}">>, Credential) -> replace_placeholder(?PH_PASSWORD, Credential) ->
maps:get(password, Credential, undefined); maps:get(password, Credential, undefined);
replace_placeholder(<<"${ip-address}">>, Credential) -> replace_placeholder(?PH_PEERHOST, Credential) ->
maps:get(peerhost, Credential, undefined); maps:get(peerhost, Credential, undefined);
replace_placeholder(<<"${cert-subject}">>, Credential) -> replace_placeholder(?PH_CERT_SUBJECT, Credential) ->
maps:get(dn, Credential, undefined); maps:get(dn, Credential, undefined);
replace_placeholder(<<"${cert-common-name}">>, Credential) -> replace_placeholder(?PH_CERT_CN_NAME, Credential) ->
maps:get(cn, Credential, undefined); maps:get(cn, Credential, undefined);
replace_placeholder(Constant, _) -> replace_placeholder(Constant, _) ->
Constant. Constant.

View File

@ -82,10 +82,10 @@ handle_info({refresh_jwks, _TRef, refresh}, #{request_id := RequestID} = State)
_ -> _ ->
ok = httpc:cancel_request(RequestID), ok = httpc:cancel_request(RequestID),
receive receive
{http, _} -> ok {http, _} -> ok
after 0 -> after 0 ->
ok ok
end end
end, end,
{noreply, refresh_jwks(State)}; {noreply, refresh_jwks(State)};

View File

@ -345,7 +345,7 @@ handle_placeholder(Placeholder0) ->
Placeholder0 Placeholder0
end. end.
validate_placeholder(<<"mqtt-clientid">>) -> validate_placeholder(<<"clientid">>) ->
clientid; clientid;
validate_placeholder(<<"mqtt-username">>) -> validate_placeholder(<<"username">>) ->
username. username.

View File

@ -4,6 +4,7 @@
"password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242", "password_hash":"c5e46903df45e5dc096dc74657610dbee8deaacae656df88a1788f1847390242",
"salt": "e378187547bf2d6f0545a3f441aa4d8a", "salt": "e378187547bf2d6f0545a3f441aa4d8a",
"is_superuser": true "is_superuser": true
, ,
{ {
"user_id":"myuser2", "user_id":"myuser2",

View File

@ -58,6 +58,10 @@ init_per_suite(Config) ->
ok = emqx_common_test_helpers:start_apps( ok = emqx_common_test_helpers:start_apps(
[emqx_authn, emqx_dashboard], [emqx_authn, emqx_dashboard],
fun set_special_configs/1), fun set_special_configs/1),
?AUTHN:delete_chain(?GLOBAL),
{ok, Chains} = ?AUTHN:list_chains(),
?assertEqual(length(Chains), 0),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->

View File

@ -21,6 +21,7 @@
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -62,7 +63,9 @@ t_authn(_) ->
<<"backend">> => <<"mysql">>, <<"backend">> => <<"mysql">>,
<<"server">> => <<"127.0.0.1:3306">>, <<"server">> => <<"127.0.0.1:3306">>,
<<"database">> => <<"mqtt">>, <<"database">> => <<"mqtt">>,
<<"query">> => <<"SELECT password_hash, salt FROM users where username = ${mqtt-username} LIMIT 1">> <<"query">> =>
<<"SELECT password_hash, salt FROM users where username = ",
?PH_USERNAME/binary, " LIMIT 1">>
}, },
{ok, _} = update_config([authentication], {create_authenticator, ?GLOBAL, Config}), {ok, _} = update_config([authentication], {create_authenticator, ?GLOBAL, Config}),
@ -77,14 +80,13 @@ t_authn(_) ->
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt, protocol => mqtt,
username => <<"good">>, username => <<"good">>,
password => Password}, password => Password},
?assertEqual({ok, #{is_superuser => false}}, emqx_access_control:authenticate(ClientInfo)), ?assertEqual({ok, #{is_superuser => false}}, emqx_access_control:authenticate(ClientInfo)),
ClientInfo2 = ClientInfo#{username => <<"bad">>}, ClientInfo2 = ClientInfo#{username => <<"bad">>},
?assertEqual({error, not_authorized}, emqx_access_control:authenticate(ClientInfo2)), ?assertEqual({error, not_authorized}, emqx_access_control:authenticate(ClientInfo2)),
emqx_authn_test_lib:delete_config(<<"password-based:mysql">>),
?AUTHN:delete_chain(?GLOBAL). ?AUTHN:delete_chain(?GLOBAL).
update_config(Path, ConfigRequest) -> update_config(Path, ConfigRequest) ->
emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}). emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}).

View File

@ -22,6 +22,7 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("epgsql/include/epgsql.hrl"). -include_lib("epgsql/include/epgsql.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -55,11 +56,12 @@ end_per_testcase(_, _Config) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
t_parse_query(_) -> t_parse_query(_) ->
Query1 = <<"${mqtt-username}">>, Query1 = ?PH_USERNAME,
?assertEqual({<<"$1">>, [<<"${mqtt-username}">>]}, emqx_authn_pgsql:parse_query(Query1)), ?assertEqual({<<"$1">>, [?PH_USERNAME]}, emqx_authn_pgsql:parse_query(Query1)),
Query2 = <<"${mqtt-username}, ${mqtt-clientid}">>, Query2 = <<?PH_USERNAME/binary, ", ", ?PH_CLIENTID/binary>>,
?assertEqual({<<"$1, $2">>, [<<"${mqtt-username}">>, <<"${mqtt-clientid}">>]}, emqx_authn_pgsql:parse_query(Query2)), ?assertEqual({<<"$1, $2">>, [?PH_USERNAME, ?PH_CLIENTID]},
emqx_authn_pgsql:parse_query(Query2)),
Query3 = <<"nomatch">>, Query3 = <<"nomatch">>,
?assertEqual({<<"nomatch">>, []}, emqx_authn_pgsql:parse_query(Query3)). ?assertEqual({<<"nomatch">>, []}, emqx_authn_pgsql:parse_query(Query3)).
@ -73,13 +75,16 @@ t_authn(_) ->
<<"backend">> => <<"postgresql">>, <<"backend">> => <<"postgresql">>,
<<"server">> => <<"127.0.0.1:5432">>, <<"server">> => <<"127.0.0.1:5432">>,
<<"database">> => <<"mqtt">>, <<"database">> => <<"mqtt">>,
<<"query">> => <<"SELECT password_hash, salt FROM users where username = ${mqtt-username} LIMIT 1">> <<"query">> =>
<<"SELECT password_hash, salt FROM users where username = ",
?PH_USERNAME/binary, " LIMIT 1">>
}, },
{ok, _} = update_config([authentication], {create_authenticator, ?GLOBAL, Config}), {ok, _} = update_config([authentication], {create_authenticator, ?GLOBAL, Config}),
meck:expect(emqx_resource, query, meck:expect(emqx_resource, query,
fun(_, {sql, _, [<<"good">>]}) -> fun(_, {sql, _, [<<"good">>]}) ->
{ok, [#column{name = <<"password_hash">>}, #column{name = <<"salt">>}], [{PasswordHash, Salt}]}; {ok, [#column{name = <<"password_hash">>}, #column{name = <<"salt">>}],
[{PasswordHash, Salt}]};
(_, {sql, _, _}) -> (_, {sql, _, _}) ->
{error, this_is_a_fictitious_reason} {error, this_is_a_fictitious_reason}
end), end),
@ -94,6 +99,7 @@ t_authn(_) ->
ClientInfo2 = ClientInfo#{username => <<"bad">>}, ClientInfo2 = ClientInfo#{username => <<"bad">>},
?assertEqual({error, not_authorized}, emqx_access_control:authenticate(ClientInfo2)), ?assertEqual({error, not_authorized}, emqx_access_control:authenticate(ClientInfo2)),
emqx_authn_test_lib:delete_config(<<"password-based:postgresql">>),
?AUTHN:delete_chain(?GLOBAL). ?AUTHN:delete_chain(?GLOBAL).
update_config(Path, ConfigRequest) -> update_config(Path, ConfigRequest) ->

View File

@ -99,11 +99,11 @@ t_create_invalid(_Config) ->
AuthConfig#{password => <<"wrongpass">>}, AuthConfig#{password => <<"wrongpass">>},
AuthConfig#{database => <<"5678">>}, AuthConfig#{database => <<"5678">>},
AuthConfig#{ AuthConfig#{
query => <<"MGET password_hash:${mqtt-username} salt:${mqtt-username}">>}, query => <<"MGET password_hash:${username} salt:${username}">>},
AuthConfig#{ AuthConfig#{
query => <<"HMGET mqtt_user:${mqtt-username} password_hash invalid_field">>}, query => <<"HMGET mqtt_user:${username} password_hash invalid_field">>},
AuthConfig#{ AuthConfig#{
query => <<"HMGET mqtt_user:${mqtt-username} salt is_superuser">>} query => <<"HMGET mqtt_user:${username} salt is_superuser">>}
], ],
lists:foreach( lists:foreach(
@ -178,7 +178,7 @@ t_update(_Config) ->
CorrectConfig = raw_redis_auth_config(), CorrectConfig = raw_redis_auth_config(),
IncorrectConfig = IncorrectConfig =
CorrectConfig#{ CorrectConfig#{
query => <<"HMGET invalid_key:${mqtt-username} password_hash salt is_superuser">>}, query => <<"HMGET invalid_key:${username} password_hash salt is_superuser">>},
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
@ -215,7 +215,7 @@ raw_redis_auth_config() ->
enable => <<"true">>, enable => <<"true">>,
backend => <<"redis">>, backend => <<"redis">>,
query => <<"HMGET mqtt_user:${mqtt-username} password_hash salt is_superuser">>, query => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
database => <<"1">>, database => <<"1">>,
password => <<"public">>, password => <<"public">>,
server => redis_server() server => redis_server()
@ -263,7 +263,7 @@ user_seeds() ->
}, },
key => "mqtt_user:sha256", key => "mqtt_user:sha256",
config_params => #{ config_params => #{
query => <<"HMGET mqtt_user:${mqtt-clientid} password_hash salt is_superuser">>, query => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>,
password_hash_algorithm => <<"sha256">>, password_hash_algorithm => <<"sha256">>,
salt_position => <<"prefix">> salt_position => <<"prefix">>
}, },
@ -299,7 +299,7 @@ user_seeds() ->
key => "mqtt_user:bcrypt0", key => "mqtt_user:bcrypt0",
config_params => #{ config_params => #{
% clientid variable & username credentials % clientid variable & username credentials
query => <<"HMGET mqtt_client:${mqtt-clientid} password_hash salt is_superuser">>, query => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>,
password_hash_algorithm => <<"bcrypt">>, password_hash_algorithm => <<"bcrypt">>,
salt_position => <<"suffix">> salt_position => <<"suffix">>
}, },
@ -318,7 +318,7 @@ user_seeds() ->
key => "mqtt_user:bcrypt1", key => "mqtt_user:bcrypt1",
config_params => #{ config_params => #{
% Bad key in query % Bad key in query
query => <<"HMGET badkey:${mqtt-username} password_hash salt is_superuser">>, query => <<"HMGET badkey:${username} password_hash salt is_superuser">>,
password_hash_algorithm => <<"bcrypt">>, password_hash_algorithm => <<"bcrypt">>,
salt_position => <<"suffix">> salt_position => <<"suffix">>
}, },
@ -337,7 +337,7 @@ user_seeds() ->
}, },
key => "mqtt_user:bcrypt2", key => "mqtt_user:bcrypt2",
config_params => #{ config_params => #{
query => <<"HMGET mqtt_user:${mqtt-username} password_hash salt is_superuser">>, query => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
password_hash_algorithm => <<"bcrypt">>, password_hash_algorithm => <<"bcrypt">>,
salt_position => <<"suffix">> salt_position => <<"suffix">>
}, },

View File

@ -16,6 +16,8 @@
-module(emqx_authn_test_lib). -module(emqx_authn_test_lib).
-include("emqx_authn.hrl").
-compile(nowarn_export_all). -compile(nowarn_export_all).
-compile(export_all). -compile(export_all).
@ -45,3 +47,10 @@ delete_authenticators(Path, Chain) ->
end, end,
Authenticators) Authenticators)
end. end.
delete_config(ID) ->
{ok, _} =
emqx:update_config(
[authentication],
{delete_authenticator, ?GLOBAL, ID},
#{rawconf_with_defaults => false}).

View File

@ -19,6 +19,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
%% AuthZ Callbacks %% AuthZ Callbacks
-export([ authorize/4 -export([ authorize/4
@ -68,7 +69,9 @@ query_string([], Acc) ->
<<$&, Str/binary>> = iolist_to_binary(lists:reverse(Acc)), <<$&, Str/binary>> = iolist_to_binary(lists:reverse(Acc)),
Str; Str;
query_string([{K, V} | More], Acc) -> query_string([{K, V} | More], Acc) ->
query_string(More, [["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] | Acc]). query_string( More
, [ ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)]
| Acc]).
serialize_body(<<"application/json">>, Body) -> serialize_body(<<"application/json">>, Body) ->
jsx:encode(Body); jsx:encode(Body);
@ -84,13 +87,20 @@ replvar(Str0, PubSub, Topic,
}) when is_list(Str0); }) when is_list(Str0);
is_binary(Str0) -> is_binary(Str0) ->
NTopic = emqx_http_lib:uri_encode(Topic), NTopic = emqx_http_lib:uri_encode(Topic),
Str1 = re:replace(Str0, "%c", Clientid, [global, {return, binary}]), Str1 = re:replace( Str0, ?PH_S_CLIENTID
Str2 = re:replace(Str1, "%u", bin(Username), [global, {return, binary}]), , Clientid, [global, {return, binary}]),
Str3 = re:replace(Str2, "%a", inet_parse:ntoa(IpAddress), [global, {return, binary}]), Str2 = re:replace( Str1, ?PH_S_USERNAME
Str4 = re:replace(Str3, "%r", bin(Protocol), [global, {return, binary}]), , bin(Username), [global, {return, binary}]),
Str5 = re:replace(Str4, "%m", Mountpoint, [global, {return, binary}]), Str3 = re:replace( Str2, ?PH_S_HOST
Str6 = re:replace(Str5, "%t", NTopic, [global, {return, binary}]), , inet_parse:ntoa(IpAddress), [global, {return, binary}]),
Str7 = re:replace(Str6, "%A", bin(PubSub), [global, {return, binary}]), Str4 = re:replace( Str3, ?PH_S_PROTONAME
, bin(Protocol), [global, {return, binary}]),
Str5 = re:replace( Str4, ?PH_S_MOUNTPOINT
, Mountpoint, [global, {return, binary}]),
Str6 = re:replace( Str5, ?PH_S_TOPIC
, NTopic, [global, {return, binary}]),
Str7 = re:replace( Str6, ?PH_S_ACTION
, bin(PubSub), [global, {return, binary}]),
Str7. Str7.
bin(A) when is_atom(A) -> atom_to_binary(A, utf8); bin(A) when is_atom(A) -> atom_to_binary(A, utf8);

View File

@ -19,6 +19,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
%% AuthZ Callbacks %% AuthZ Callbacks
-export([ authorize/4 -export([ authorize/4
@ -40,12 +41,16 @@ authorize(Client, PubSub, Topic,
}) -> }) ->
case emqx_resource:query(ResourceID, {find, Collection, replvar(Selector, Client), #{}}) of case emqx_resource:query(ResourceID, {find, Collection, replvar(Selector, Client), #{}}) of
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "query_mongo_error", reason => Reason, resource_id => ResourceID}), ?SLOG(error, #{msg => "query_mongo_error",
reason => Reason,
resource_id => ResourceID}),
nomatch; nomatch;
[] -> nomatch; [] -> nomatch;
Rows -> Rows ->
Rules = [ emqx_authz_rule:compile({Permission, all, Action, Topics}) Rules = [ emqx_authz_rule:compile({Permission, all, Action, Topics})
|| #{<<"topics">> := Topics, <<"permission">> := Permission, <<"action">> := Action} <- Rows], || #{<<"topics">> := Topics,
<<"permission">> := Permission,
<<"action">> := Action} <- Rows],
do_authorize(Client, PubSub, Topic, Rules) do_authorize(Client, PubSub, Topic, Rules)
end. end.
@ -62,19 +67,23 @@ replvar(Selector, #{clientid := Clientid,
peerhost := IpAddress peerhost := IpAddress
}) -> }) ->
Fun = fun Fun = fun
_Fun(K, V, AccIn) when is_map(V) -> maps:put(K, maps:fold(_Fun, AccIn, V), AccIn); InFun(K, V, AccIn) when is_map(V) ->
_Fun(K, V, AccIn) when is_list(V) -> maps:put(K, maps:fold(InFun, AccIn, V), AccIn);
InFun(K, V, AccIn) when is_list(V) ->
maps:put(K, [ begin maps:put(K, [ begin
[{K1, V1}] = maps:to_list(M), [{K1, V1}] = maps:to_list(M),
_Fun(K1, V1, AccIn) InFun(K1, V1, AccIn)
end || M <- V], end || M <- V],
AccIn); AccIn);
_Fun(K, V, AccIn) when is_binary(V) -> InFun(K, V, AccIn) when is_binary(V) ->
V1 = re:replace(V, "%c", bin(Clientid), [global, {return, binary}]), V1 = re:replace( V, ?PH_S_CLIENTID
V2 = re:replace(V1, "%u", bin(Username), [global, {return, binary}]), , bin(Clientid), [global, {return, binary}]),
V3 = re:replace(V2, "%a", inet_parse:ntoa(IpAddress), [global, {return, binary}]), V2 = re:replace( V1, ?PH_S_USERNAME
, bin(Username), [global, {return, binary}]),
V3 = re:replace( V2, ?PH_S_HOST
, inet_parse:ntoa(IpAddress), [global, {return, binary}]),
maps:put(K, V3, AccIn); maps:put(K, V3, AccIn);
_Fun(K, V, AccIn) -> maps:put(K, V, AccIn) InFun(K, V, AccIn) -> maps:put(K, V, AccIn)
end, end,
maps:fold(Fun, #{}, Selector). maps:fold(Fun, #{}, Selector).
@ -82,4 +91,3 @@ bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
bin(B) when is_binary(B) -> B; bin(B) when is_binary(B) -> B;
bin(L) when is_list(L) -> list_to_binary(L); bin(L) when is_list(L) -> list_to_binary(L);
bin(X) -> X. bin(X) -> X.

View File

@ -19,6 +19,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
%% AuthZ Callbacks %% AuthZ Callbacks
-export([ description/0 -export([ description/0
@ -55,7 +56,9 @@ authorize(Client, PubSub, Topic,
{ok, Columns, Rows} -> {ok, Columns, Rows} ->
do_authorize(Client, PubSub, Topic, Columns, Rows); do_authorize(Client, PubSub, Topic, Columns, Rows);
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "query_mysql_error", reason => Reason, resource_id => ResourceID}), ?SLOG(error, #{ msg => "query_mysql_error"
, reason => Reason
, resource_id => ResourceID}),
nomatch nomatch
end. end.
@ -87,16 +90,16 @@ replvar(Params, ClientInfo) ->
replvar([], _ClientInfo, Acc) -> replvar([], _ClientInfo, Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
replvar(["'%u'" | Params], ClientInfo, Acc) -> replvar([?PH_S_USERNAME | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [safe_get(username, ClientInfo) | Acc]); replvar(Params, ClientInfo, [safe_get(username, ClientInfo) | Acc]);
replvar(["'%c'" | Params], ClientInfo = #{clientid := ClientId}, Acc) -> replvar([?PH_S_CLIENTID | Params], ClientInfo = #{clientid := ClientId}, Acc) ->
replvar(Params, ClientInfo, [ClientId | Acc]); replvar(Params, ClientInfo, [ClientId | Acc]);
replvar(["'%a'" | Params], ClientInfo = #{peerhost := IpAddr}, Acc) -> replvar([?PH_S_PEERHOST | Params], ClientInfo = #{peerhost := IpAddr}, Acc) ->
replvar(Params, ClientInfo, [inet_parse:ntoa(IpAddr) | Acc]); replvar(Params, ClientInfo, [inet_parse:ntoa(IpAddr) | Acc]);
replvar(["'%C'" | Params], ClientInfo, Acc) -> replvar([?PH_S_CERT_CN_NAME | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [safe_get(cn, ClientInfo)| Acc]); replvar(Params, ClientInfo, [safe_get(cn, ClientInfo) | Acc]);
replvar(["'%d'" | Params], ClientInfo, Acc) -> replvar([?PH_S_CERT_SUBJECT | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [safe_get(dn, ClientInfo)| Acc]); replvar(Params, ClientInfo, [safe_get(dn, ClientInfo) | Acc]);
replvar([Param | Params], ClientInfo, Acc) -> replvar([Param | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [Param | Acc]). replvar(Params, ClientInfo, [Param | Acc]).
@ -107,4 +110,3 @@ bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
bin(B) when is_binary(B) -> B; bin(B) when is_binary(B) -> B;
bin(L) when is_list(L) -> list_to_binary(L); bin(L) when is_list(L) -> list_to_binary(L);
bin(X) -> X. bin(X) -> X.

View File

@ -19,6 +19,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
%% AuthZ Callbacks %% AuthZ Callbacks
-export([ description/0 -export([ description/0
@ -59,7 +60,9 @@ authorize(Client, PubSub, Topic,
{ok, Columns, Rows} -> {ok, Columns, Rows} ->
do_authorize(Client, PubSub, Topic, Columns, Rows); do_authorize(Client, PubSub, Topic, Columns, Rows);
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "query_postgresql_error", reason => Reason, resource_id => ResourceID}), ?SLOG(error, #{ msg => "query_postgresql_error"
, reason => Reason
, resource_id => ResourceID}),
nomatch nomatch
end. end.
@ -92,16 +95,16 @@ replvar(Params, ClientInfo) ->
replvar([], _ClientInfo, Acc) -> replvar([], _ClientInfo, Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
replvar(["'%u'" | Params], ClientInfo, Acc) -> replvar([?PH_S_USERNAME | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [safe_get(username, ClientInfo) | Acc]); replvar(Params, ClientInfo, [safe_get(username, ClientInfo) | Acc]);
replvar(["'%c'" | Params], ClientInfo = #{clientid := ClientId}, Acc) -> replvar([?PH_S_CLIENTID | Params], ClientInfo = #{clientid := ClientId}, Acc) ->
replvar(Params, ClientInfo, [ClientId | Acc]); replvar(Params, ClientInfo, [ClientId | Acc]);
replvar(["'%a'" | Params], ClientInfo = #{peerhost := IpAddr}, Acc) -> replvar([?PH_S_PEERHOST | Params], ClientInfo = #{peerhost := IpAddr}, Acc) ->
replvar(Params, ClientInfo, [inet_parse:ntoa(IpAddr) | Acc]); replvar(Params, ClientInfo, [inet_parse:ntoa(IpAddr) | Acc]);
replvar(["'%C'" | Params], ClientInfo, Acc) -> replvar([?PH_S_CERT_CN_NAME | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [safe_get(cn, ClientInfo)| Acc]); replvar(Params, ClientInfo, [safe_get(cn, ClientInfo) | Acc]);
replvar(["'%d'" | Params], ClientInfo, Acc) -> replvar([?PH_S_CERT_SUBJECT | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [safe_get(dn, ClientInfo)| Acc]); replvar(Params, ClientInfo, [safe_get(dn, ClientInfo) | Acc]);
replvar([Param | Params], ClientInfo, Acc) -> replvar([Param | Params], ClientInfo, Acc) ->
replvar(Params, ClientInfo, [Param | Acc]). replvar(Params, ClientInfo, [Param | Acc]).
@ -112,4 +115,3 @@ bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
bin(B) when is_binary(B) -> B; bin(B) when is_binary(B) -> B;
bin(L) when is_list(L) -> list_to_binary(L); bin(L) when is_list(L) -> list_to_binary(L);
bin(X) -> X. bin(X) -> X.

View File

@ -19,6 +19,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
%% AuthZ Callbacks %% AuthZ Callbacks
-export([ authorize/4 -export([ authorize/4
@ -43,7 +44,9 @@ authorize(Client, PubSub, Topic,
{ok, Rows} -> {ok, Rows} ->
do_authorize(Client, PubSub, Topic, Rows); do_authorize(Client, PubSub, Topic, Rows);
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "query_redis_error", reason => Reason, resource_id => ResourceID}), ?SLOG(error, #{ msg => "query_redis_error"
, reason => Reason
, resource_id => ResourceID}),
nomatch nomatch
end. end.
@ -58,13 +61,13 @@ do_authorize(Client, PubSub, Topic, [TopicFilter, Action | Tail]) ->
end. end.
replvar(Cmd, Client = #{cn := CN}) -> replvar(Cmd, Client = #{cn := CN}) ->
replvar(repl(Cmd, "%C", CN), maps:remove(cn, Client)); replvar(repl(Cmd, ?PH_S_CERT_CN_NAME, CN), maps:remove(cn, Client));
replvar(Cmd, Client = #{dn := DN}) -> replvar(Cmd, Client = #{dn := DN}) ->
replvar(repl(Cmd, "%d", DN), maps:remove(dn, Client)); replvar(repl(Cmd, ?PH_S_CERT_SUBJECT, DN), maps:remove(dn, Client));
replvar(Cmd, Client = #{clientid := ClientId}) -> replvar(Cmd, Client = #{clientid := ClientId}) ->
replvar(repl(Cmd, "%c", ClientId), maps:remove(clientid, Client)); replvar(repl(Cmd, ?PH_S_CLIENTID, ClientId), maps:remove(clientid, Client));
replvar(Cmd, Client = #{username := Username}) -> replvar(Cmd, Client = #{username := Username}) ->
replvar(repl(Cmd, "%u", Username), maps:remove(username, Client)); replvar(repl(Cmd, ?PH_S_USERNAME, Username), maps:remove(username, Client));
replvar(Cmd, _) -> replvar(Cmd, _) ->
Cmd. Cmd.

View File

@ -18,6 +18,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-ifdef(TEST). -ifdef(TEST).
-compile(export_all). -compile(export_all).
@ -32,9 +33,12 @@
-export_type([rule/0]). -export_type([rule/0]).
compile({Permission, all}) when ?ALLOW_DENY(Permission) -> {Permission, all, all, [compile_topic(<<"#">>)]}; compile({Permission, all})
compile({Permission, Who, Action, TopicFilters}) when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(TopicFilters) -> when ?ALLOW_DENY(Permission) -> {Permission, all, all, [compile_topic(<<"#">>)]};
{atom(Permission), compile_who(Who), atom(Action), [compile_topic(Topic) || Topic <- TopicFilters]}. compile({Permission, Who, Action, TopicFilters})
when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(TopicFilters) ->
{ atom(Permission), compile_who(Who), atom(Action)
, [compile_topic(Topic) || Topic <- TopicFilters]}.
compile_who(all) -> all; compile_who(all) -> all;
compile_who({user, Username}) -> compile_who({username, Username}); compile_who({user, Username}) -> compile_who({username, Username});
@ -68,12 +72,12 @@ compile_topic(Topic) ->
end. end.
pattern(Words) -> pattern(Words) ->
lists:member(<<"%u">>, Words) orelse lists:member(<<"%c">>, Words). lists:member(?PH_USERNAME, Words) orelse lists:member(?PH_CLIENTID, Words).
atom(B) when is_binary(B) -> atom(B) when is_binary(B) ->
try binary_to_existing_atom(B, utf8) try binary_to_existing_atom(B, utf8)
catch catch
_ -> binary_to_atom(B) _E:_S -> binary_to_atom(B)
end; end;
atom(A) when is_atom(A) -> A. atom(A) when is_atom(A) -> A.
@ -143,11 +147,11 @@ match_who(_, _) -> false.
match_topics(_ClientInfo, _Topic, []) -> match_topics(_ClientInfo, _Topic, []) ->
false; false;
match_topics(ClientInfo, Topic, [{pattern, PatternFilter}|Filters]) -> match_topics(ClientInfo, Topic, [{pattern, PatternFilter} | Filters]) ->
TopicFilter = feed_var(ClientInfo, PatternFilter), TopicFilter = feed_var(ClientInfo, PatternFilter),
match_topic(emqx_topic:words(Topic), TopicFilter) match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(ClientInfo, Topic, Filters); orelse match_topics(ClientInfo, Topic, Filters);
match_topics(ClientInfo, Topic, [TopicFilter|Filters]) -> match_topics(ClientInfo, Topic, [TopicFilter | Filters]) ->
match_topic(emqx_topic:words(Topic), TopicFilter) match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(ClientInfo, Topic, Filters). orelse match_topics(ClientInfo, Topic, Filters).
@ -160,13 +164,13 @@ feed_var(ClientInfo, Pattern) ->
feed_var(ClientInfo, Pattern, []). feed_var(ClientInfo, Pattern, []).
feed_var(_ClientInfo, [], Acc) -> feed_var(_ClientInfo, [], Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) -> feed_var(ClientInfo = #{clientid := undefined}, [?PH_CLIENTID | Words], Acc) ->
feed_var(ClientInfo, Words, [<<"%c">>|Acc]); feed_var(ClientInfo, Words, [?PH_CLIENTID | Acc]);
feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) -> feed_var(ClientInfo = #{clientid := ClientId}, [?PH_CLIENTID | Words], Acc) ->
feed_var(ClientInfo, Words, [ClientId |Acc]); feed_var(ClientInfo, Words, [ClientId | Acc]);
feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) -> feed_var(ClientInfo = #{username := undefined}, [?PH_USERNAME | Words], Acc) ->
feed_var(ClientInfo, Words, [<<"%u">>|Acc]); feed_var(ClientInfo, Words, [?PH_USERNAME | Acc]);
feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) -> feed_var(ClientInfo = #{username := Username}, [?PH_USERNAME | Words], Acc) ->
feed_var(ClientInfo, Words, [Username|Acc]); feed_var(ClientInfo, Words, [Username | Acc]);
feed_var(ClientInfo, [W|Words], Acc) -> feed_var(ClientInfo, [W | Words], Acc) ->
feed_var(ClientInfo, Words, [W|Acc]). feed_var(ClientInfo, Words, [W | Acc]).

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -107,7 +108,7 @@ set_special_configs(_App) ->
<<"password">> => <<"ee">>, <<"password">> => <<"ee">>,
<<"auto_reconnect">> => true, <<"auto_reconnect">> => true,
<<"ssl">> => #{<<"enable">> => false}, <<"ssl">> => #{<<"enable">> => false},
<<"cmd">> => <<"HGETALL mqtt_authz:%u">> <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>>
}). }).
-define(SOURCE6, #{<<"type">> => <<"file">>, -define(SOURCE6, #{<<"type">> => <<"file">>,
<<"enable">> => true, <<"enable">> => true,

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-define(HOST, "http://127.0.0.1:18083/"). -define(HOST, "http://127.0.0.1:18083/").
-define(API_VERSION, "v5"). -define(API_VERSION, "v5").
@ -77,7 +78,7 @@
<<"password">> => <<"ee">>, <<"password">> => <<"ee">>,
<<"auto_reconnect">> => true, <<"auto_reconnect">> => true,
<<"ssl">> => #{<<"enable">> => false}, <<"ssl">> => #{<<"enable">> => false},
<<"cmd">> => <<"HGETALL mqtt_authz:%u">> <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>>
}). }).
-define(SOURCE6, #{<<"type">> => <<"file">>, -define(SOURCE6, #{<<"type">> => <<"file">>,
<<"enable">> => true, <<"enable">> => true,

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -55,12 +56,12 @@ set_special_configs(_App) ->
init_per_testcase(t_authz, Config) -> init_per_testcase(t_authz, Config) ->
mria:dirty_write(#emqx_acl{who = {?ACL_TABLE_USERNAME, <<"test_username">>}, mria:dirty_write(#emqx_acl{who = {?ACL_TABLE_USERNAME, <<"test_username">>},
rules = [{allow, publish, <<"test/%u">>}, rules = [{allow, publish, <<"test/", ?PH_S_USERNAME>>},
{allow, subscribe, <<"eq #">>} {allow, subscribe, <<"eq #">>}
] ]
}), }),
mria:dirty_write(#emqx_acl{who = {?ACL_TABLE_CLIENTID, <<"test_clientid">>}, mria:dirty_write(#emqx_acl{who = {?ACL_TABLE_CLIENTID, <<"test_clientid">>},
rules = [{allow, publish, <<"test/%c">>}, rules = [{allow, publish, <<"test/", ?PH_S_CLIENTID>>},
{deny, subscribe, <<"eq #">>} {deny, subscribe, <<"eq #">>}
] ]
}), }),

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -74,10 +75,10 @@ set_special_configs(_App) ->
-define(SOURCE2,[#{<<"topics">> => [<<"eq #">>], -define(SOURCE2,[#{<<"topics">> => [<<"eq #">>],
<<"permission">> => <<"allow">>, <<"permission">> => <<"allow">>,
<<"action">> => <<"all">>}]). <<"action">> => <<"all">>}]).
-define(SOURCE3,[#{<<"topics">> => [<<"test/%c">>], -define(SOURCE3,[#{<<"topics">> => [<<"test/", ?PH_CLIENTID/binary>>],
<<"permission">> => <<"allow">>, <<"permission">> => <<"allow">>,
<<"action">> => <<"subscribe">>}]). <<"action">> => <<"subscribe">>}]).
-define(SOURCE4,[#{<<"topics">> => [<<"test/%u">>], -define(SOURCE4,[#{<<"topics">> => [<<"test/", ?PH_USERNAME/binary>>],
<<"permission">> => <<"allow">>, <<"permission">> => <<"allow">>,
<<"action">> => <<"publish">>}]). <<"action">> => <<"publish">>}]).
@ -131,4 +132,3 @@ t_authz(_) ->
?assertEqual(deny, emqx_access_control:authorize( ?assertEqual(deny, emqx_access_control:authorize(
ClientInfo3, publish, <<"test">>)), % nomatch ClientInfo3, publish, <<"test">>)), % nomatch
ok. ok.

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-define(CONF_DEFAULT, <<"authorization: {sources: []}">>). -define(CONF_DEFAULT, <<"authorization: {sources: []}">>).
@ -76,8 +77,8 @@ set_special_configs(_App) ->
]). ]).
-define(SOURCE1, [[<<"all">>, <<"deny">>, <<"#">>]]). -define(SOURCE1, [[<<"all">>, <<"deny">>, <<"#">>]]).
-define(SOURCE2, [[<<"all">>, <<"allow">>, <<"eq #">>]]). -define(SOURCE2, [[<<"all">>, <<"allow">>, <<"eq #">>]]).
-define(SOURCE3, [[<<"subscribe">>, <<"allow">>, <<"test/%c">>]]). -define(SOURCE3, [[<<"subscribe">>, <<"allow">>, <<"test/", ?PH_CLIENTID/binary>>]]).
-define(SOURCE4, [[<<"publish">>, <<"allow">>, <<"test/%u">>]]). -define(SOURCE4, [[<<"publish">>, <<"allow">>, <<"test/", ?PH_USERNAME/binary>>]]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Testcases %% Testcases
@ -129,4 +130,3 @@ t_authz(_) ->
?assertEqual(deny, emqx_access_control:authorize( ?assertEqual(deny, emqx_access_control:authorize(
ClientInfo3, publish, <<"test">>)), % nomatch ClientInfo3, publish, <<"test">>)), % nomatch
ok. ok.

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -74,8 +75,8 @@ set_special_configs(_App) ->
]). ]).
-define(SOURCE1, [{<<"all">>, <<"deny">>, <<"#">>}]). -define(SOURCE1, [{<<"all">>, <<"deny">>, <<"#">>}]).
-define(SOURCE2, [{<<"all">>, <<"allow">>, <<"eq #">>}]). -define(SOURCE2, [{<<"all">>, <<"allow">>, <<"eq #">>}]).
-define(SOURCE3, [{<<"subscribe">>, <<"allow">>, <<"test/%c">>}]). -define(SOURCE3, [{<<"subscribe">>, <<"allow">>, <<"test/", ?PH_CLIENTID/binary>>}]).
-define(SOURCE4, [{<<"publish">>, <<"allow">>, <<"test/%u">>}]). -define(SOURCE4, [{<<"publish">>, <<"allow">>, <<"test/", ?PH_USERNAME/binary>>}]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Testcases %% Testcases
@ -127,4 +128,3 @@ t_authz(_) ->
?assertEqual(deny, emqx_access_control:authorize( ?assertEqual(deny, emqx_access_control:authorize(
ClientInfo3, publish, <<"test">>)), % nomatch ClientInfo3, publish, <<"test">>)), % nomatch
ok. ok.

View File

@ -21,6 +21,7 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-define(CONF_DEFAULT, <<"authorization: {sources: []}">>). -define(CONF_DEFAULT, <<"authorization: {sources: []}">>).
all() -> all() ->
@ -45,7 +46,7 @@ init_per_suite(Config) ->
<<"password">> => <<"ee">>, <<"password">> => <<"ee">>,
<<"auto_reconnect">> => true, <<"auto_reconnect">> => true,
<<"ssl">> => #{<<"enable">> => false}, <<"ssl">> => #{<<"enable">> => false},
<<"cmd">> => <<"HGETALL mqtt_authz:%u">> <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>>
}], }],
{ok, _} = emqx_authz:update(replace, Rules), {ok, _} = emqx_authz:update(replace, Rules),
Config. Config.
@ -68,8 +69,8 @@ set_special_configs(emqx_authz) ->
set_special_configs(_App) -> set_special_configs(_App) ->
ok. ok.
-define(SOURCE1, [<<"test/%u">>, <<"publish">>]). -define(SOURCE1, [<<"test/", ?PH_USERNAME/binary>>, <<"publish">>]).
-define(SOURCE2, [<<"test/%c">>, <<"publish">>]). -define(SOURCE2, [<<"test/", ?PH_CLIENTID/binary>>, <<"publish">>]).
-define(SOURCE3, [<<"#">>, <<"subscribe">>]). -define(SOURCE3, [<<"#">>, <<"subscribe">>]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -113,4 +114,3 @@ t_authz(_) ->
?assertEqual(deny, ?assertEqual(deny,
emqx_access_control:authorize(ClientInfo, publish, <<"#">>)), emqx_access_control:authorize(ClientInfo, publish, <<"#">>)),
ok. ok.

View File

@ -21,15 +21,16 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-define(SOURCE1, {deny, all}). -define(SOURCE1, {deny, all}).
-define(SOURCE2, {allow, {ipaddr, "127.0.0.1"}, all, [{eq, "#"}, {eq, "+"}]}). -define(SOURCE2, {allow, {ipaddr, "127.0.0.1"}, all, [{eq, "#"}, {eq, "+"}]}).
-define(SOURCE3, {allow, {ipaddrs, ["127.0.0.1", "192.168.1.0/24"]}, subscribe, ["%c"]}). -define(SOURCE3, {allow, {ipaddrs, ["127.0.0.1", "192.168.1.0/24"]}, subscribe, [?PH_S_CLIENTID]}).
-define(SOURCE4, {allow, {'and', [{client, "test"}, {user, "test"}]}, publish, ["topic/test"]}). -define(SOURCE4, {allow, {'and', [{client, "test"}, {user, "test"}]}, publish, ["topic/test"]}).
-define(SOURCE5, {allow, {'or', -define(SOURCE5, {allow, {'or',
[{username, {re, "^test"}}, [{username, {re, "^test"}},
{clientid, {re, "test?"}}]}, {clientid, {re, "test?"}}]},
publish, ["%u", "%c"]}). publish, [?PH_S_USERNAME, ?PH_S_CLIENTID]}).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -67,7 +68,7 @@ t_compile(_) ->
{ipaddrs,[{{127,0,0,1},{127,0,0,1},32}, {ipaddrs,[{{127,0,0,1},{127,0,0,1},32},
{{192,168,1,0},{192,168,1,255},24}]}, {{192,168,1,0},{192,168,1,255},24}]},
subscribe, subscribe,
[{pattern,[<<"%c">>]}] [{pattern,[?PH_CLIENTID]}]
}, emqx_authz_rule:compile(?SOURCE3)), }, emqx_authz_rule:compile(?SOURCE3)),
?assertMatch({allow, ?assertMatch({allow,
@ -79,7 +80,7 @@ t_compile(_) ->
?assertMatch({allow, ?assertMatch({allow,
{'or', [{username, {re_pattern, _, _, _, _}}, {'or', [{username, {re_pattern, _, _, _, _}},
{clientid, {re_pattern, _, _, _, _}}]}, {clientid, {re_pattern, _, _, _, _}}]},
publish, [{pattern, [<<"%u">>]}, {pattern, [<<"%c">>]}] publish, [{pattern, [?PH_USERNAME]}, {pattern, [?PH_CLIENTID]}]
}, emqx_authz_rule:compile(?SOURCE5)), }, emqx_authz_rule:compile(?SOURCE5)),
ok. ok.

View File

@ -15,6 +15,8 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_auto_subscribe_placeholder). -module(emqx_auto_subscribe_placeholder).
-include_lib("emqx/include/emqx_placeholder.hrl").
-export([generate/1]). -export([generate/1]).
-export([to_topic_table/3]). -export([to_topic_table/3]).
@ -40,13 +42,13 @@ to_topic_table(PHs, ClientInfo, ConnInfo) ->
generate(<<"">>, Result) -> generate(<<"">>, Result) ->
lists:reverse(Result); lists:reverse(Result);
generate(<<"${clientid}", Tail/binary>>, Result) -> generate(<<?PH_S_CLIENTID, Tail/binary>>, Result) ->
generate(Tail, [clientid | Result]); generate(Tail, [clientid | Result]);
generate(<<"${username}", Tail/binary>>, Result) -> generate(<<?PH_S_USERNAME, Tail/binary>>, Result) ->
generate(Tail, [username | Result]); generate(Tail, [username | Result]);
generate(<<"${host}", Tail/binary>>, Result) -> generate(<<?PH_S_HOST, Tail/binary>>, Result) ->
generate(Tail, [host | Result]); generate(Tail, [host | Result]);
generate(<<"${port}", Tail/binary>>, Result) -> generate(<<?PH_S_PORT, Tail/binary>>, Result) ->
generate(Tail, [port | Result]); generate(Tail, [port | Result]);
generate(<<Char:8, Tail/binary>>, []) -> generate(<<Char:8, Tail/binary>>, []) ->
generate(Tail, [<<Char:8>>]); generate(Tail, [<<Char:8>>]);
@ -62,7 +64,7 @@ to_topic([Binary | PTs], C, Co, Res) when is_binary(Binary) ->
to_topic([clientid | PTs], C = #{clientid := ClientID}, Co, Res) -> to_topic([clientid | PTs], C = #{clientid := ClientID}, Co, Res) ->
to_topic(PTs, C, Co, [ClientID | Res]); to_topic(PTs, C, Co, [ClientID | Res]);
to_topic([username | PTs], C = #{username := undefined}, Co, Res) -> to_topic([username | PTs], C = #{username := undefined}, Co, Res) ->
to_topic(PTs, C, Co, [<<"${username}">> | Res]); to_topic(PTs, C, Co, [?PH_USERNAME | Res]);
to_topic([username | PTs], C = #{username := Username}, Co, Res) -> to_topic([username | PTs], C = #{username := Username}, Co, Res) ->
to_topic(PTs, C, Co, [Username | Res]); to_topic(PTs, C, Co, [Username | Res]);
to_topic([host | PTs], C, Co = #{peername := {Host, _}}, Res) -> to_topic([host | PTs], C, Co = #{peername := {Host, _}}, Res) ->

View File

@ -35,7 +35,7 @@ gateway.lwm2m {
lifetime_max = 86400s lifetime_max = 86400s
qmode_time_window = 22 qmode_time_window = 22
auto_observe = false auto_observe = false
mountpoint = \"lwm2m/%u\" mountpoint = \"lwm2m/${username}\"
update_msg_publish_condition = contains_object_list update_msg_publish_condition = contains_object_list
translators { translators {
command = {topic = \"/dn/#\", qos = 0} command = {topic = \"/dn/#\", qos = 0}

View File

@ -35,7 +35,7 @@ gateway.lwm2m {
lifetime_max = 86400s lifetime_max = 86400s
qmode_time_window = 200 qmode_time_window = 200
auto_observe = false auto_observe = false
mountpoint = \"lwm2m/%u\" mountpoint = \"lwm2m/${username}\"
update_msg_publish_condition = contains_object_list update_msg_publish_condition = contains_object_list
translators { translators {
command = {topic = \"/dn/#\", qos = 0} command = {topic = \"/dn/#\", qos = 0}