Просмотр исходного кода

Merge pull request #13177 from lafirest/sync-authn-com

feat: sync `client.check_authn_complete` to release-57
lafirest 1 год назад
Родитель
Сommit
d79193fde4

+ 35 - 11
apps/emqx/src/emqx_access_control.erl

@@ -56,31 +56,31 @@ authenticate(Credential) ->
     NotSuperUser = #{is_superuser => false},
     NotSuperUser = #{is_superuser => false},
     case pre_hook_authenticate(Credential) of
     case pre_hook_authenticate(Credential) of
         ok ->
         ok ->
-            inc_authn_metrics(anonymous),
+            on_authentication_complete(Credential, NotSuperUser, anonymous),
             {ok, NotSuperUser};
             {ok, NotSuperUser};
         continue ->
         continue ->
             case run_hooks('client.authenticate', [Credential], ignore) of
             case run_hooks('client.authenticate', [Credential], ignore) of
                 ignore ->
                 ignore ->
-                    inc_authn_metrics(anonymous),
+                    on_authentication_complete(Credential, NotSuperUser, anonymous),
                     {ok, NotSuperUser};
                     {ok, NotSuperUser};
                 ok ->
                 ok ->
-                    inc_authn_metrics(ok),
+                    on_authentication_complete(Credential, NotSuperUser, ok),
                     {ok, NotSuperUser};
                     {ok, NotSuperUser};
-                {ok, _AuthResult} = OkResult ->
-                    inc_authn_metrics(ok),
+                {ok, AuthResult} = OkResult ->
+                    on_authentication_complete(Credential, AuthResult, ok),
                     OkResult;
                     OkResult;
-                {ok, _AuthResult, _AuthData} = OkResult ->
-                    inc_authn_metrics(ok),
+                {ok, AuthResult, _AuthData} = OkResult ->
+                    on_authentication_complete(Credential, AuthResult, ok),
                     OkResult;
                     OkResult;
-                {error, _Reason} = Error ->
-                    inc_authn_metrics(error),
+                {error, Reason} = Error ->
+                    on_authentication_complete(Credential, Reason, error),
                     Error;
                     Error;
                 %% {continue, AuthCache} | {continue, AuthData, AuthCache}
                 %% {continue, AuthCache} | {continue, AuthData, AuthCache}
                 Other ->
                 Other ->
                     Other
                     Other
             end;
             end;
-        {error, _Reason} = Error ->
-            inc_authn_metrics(error),
+        {error, Reason} = Error ->
+            on_authentication_complete(Credential, Reason, error),
             Error
             Error
     end.
     end.
 
 
@@ -240,3 +240,27 @@ inc_authn_metrics(ok) ->
 inc_authn_metrics(anonymous) ->
 inc_authn_metrics(anonymous) ->
     emqx_metrics:inc('authentication.success.anonymous'),
     emqx_metrics:inc('authentication.success.anonymous'),
     emqx_metrics:inc('authentication.success').
     emqx_metrics:inc('authentication.success').
+
+on_authentication_complete(Credential, Reason, error) ->
+    emqx_hooks:run(
+        'client.check_authn_complete',
+        [
+            Credential,
+            #{
+                reason_code => Reason
+            }
+        ]
+    ),
+    inc_authn_metrics(error);
+on_authentication_complete(Credential, Result, Type) ->
+    emqx_hooks:run(
+        'client.check_authn_complete',
+        [
+            Credential,
+            Result#{
+                reason_code => success,
+                is_anonymous => (Type =:= anonymous)
+            }
+        ]
+    ),
+    inc_authn_metrics(Type).

+ 1 - 0
apps/emqx/src/emqx_hookpoints.erl

@@ -44,6 +44,7 @@
     'client.disconnected',
     'client.disconnected',
     'client.authorize',
     'client.authorize',
     'client.check_authz_complete',
     'client.check_authz_complete',
+    'client.check_authn_complete',
     'client.authenticate',
     'client.authenticate',
     'client.subscribe',
     'client.subscribe',
     'client.unsubscribe',
     'client.unsubscribe',

+ 13 - 0
apps/emqx_rule_engine/src/emqx_rule_api_schema.erl

@@ -282,6 +282,18 @@ fields("ctx_check_authz_complete") ->
         {"authz_source", sc(binary(), #{desc => ?DESC("event_authz_source")})},
         {"authz_source", sc(binary(), #{desc => ?DESC("event_authz_source")})},
         {"result", sc(binary(), #{desc => ?DESC("event_result")})}
         {"result", sc(binary(), #{desc => ?DESC("event_result")})}
     ];
     ];
+fields("ctx_check_authn_complete") ->
+    Event = 'client.check_authn_complete',
+    [
+        {"event_type", event_type_sc(Event)},
+        {"event", event_sc(Event)},
+        {"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})},
+        {"username", sc(binary(), #{desc => ?DESC("event_username")})},
+        {"reason_code", sc(binary(), #{desc => ?DESC("event_ctx_authn_reason_code")})},
+        {"peername", sc(binary(), #{desc => ?DESC("event_peername")})},
+        {"is_anonymous", sc(boolean(), #{desc => ?DESC("event_is_anonymous"), required => false})},
+        {"is_superuser", sc(boolean(), #{desc => ?DESC("event_is_superuser"), required => false})}
+    ];
 fields("ctx_bridge_mqtt") ->
 fields("ctx_bridge_mqtt") ->
     Event = '$bridges/mqtt:*',
     Event = '$bridges/mqtt:*',
     EventBin = atom_to_binary(Event),
     EventBin = atom_to_binary(Event),
@@ -330,6 +342,7 @@ rule_input_message_context() ->
                 ref("ctx_disconnected"),
                 ref("ctx_disconnected"),
                 ref("ctx_connack"),
                 ref("ctx_connack"),
                 ref("ctx_check_authz_complete"),
                 ref("ctx_check_authz_complete"),
+                ref("ctx_check_authn_complete"),
                 ref("ctx_bridge_mqtt"),
                 ref("ctx_bridge_mqtt"),
                 ref("ctx_delivery_dropped"),
                 ref("ctx_delivery_dropped"),
                 ref("ctx_schema_validation_failed")
                 ref("ctx_schema_validation_failed")

+ 78 - 0
apps/emqx_rule_engine/src/emqx_rule_events.erl

@@ -40,6 +40,7 @@
     on_client_disconnected/4,
     on_client_disconnected/4,
     on_client_connack/4,
     on_client_connack/4,
     on_client_check_authz_complete/6,
     on_client_check_authz_complete/6,
+    on_client_check_authn_complete/3,
     on_session_subscribed/4,
     on_session_subscribed/4,
     on_session_unsubscribed/4,
     on_session_unsubscribed/4,
     on_message_publish/2,
     on_message_publish/2,
@@ -182,6 +183,18 @@ on_client_check_authz_complete(
         Conf
         Conf
     ).
     ).
 
 
+on_client_check_authn_complete(ClientInfo, Result, Conf) ->
+    apply_event(
+        'client.check_authn_complete',
+        fun() ->
+            eventmsg_check_authn_complete(
+                ClientInfo,
+                Result
+            )
+        end,
+        Conf
+    ).
+
 on_client_disconnected(ClientInfo, Reason, ConnInfo, Conf) ->
 on_client_disconnected(ClientInfo, Reason, ConnInfo, Conf) ->
     apply_event(
     apply_event(
         'client.disconnected',
         'client.disconnected',
@@ -438,6 +451,35 @@ eventmsg_check_authz_complete(
         #{}
         #{}
     ).
     ).
 
 
+eventmsg_check_authn_complete(
+    _ClientInfo = #{
+        clientid := ClientId,
+        username := Username,
+        peerhost := PeerHost,
+        peerport := PeerPort
+    },
+    Result
+) ->
+    #{
+        reason_code := Reason,
+        is_superuser := IsSuperuser,
+        is_anonymous := IsAnonymous
+    } = maps:merge(
+        #{is_anonymous => false, is_superuser => false}, Result
+    ),
+    with_basic_columns(
+        'client.check_authn_complete',
+        #{
+            clientid => ClientId,
+            username => Username,
+            peername => ntoa({PeerHost, PeerPort}),
+            reason_code => force_to_bin(Reason),
+            is_anonymous => IsAnonymous,
+            is_superuser => IsSuperuser
+        },
+        #{}
+    ).
+
 eventmsg_sub_or_unsub(
 eventmsg_sub_or_unsub(
     Event,
     Event,
     _ClientInfo = #{
     _ClientInfo = #{
@@ -679,6 +721,7 @@ event_info() ->
         event_info_client_disconnected(),
         event_info_client_disconnected(),
         event_info_client_connack(),
         event_info_client_connack(),
         event_info_client_check_authz_complete(),
         event_info_client_check_authz_complete(),
+        event_info_client_check_authn_complete(),
         event_info_session_subscribed(),
         event_info_session_subscribed(),
         event_info_session_unsubscribed(),
         event_info_session_unsubscribed(),
         event_info_delivery_dropped(),
         event_info_delivery_dropped(),
@@ -770,6 +813,13 @@ event_info_client_check_authz_complete() ->
         {<<"client check authz complete">>, <<"授权结果"/utf8>>},
         {<<"client check authz complete">>, <<"授权结果"/utf8>>},
         <<"SELECT * FROM \"$events/client_check_authz_complete\"">>
         <<"SELECT * FROM \"$events/client_check_authz_complete\"">>
     ).
     ).
+event_info_client_check_authn_complete() ->
+    event_info_common(
+        'client.check_authn_complete',
+        {<<"client check authn complete">>, <<"认证结果"/utf8>>},
+        {<<"client check authn complete">>, <<"认证结果"/utf8>>},
+        <<"SELECT * FROM \"$events/client_check_authn_complete\"">>
+    ).
 event_info_session_subscribed() ->
 event_info_session_subscribed() ->
     event_info_common(
     event_info_common(
         'session.subscribed',
         'session.subscribed',
@@ -854,6 +904,14 @@ test_columns('client.check_authz_complete') ->
         {<<"action">>, [<<"publish">>, <<"the action of publish or subscribe">>]},
         {<<"action">>, [<<"publish">>, <<"the action of publish or subscribe">>]},
         {<<"result">>, [<<"allow">>, <<"the authz check complete result">>]}
         {<<"result">>, [<<"allow">>, <<"the authz check complete result">>]}
     ];
     ];
+test_columns('client.check_authn_complete') ->
+    [
+        {<<"clientid">>, [<<"c_emqx">>, <<"the clientid if the client">>]},
+        {<<"username">>, [<<"u_emqx">>, <<"the username if the client">>]},
+        {<<"reason_code">>, [<<"sucess">>, <<"the reason code">>]},
+        {<<"is_superuser">>, [true, <<"Whether this is a superuser">>]},
+        {<<"is_anonymous">>, [false, <<"Whether this is a superuser">>]}
+    ];
 test_columns('session.unsubscribed') ->
 test_columns('session.unsubscribed') ->
     test_columns('session.subscribed');
     test_columns('session.subscribed');
 test_columns('session.subscribed') ->
 test_columns('session.subscribed') ->
@@ -1023,6 +1081,18 @@ columns_with_exam('client.check_authz_complete') ->
         {<<"timestamp">>, erlang:system_time(millisecond)},
         {<<"timestamp">>, erlang:system_time(millisecond)},
         {<<"node">>, node()}
         {<<"node">>, node()}
     ];
     ];
+columns_with_exam('client.check_authn_complete') ->
+    [
+        {<<"event">>, 'client.check_authz_complete'},
+        {<<"clientid">>, <<"c_emqx">>},
+        {<<"username">>, <<"u_emqx">>},
+        {<<"peername">>, <<"192.168.0.10:56431">>},
+        {<<"reason_code">>, <<"sucess">>},
+        {<<"is_superuser">>, true},
+        {<<"is_anonymous">>, false},
+        {<<"timestamp">>, erlang:system_time(millisecond)},
+        {<<"node">>, node()}
+    ];
 columns_with_exam('session.subscribed') ->
 columns_with_exam('session.subscribed') ->
     [columns_example_props(sub_props)] ++ columns_message_sub_unsub('session.subscribed');
     [columns_example_props(sub_props)] ++ columns_message_sub_unsub('session.subscribed');
 columns_with_exam('session.unsubscribed') ->
 columns_with_exam('session.unsubscribed') ->
@@ -1124,6 +1194,7 @@ hook_fun('client.connected') -> fun ?MODULE:on_client_connected/3;
 hook_fun('client.disconnected') -> fun ?MODULE:on_client_disconnected/4;
 hook_fun('client.disconnected') -> fun ?MODULE:on_client_disconnected/4;
 hook_fun('client.connack') -> fun ?MODULE:on_client_connack/4;
 hook_fun('client.connack') -> fun ?MODULE:on_client_connack/4;
 hook_fun('client.check_authz_complete') -> fun ?MODULE:on_client_check_authz_complete/6;
 hook_fun('client.check_authz_complete') -> fun ?MODULE:on_client_check_authz_complete/6;
+hook_fun('client.check_authn_complete') -> fun ?MODULE:on_client_check_authn_complete/3;
 hook_fun('session.subscribed') -> fun ?MODULE:on_session_subscribed/4;
 hook_fun('session.subscribed') -> fun ?MODULE:on_session_subscribed/4;
 hook_fun('session.unsubscribed') -> fun ?MODULE:on_session_unsubscribed/4;
 hook_fun('session.unsubscribed') -> fun ?MODULE:on_session_unsubscribed/4;
 hook_fun('message.delivered') -> fun ?MODULE:on_message_delivered/3;
 hook_fun('message.delivered') -> fun ?MODULE:on_message_delivered/3;
@@ -1139,6 +1210,11 @@ reason({shutdown, Reason}) when is_atom(Reason) -> Reason;
 reason({Error, _}) when is_atom(Error) -> Error;
 reason({Error, _}) when is_atom(Error) -> Error;
 reason(_) -> internal_error.
 reason(_) -> internal_error.
 
 
+force_to_bin(Bin) when is_binary(Bin) ->
+    Bin;
+force_to_bin(Term) ->
+    emqx_utils_conv:bin(io_lib:format("~p", [Term])).
+
 ntoa(undefined) ->
 ntoa(undefined) ->
     undefined;
     undefined;
 ntoa(IpOrIpPort) ->
 ntoa(IpOrIpPort) ->
@@ -1149,6 +1225,7 @@ event_name(<<"$events/client_connected">>) -> 'client.connected';
 event_name(<<"$events/client_disconnected">>) -> 'client.disconnected';
 event_name(<<"$events/client_disconnected">>) -> 'client.disconnected';
 event_name(<<"$events/client_connack">>) -> 'client.connack';
 event_name(<<"$events/client_connack">>) -> 'client.connack';
 event_name(<<"$events/client_check_authz_complete">>) -> 'client.check_authz_complete';
 event_name(<<"$events/client_check_authz_complete">>) -> 'client.check_authz_complete';
+event_name(<<"$events/client_check_authn_complete">>) -> 'client.check_authn_complete';
 event_name(<<"$events/session_subscribed">>) -> 'session.subscribed';
 event_name(<<"$events/session_subscribed">>) -> 'session.subscribed';
 event_name(<<"$events/session_unsubscribed">>) -> 'session.unsubscribed';
 event_name(<<"$events/session_unsubscribed">>) -> 'session.unsubscribed';
 event_name(<<"$events/message_delivered">>) -> 'message.delivered';
 event_name(<<"$events/message_delivered">>) -> 'message.delivered';
@@ -1163,6 +1240,7 @@ event_topic('client.connected') -> <<"$events/client_connected">>;
 event_topic('client.disconnected') -> <<"$events/client_disconnected">>;
 event_topic('client.disconnected') -> <<"$events/client_disconnected">>;
 event_topic('client.connack') -> <<"$events/client_connack">>;
 event_topic('client.connack') -> <<"$events/client_connack">>;
 event_topic('client.check_authz_complete') -> <<"$events/client_check_authz_complete">>;
 event_topic('client.check_authz_complete') -> <<"$events/client_check_authz_complete">>;
+event_topic('client.check_authn_complete') -> <<"$events/client_check_authn_complete">>;
 event_topic('session.subscribed') -> <<"$events/session_subscribed">>;
 event_topic('session.subscribed') -> <<"$events/session_subscribed">>;
 event_topic('session.unsubscribed') -> <<"$events/session_unsubscribed">>;
 event_topic('session.unsubscribed') -> <<"$events/session_unsubscribed">>;
 event_topic('message.delivered') -> <<"$events/message_delivered">>;
 event_topic('message.delivered') -> <<"$events/message_delivered">>;

+ 14 - 1
apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl

@@ -237,6 +237,7 @@ init_per_testcase(t_events, Config) ->
         "\"$events/client_disconnected\", "
         "\"$events/client_disconnected\", "
         "\"$events/client_connack\", "
         "\"$events/client_connack\", "
         "\"$events/client_check_authz_complete\", "
         "\"$events/client_check_authz_complete\", "
+        "\"$events/client_check_authn_complete\", "
         "\"$events/session_subscribed\", "
         "\"$events/session_subscribed\", "
         "\"$events/session_unsubscribed\", "
         "\"$events/session_unsubscribed\", "
         "\"$events/message_acked\", "
         "\"$events/message_acked\", "
@@ -1084,6 +1085,7 @@ client_connected(Client, Client2) ->
     {ok, _} = emqtt:connect(Client2),
     {ok, _} = emqtt:connect(Client2),
     verify_event('client.connack'),
     verify_event('client.connack'),
     verify_event('client.connected'),
     verify_event('client.connected'),
+    verify_event('client.check_authn_complete'),
     ok.
     ok.
 client_disconnected(Client, Client2) ->
 client_disconnected(Client, Client2) ->
     ok = emqtt:disconnect(Client, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
     ok = emqtt:disconnect(Client, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
@@ -4196,7 +4198,18 @@ verify_event_fields('client.check_authz_complete', Fields) ->
         ])
         ])
     ),
     ),
     ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
     ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
-    ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])).
+    ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>]));
+verify_event_fields('client.check_authn_complete', Fields) ->
+    #{
+        clientid := ClientId,
+        username := Username,
+        is_anonymous := IsAnonymous,
+        is_superuser := IsSuperuser
+    } = Fields,
+    ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
+    ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])),
+    ?assert(erlang:is_boolean(IsAnonymous)),
+    ?assert(erlang:is_boolean(IsSuperuser)).
 
 
 verify_peername(PeerName) ->
 verify_peername(PeerName) ->
     case string:split(PeerName, ":") of
     case string:split(PeerName, ":") of

+ 16 - 0
apps/emqx_rule_engine/test/emqx_rule_engine_api_2_SUITE.erl

@@ -265,6 +265,22 @@ t_rule_test_smoke(_Config) ->
                     <<"sql">> => <<"SELECT\n  *\nFROM\n  \"t/#\"">>
                     <<"sql">> => <<"SELECT\n  *\nFROM\n  \"t/#\"">>
                 }
                 }
         },
         },
+        #{
+            expected => #{code => 412},
+            input =>
+                #{
+                    <<"context">> =>
+                        #{
+                            <<"clientid">> => <<"c_emqx">>,
+                            <<"event_type">> => <<"client_check_authn_complete">>,
+                            <<"reason_code">> => <<"sucess">>,
+                            <<"is_superuser">> => true,
+                            <<"is_anonymous">> => false,
+                            <<"username">> => <<"u_emqx">>
+                        },
+                    <<"sql">> => <<"SELECT\n  *\nFROM\n  \"t/#\"">>
+                }
+        },
         #{
         #{
             expected => #{code => 412},
             expected => #{code => 412},
             input =>
             input =>

+ 23 - 0
apps/emqx_rule_engine/test/emqx_rule_engine_api_rule_test_SUITE.erl

@@ -195,6 +195,29 @@ t_ctx_check_authz_complete(_) ->
 
 
     do_test(SQL, Context, Expected).
     do_test(SQL, Context, Expected).
 
 
+t_ctx_check_authn_complete(_) ->
+    SQL =
+        <<
+            "SELECT clientid, username, is_superuser, is_anonymous\n"
+            "FROM \"$events/client_check_authn_complete\""
+        >>,
+
+    Context =
+        #{
+            clientid => <<"c_emqx">>,
+            event_type => client_check_authn_complete,
+            reason_code => <<"sucess">>,
+            is_superuser => true,
+            is_anonymous => false
+        },
+    Expected = check_result(
+        [clientid, username, is_superuser, is_anonymous],
+        [],
+        Context
+    ),
+
+    do_test(SQL, Context, Expected).
+
 t_ctx_delivery_dropped(_) ->
 t_ctx_delivery_dropped(_) ->
     SQL =
     SQL =
         <<"SELECT from_clientid, from_username, reason, topic, qos FROM \"$events/delivery_dropped\"">>,
         <<"SELECT from_clientid, from_username, reason, topic, qos FROM \"$events/delivery_dropped\"">>,

+ 1 - 0
changes/ce/feat-12983.en.md

@@ -0,0 +1 @@
+Add new rule engine event `$events/client_check_authn_complete` for authentication completion event.

+ 9 - 0
rel/i18n/emqx_rule_api_schema.hocon

@@ -390,4 +390,13 @@ event_ctx_disconnected_reason.desc:
 event_ctx_disconnected_reason.label:
 event_ctx_disconnected_reason.label:
 """Disconnect Reason"""
 """Disconnect Reason"""
 
 
+event_is_anonymous.desc:
+"""True if this user is anonymous."""
+
+event_is_superuser.desc:
+"""True if this is a super user."""
+
+event_ctx_authn_reason_code.desc:
+"""The reason code"""
+
 }
 }