فهرست منبع

Merge pull request #8351 from HJianBo/compatible-password-field

Authn: backword compatibility for 4.x authn data
JianBo He 3 سال پیش
والد
کامیت
094ed05897

+ 3 - 0
.github/workflows/run_test_cases.yaml

@@ -204,6 +204,9 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: make coveralls
+      - name: get coveralls logs
+        if: failure()
+        run: cat rebar3.crashdump
 
     # do this in a separate job
     upload_coverdata:

+ 8 - 2
apps/emqx_authn/src/emqx_authn.appup.src

@@ -1,5 +1,11 @@
 %% -*- mode: erlang -*-
 %% Unless you know what you are doing, DO NOT edit manually!!
 {VSN,
-  [{"0.1.0",[{load_module,emqx_authn_utils,brutal_purge,soft_purge,[]}]}],
-  [{"0.1.0",[{load_module,emqx_authn_utils,brutal_purge,soft_purge,[]}]}]}.
+  [{"0.1.0",[
+     {load_module,emqx_authn_utils,brutal_purge,soft_purge,[]},
+     {load_module,emqx_authn_redis,brutal_purge,soft_purge,[]}]
+   }],
+  [{"0.1.0",[
+     {load_module,emqx_authn_utils,brutal_purge,soft_purge,[]},
+     {load_module,emqx_authn_redis,brutal_purge,soft_purge,[]}]
+   }]}.

+ 23 - 7
apps/emqx_authn/src/emqx_authn_utils.erl

@@ -72,13 +72,25 @@ start_resource_if_enabled(Result, _ResourceId, _Config) ->
 
 check_password_from_selected_map(_Algorithm, _Selected, undefined) ->
     {error, bad_username_or_password};
-check_password_from_selected_map(
-    Algorithm, #{<<"password_hash">> := Hash} = Selected, Password
-) ->
-    Salt = maps:get(<<"salt">>, Selected, <<>>),
-    case emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password) of
-        true -> ok;
-        false -> {error, bad_username_or_password}
+check_password_from_selected_map(Algorithm, Selected, Password) ->
+    Hash = maps:get(
+        <<"password_hash">>,
+        Selected,
+        maps:get(<<"password">>, Selected, undefined)
+    ),
+    case Hash of
+        undefined ->
+            {error, not_authorized};
+        _ ->
+            Salt = maps:get(<<"salt">>, Selected, <<>>),
+            case
+                emqx_authn_password_hashing:check_password(
+                    Algorithm, Salt, Hash, Password
+                )
+            of
+                true -> ok;
+                false -> {error, bad_username_or_password}
+            end
     end.
 
 parse_deep(Template) ->
@@ -160,10 +172,14 @@ make_resource_id(Name) ->
 
 handle_var({var, Name}, undefined) ->
     error({cannot_get_variable, Name});
+handle_var({var, <<"peerhost">>}, PeerHost) ->
+    emqx_placeholder:bin(inet:ntoa(PeerHost));
 handle_var(_, Value) ->
     emqx_placeholder:bin(Value).
 
 handle_sql_var({var, Name}, undefined) ->
     error({cannot_get_variable, Name});
+handle_sql_var({var, <<"peerhost">>}, PeerHost) ->
+    emqx_placeholder:bin(inet:ntoa(PeerHost));
 handle_sql_var(_, Value) ->
     emqx_placeholder:sql_data(Value).

+ 12 - 23
apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl

@@ -138,27 +138,16 @@ authenticate(
         {ok, []} ->
             ignore;
         {ok, Values} ->
-            case merge(Fields, Values) of
-                #{<<"password_hash">> := _} = Selected ->
-                    case
-                        emqx_authn_utils:check_password_from_selected_map(
-                            Algorithm, Selected, Password
-                        )
-                    of
-                        ok ->
-                            {ok, emqx_authn_utils:is_superuser(Selected)};
-                        {error, Reason} ->
-                            {error, Reason}
-                    end;
-                _ ->
-                    ?SLOG(error, #{
-                        msg => "cannot_find_password_hash_field",
-                        cmd => Command,
-                        keys => NKey,
-                        fields => Fields,
-                        resource => ResourceId
-                    }),
-                    ignore
+            Selected = merge(Fields, Values),
+            case
+                emqx_authn_utils:check_password_from_selected_map(
+                    Algorithm, Selected, Password
+                )
+            of
+                ok ->
+                    {ok, emqx_authn_utils:is_superuser(Selected)};
+                {error, Reason} ->
+                    {error, Reason}
             end;
         {error, Reason} ->
             ?SLOG(error, #{
@@ -210,8 +199,8 @@ parse_cmd(Cmd) ->
     end.
 
 check_fields(Fields) ->
-    HasPassHash = lists:member("password_hash", Fields),
-    KnownFields = ["password_hash", "salt", "is_superuser"],
+    HasPassHash = lists:member("password_hash", Fields) orelse lists:member("password", Fields),
+    KnownFields = ["password_hash", "password", "salt", "is_superuser"],
     UnknownFields = [F || F <- Fields, not lists:member(F, KnownFields)],
 
     case {HasPassHash, UnknownFields} of

+ 28 - 0
apps/emqx_authn/test/emqx_authn_http_SUITE.erl

@@ -29,8 +29,10 @@
 -define(HTTP_PORT, 32333).
 -define(HTTP_PATH, "/auth").
 -define(CREDENTIALS, #{
+    clientid => <<"clienta">>,
     username => <<"plain">>,
     password => <<"plain">>,
+    peerhost => {127, 0, 0, 1},
     listener => 'tcp:default',
     protocol => mqtt
 }).
@@ -390,6 +392,32 @@ samples() ->
             result => {ok, #{is_superuser => false}}
         },
 
+        %% simple post request for placeholders, application/json
+        #{
+            handler => fun(Req0, State) ->
+                {ok, RawBody, Req1} = cowboy_req:read_body(Req0),
+                #{
+                    <<"username">> := <<"plain">>,
+                    <<"password">> := <<"plain">>,
+                    <<"clientid">> := <<"clienta">>,
+                    <<"peerhost">> := <<"127.0.0.1">>
+                } = jiffy:decode(RawBody, [return_maps]),
+                Req = cowboy_req:reply(200, Req1),
+                {ok, Req, State}
+            end,
+            config_params => #{
+                <<"method">> => <<"post">>,
+                <<"headers">> => #{<<"content-type">> => <<"application/json">>},
+                <<"body">> => #{
+                    <<"clientid">> => ?PH_CLIENTID,
+                    <<"username">> => ?PH_USERNAME,
+                    <<"password">> => ?PH_PASSWORD,
+                    <<"peerhost">> => ?PH_PEERHOST
+                }
+            },
+            result => {ok, #{is_superuser => false}}
+        },
+
         %% custom headers
         #{
             handler => fun(Req0, State) ->

+ 22 - 0
apps/emqx_authn/test/emqx_authn_redis_SUITE.erl

@@ -453,6 +453,28 @@ user_seeds() ->
                 <<"password_hash_algorithm">> => #{<<"name">> => <<"bcrypt">>}
             },
             result => {error, bad_username_or_password}
+        },
+
+        #{
+            data => #{
+                password =>
+                    <<"a3c7f6b085c3e5897ffb9b86f18a9d905063f8550a74444b5892e193c1b50428">>,
+                is_superuser => <<"1">>
+            },
+            credentials => #{
+                clientid => <<"sha256_no_salt">>,
+                password => <<"sha256_no_salt">>
+            },
+            key => <<"mqtt_user:sha256_no_salt">>,
+            config_params => #{
+                %% Needs to be compatible with emqx 4.x auth data
+                <<"cmd">> => <<"HMGET mqtt_user:${clientid} password is_superuser">>,
+                <<"password_hash_algorithm">> => #{
+                    <<"name">> => <<"sha256">>,
+                    <<"salt_position">> => <<"disable">>
+                }
+            },
+            result => {ok, #{is_superuser => true}}
         }
     ].