Auth packet (#3374)

This commit is contained in:
张奇怪 2020-04-10 19:45:18 +08:00 committed by GitHub
parent 1790cde942
commit d4932533ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 22 deletions

View File

@ -71,6 +71,8 @@
topic_aliases :: emqx_types:topic_aliases(),
%% MQTT Topic Alias Maximum
alias_maximum :: maybe(map()),
%% Authentication Data Cache
auth_cache :: maybe(map()),
%% Timers
timers :: #{atom() => disabled | maybe(reference())},
%% Conn State
@ -185,6 +187,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
topic_aliases = #{inbound => #{},
outbound => #{}
},
auth_cache = #{},
timers = #{},
conn_state = idle,
takeover = false,
@ -216,10 +219,41 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
fun check_banned/2,
fun auth_connect/2
], ConnPkt, Channel#channel{conn_state = connecting}) of
{ok, NConnPkt, NChannel} ->
process_connect(NConnPkt, NChannel);
{ok, NConnPkt, NChannel = #channel{clientinfo = ClientInfo}} ->
NChannel1 = NChannel#channel{
will_msg = emqx_packet:will_msg(NConnPkt),
alias_maximum = init_alias_maximum(NConnPkt, ClientInfo)
},
case enhanced_auth(?CONNECT_PACKET(NConnPkt), NChannel1) of
{ok, Properties, NChannel2} ->
process_connect(Properties, ensure_connected(NChannel2));
{continue, Properties, NChannel2} ->
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, Properties}, NChannel2);
{error, ReasonCode, NChannel2} ->
handle_out(connack, ReasonCode, NChannel2)
end;
{error, ReasonCode, NChannel} ->
handle_out(connack, {ReasonCode, ConnPkt}, NChannel)
handle_out(connack, ReasonCode, NChannel)
end;
handle_in(Packet = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, _Properties), Channel) ->
case enhanced_auth(Packet, Channel) of
{ok, NProperties, NChannel} ->
process_connect(NProperties, ensure_connected(NChannel));
{continue, NProperties, NChannel} ->
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, NProperties}, NChannel);
{error, NReasonCode, NChannel} ->
handle_out(connack, NReasonCode, NChannel)
end;
handle_in(Packet = ?AUTH_PACKET(?RC_RE_AUTHENTICATE, _Properties), Channel) ->
case enhanced_auth(Packet, Channel) of
{ok, NProperties, NChannel} ->
handle_out(auth, {?RC_SUCCESS, NProperties}, NChannel);
{continue, NProperties, NChannel} ->
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, NProperties}, NChannel);
{error, NReasonCode, NChannel} ->
handle_out(disconnect, NReasonCode, NChannel)
end;
handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) ->
@ -362,24 +396,23 @@ handle_in(Packet, Channel) ->
%% Process Connect
%%--------------------------------------------------------------------
process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart},
Channel = #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) ->
process_connect(AckProps, Channel = #channel{conninfo = #{clean_start := CleanStart} = ConnInfo, clientinfo = ClientInfo}) ->
case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of
{ok, #{session := Session, present := false}} ->
NChannel = Channel#channel{session = Session},
handle_out(connack, {?RC_SUCCESS, sp(false), ConnPkt}, NChannel);
handle_out(connack, {?RC_SUCCESS, sp(false), AckProps}, NChannel);
{ok, #{session := Session, present := true, pendings := Pendings}} ->
Pendings1 = lists:usort(lists:append(Pendings, emqx_misc:drain_deliver())),
NChannel = Channel#channel{session = Session,
resuming = true,
pendings = Pendings1
},
handle_out(connack, {?RC_SUCCESS, sp(true), ConnPkt}, NChannel);
handle_out(connack, {?RC_SUCCESS, sp(true), AckProps}, NChannel);
{error, client_id_unavailable} ->
handle_out(connack, {?RC_CLIENT_IDENTIFIER_NOT_VALID, ConnPkt}, Channel);
handle_out(connack, ?RC_CLIENT_IDENTIFIER_NOT_VALID, Channel);
{error, Reason} ->
?LOG(error, "Failed to open session due to ~p", [Reason]),
handle_out(connack, {?RC_UNSPECIFIED_ERROR, ConnPkt}, Channel)
handle_out(connack, ?RC_UNSPECIFIED_ERROR, Channel)
end.
%%--------------------------------------------------------------------
@ -579,18 +612,17 @@ not_nacked({deliver, _Topic, Msg}) ->
| {ok, replies(), channel()}
| {shutdown, Reason :: term(), channel()}
| {shutdown, Reason :: term(), replies(), channel()}).
handle_out(connack, {?RC_SUCCESS, SP, ConnPkt}, Channel = #channel{conninfo = ConnInfo}) ->
handle_out(connack, {?RC_SUCCESS, SP, Props}, Channel = #channel{conninfo = ConnInfo}) ->
AckProps = run_fold([fun enrich_connack_caps/2,
fun enrich_server_keepalive/2,
fun enrich_assigned_clientid/2
], #{}, Channel),
], Props, Channel),
NAckProps = run_hooks('client.connack', [ConnInfo, emqx_reason_codes:name(?RC_SUCCESS)], AckProps),
return_connack(?CONNACK_PACKET(?RC_SUCCESS, SP, NAckProps),
ensure_keepalive(NAckProps,
ensure_connected(ConnPkt, Channel)));
ensure_keepalive(NAckProps, Channel));
handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo}) ->
handle_out(connack, ReasonCode, Channel = #channel{conninfo = ConnInfo}) ->
Reason = emqx_reason_codes:name(ReasonCode),
AckProps = run_hooks('client.connack', [ConnInfo, Reason], emqx_mqtt_props:new()),
AckPacket = ?CONNACK_PACKET(case maps:get(proto_ver, ConnInfo) of
@ -643,6 +675,9 @@ handle_out(disconnect, {ReasonCode, ReasonName}, Channel = ?IS_MQTT_V5) ->
handle_out(disconnect, {_ReasonCode, ReasonName}, Channel) ->
{ok, {close, ReasonName}, Channel};
handle_out(auth, {ReasonCode, Properties}, Channel) ->
{ok, ?AUTH_PACKET(ReasonCode, Properties), Channel};
handle_out(Type, Data, Channel) ->
?LOG(error, "Unexpected outgoing: ~s, ~p", [Type, Data]),
{ok, Channel}.
@ -1069,6 +1104,55 @@ auth_connect(#mqtt_packet_connect{clientid = ClientId,
is_anonymous(#{anonymous := true}) -> true;
is_anonymous(_AuthResult) -> false.
%%--------------------------------------------------------------------
%% Enhanced Authentication
enhanced_auth(?CONNECT_PACKET(#mqtt_packet_connect{
proto_ver = Protover,
properties = Properties
}), Channel) ->
case Protover of
?MQTT_PROTO_V5 ->
AuthMethod = emqx_mqtt_props:get('Authentication-Method', Properties, undefined),
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
do_enhanced_auth(AuthMethod, AuthData, Channel);
_ ->
{ok, #{}, Channel}
end;
enhanced_auth(?AUTH_PACKET(_ReasonCode, Properties), Channel = #channel{conninfo = ConnInfo}) ->
AuthMethod = maps:get('Authentication-Method', maps:get(conn_props, ConnInfo), undefined),
NAuthMethod = emqx_mqtt_props:get('Authentication-Method', Properties, undefined),
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
case NAuthMethod =:= undefined orelse NAuthMethod =/= AuthMethod of
true ->
{error, emqx_reason_codes:connack_error(bad_authentication_method), Channel};
false ->
do_enhanced_auth(AuthMethod, AuthData, Channel)
end.
do_enhanced_auth(undefined, undefined, Channel) ->
{ok, #{}, Channel};
do_enhanced_auth(undefined, _AuthData, Channel) ->
{error, emqx_reason_codes:connack_error(not_authorized), Channel};
do_enhanced_auth(_AuthMethod, undefined, Channel) ->
{error, emqx_reason_codes:connack_error(not_authorized), Channel};
do_enhanced_auth(AuthMethod, AuthData, Channel = #channel{auth_cache = Cache}) ->
case do_auth_check(AuthMethod, AuthData, Cache) of
ok -> {ok, #{}, Channel#channel{auth_cache = #{}}};
{ok, NAuthData} ->
NProperties = #{'Authentication-Method' => AuthMethod, 'Authentication-Data' => NAuthData},
{ok, NProperties, Channel#channel{auth_cache = #{}}};
{continue, NAuthData, NCache} ->
NProperties = #{'Authentication-Method' => AuthMethod, 'Authentication-Data' => NAuthData},
{continue, NProperties, Channel#channel{auth_cache = NCache}};
{error, _Reason} ->
{error, emqx_reason_codes:connack_error(not_authorized), Channel}
end.
do_auth_check(_AuthMethod, _AuthData, _AuthDataCache) ->
{error, not_authorized}.
%%--------------------------------------------------------------------
%% Process Topic Alias
@ -1259,14 +1343,12 @@ enrich_assigned_clientid(AckProps, #channel{conninfo = ConnInfo,
%%--------------------------------------------------------------------
%% Ensure connected
ensure_connected(ConnPkt, Channel = #channel{conninfo = ConnInfo,
clientinfo = ClientInfo}) ->
ensure_connected(Channel = #channel{conninfo = ConnInfo,
clientinfo = ClientInfo}) ->
NConnInfo = ConnInfo#{connected_at => erlang:system_time(second)},
ok = run_hooks('client.connected', [ClientInfo, NConnInfo]),
Channel#channel{conninfo = NConnInfo,
conn_state = connected,
will_msg = emqx_packet:will_msg(ConnPkt),
alias_maximum = init_alias_maximum(ConnPkt, ClientInfo)
conn_state = connected
}.
%%--------------------------------------------------------------------

View File

@ -169,6 +169,7 @@ compat(_Other, _Code) -> undefined.
frame_error(frame_too_large) -> ?RC_PACKET_TOO_LARGE;
frame_error(_) -> ?RC_MALFORMED_PACKET.
connack_error(protocol_error) -> ?RC_PROTOCOL_ERROR;
connack_error(client_identifier_not_valid) -> ?RC_CLIENT_IDENTIFIER_NOT_VALID;
connack_error(bad_username_or_password) -> ?RC_BAD_USER_NAME_OR_PASSWORD;
connack_error(bad_clientid_or_password) -> ?RC_BAD_USER_NAME_OR_PASSWORD;

View File

@ -112,6 +112,43 @@ t_handle_in_unexpected_connect_packet(_) ->
{ok, [{outgoing, Packet}, {close, protocol_error}], Channel} =
emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), Channel).
t_handle_in_connect_auth_failed(_) ->
ConnPkt = #mqtt_packet_connect{
proto_name = <<"MQTT">>,
proto_ver = ?MQTT_PROTO_V5,
is_bridge = false,
clean_start = true,
keepalive = 30,
properties = #{
'Authentication-Method' => "failed_auth_method",
'Authentication-Data' => <<"failed_auth_data">>
},
clientid = <<"clientid">>,
username = <<"username">>
},
{shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} =
emqx_channel:handle_in(?CONNECT_PACKET(ConnPkt), channel(#{conn_state => idle})).
t_handle_in_continue_auth(_) ->
Properties = #{
'Authentication-Method' => "failed_auth_method",
'Authentication-Data' => <<"failed_auth_data">>
},
{shutdown, bad_authentication_method, ?CONNACK_PACKET(?RC_BAD_AUTHENTICATION_METHOD), _} =
emqx_channel:handle_in(?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION,Properties), channel()),
{shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} =
emqx_channel:handle_in(?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION,Properties), channel(#{conninfo => #{proto_ver => ?MQTT_PROTO_V5, conn_props => Properties}})).
t_handle_in_re_auth(_) ->
Properties = #{
'Authentication-Method' => "failed_auth_method",
'Authentication-Data' => <<"failed_auth_data">>
},
{ok, [{outgoing, ?DISCONNECT_PACKET(?RC_BAD_AUTHENTICATION_METHOD)}, {close, bad_authentication_method}], _} =
emqx_channel:handle_in(?AUTH_PACKET(?RC_RE_AUTHENTICATE,Properties), channel()),
{ok, [{outgoing, ?DISCONNECT_PACKET(?RC_NOT_AUTHORIZED)}, {close, not_authorized}], _} =
emqx_channel:handle_in(?AUTH_PACKET(?RC_RE_AUTHENTICATE,Properties), channel(#{conninfo => #{proto_ver => ?MQTT_PROTO_V5, conn_props => Properties}})).
t_handle_in_qos0_publish(_) ->
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
Channel = channel(#{conn_state => connected}),
@ -286,7 +323,7 @@ t_process_connect(_) ->
{ok, #{session => session(), present => false}}
end),
{ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS)}], _Chan} =
emqx_channel:process_connect(connpkt(), channel(#{conn_state => idle})).
emqx_channel:process_connect(#{}, channel(#{conn_state => idle})).
t_process_publish_qos0(_) ->
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
@ -346,12 +383,12 @@ t_handle_out_publish_nl(_) ->
t_handle_out_connack_sucess(_) ->
{ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, _)}], Channel} =
emqx_channel:handle_out(connack, {?RC_SUCCESS, 0, connpkt()}, channel()),
emqx_channel:handle_out(connack, {?RC_SUCCESS, 0, #{}}, channel()),
?assertEqual(connected, emqx_channel:info(conn_state, Channel)).
t_handle_out_connack_failure(_) ->
{shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _Chan} =
emqx_channel:handle_out(connack, {?RC_NOT_AUTHORIZED, connpkt()}, channel()).
emqx_channel:handle_out(connack, ?RC_NOT_AUTHORIZED, channel()).
t_handle_out_puback(_) ->
Channel = channel(#{conn_state => connected}),