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:
parent
6fb464fc05
commit
21bd9bba55
|
@ -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
|
||||||
|
|
|
@ -20,57 +20,93 @@
|
||||||
-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.
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
})).
|
})).
|
||||||
|
|
|
@ -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">>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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}),
|
||||||
|
|
||||||
|
@ -82,9 +85,8 @@ 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: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}).
|
||||||
|
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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">>
|
||||||
},
|
},
|
||||||
|
|
|
@ -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}).
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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,15 +90,15 @@ 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.
|
||||||
|
|
||||||
|
|
|
@ -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,15 +95,15 @@ 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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
@ -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]).
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 #">>}
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue