chore(authn): improve code

This commit is contained in:
zhouzb 2021-09-27 14:50:22 +08:00
parent 096e85dc14
commit 2262bf508e
8 changed files with 89 additions and 88 deletions

View File

@ -310,7 +310,7 @@ do_authenticate([#authenticator{id = ID, provider = Provider, state = State} | M
{stop, Result}
catch
Class:Reason:Stacktrace ->
?SLOG(warning, #{msg => "an unexpected error in authentication",
?SLOG(warning, #{msg => "unexpected_error_in_authentication",
class => Class,
reason => Reason,
stacktrace => Stacktrace,

View File

@ -18,6 +18,8 @@
-export([ replace_placeholders/2
, replace_placeholder/2
, check_password/3
, is_superuser/1
, hash/4
, gen_salt/0
, bin/1
@ -55,6 +57,28 @@ replace_placeholder(<<"${cert-common-name}">>, Credential) ->
replace_placeholder(Constant, _) ->
Constant.
check_password(undefined, _Selected, _State) ->
{error, bad_username_or_password};
check_password(Password,
#{<<"password_hash">> := Hash},
#{password_hash_algorithm := bcrypt}) ->
case {ok, Hash} =:= bcrypt:hashpw(Password, Hash) of
true -> ok;
false -> {error, bad_username_or_password}
end;
check_password(Password,
#{<<"password_hash">> := Hash} = Selected,
#{password_hash_algorithm := Algorithm,
salt_position := SaltPosition}) ->
Salt = maps:get(<<"salt">>, Selected, <<>>),
case Hash =:= hash(Algorithm, Password, Salt, SaltPosition) of
true -> ok;
false -> {error, bad_username_or_password}
end.
is_superuser(Selected) ->
#{is_superuser => maps:get(<<"is_superuser">>, Selected, false)}.
hash(Algorithm, Password, Salt, prefix) ->
emqx_passwd:hash(Algorithm, <<Salt/binary, Password/binary>>);
hash(Algorithm, Password, Salt, suffix) ->
@ -75,4 +99,4 @@ bin(X) -> X.
convert_to_sql_param(undefined) ->
null;
convert_to_sql_param(V) ->
bin(V).
bin(V).

View File

@ -165,11 +165,14 @@ authenticate(Credential, #{'_unique' := Unique,
{ok, NBody} ->
%% TODO: Return by user property
{ok, #{is_superuser => maps:get(<<"is_superuser">>, NBody, false),
user_property => NBody}};
user_property => NBody}};
{error, _Reason} ->
{ok, #{is_superuser => false}}
end;
{error, _Reason} ->
{error, Reason} ->
?SLOG(error, #{msg => "http_server_query_failed",
resource => Unique,
reason => Reason}),
ignore
end.

View File

@ -94,7 +94,7 @@ handle_info({http, {RequestID, Result}},
State1 = State0#{request_id := undefined},
case Result of
{error, Reason} ->
?SLOG(warning, #{msg => "failed to request jwks endpoint",
?SLOG(warning, #{msg => "failed_to_request_jwks_endpoint",
endpoint => Endpoint,
reason => Reason}),
State1;
@ -104,7 +104,7 @@ handle_info({http, {RequestID, Result}},
{_, JWKs} = JWKS#jose_jwk.keys,
State1#{jwks := JWKs}
catch _:_ ->
?SLOG(warning, #{msg => "invalid jwks returned from jwks endpoint",
?SLOG(warning, #{msg => "invalid_jwks_returned",
endpoint => Endpoint,
body => Body}),
State1
@ -140,11 +140,14 @@ handle_options(#{endpoint := Endpoint,
refresh_jwks(#{endpoint := Endpoint,
ssl_opts := SSLOpts} = State) ->
HTTPOpts = [{timeout, 5000}, {connect_timeout, 5000}, {ssl, SSLOpts}],
HTTPOpts = [ {timeout, 5000}
, {connect_timeout, 5000}
, {ssl, SSLOpts}
],
NState = case httpc:request(get, {Endpoint, [{"Accept", "application/json"}]}, HTTPOpts,
[{body_format, binary}, {sync, false}, {receiver, self()}]) of
{error, Reason} ->
?SLOG(warning, #{msg => "failed to request jwks endpoint",
?SLOG(warning, #{msg => "failed_to_request_jwks_endpoint",
endpoint => Endpoint,
reason => Reason}),
State;

View File

@ -146,8 +146,8 @@ authenticate(#{password := Password} = Credential,
case emqx_resource:query(Unique, {find_one, Collection, Selector2, #{}}) of
undefined -> ignore;
{error, Reason} ->
?SLOG(error, #{msg => "query failed",
unique => Unique,
?SLOG(error, #{msg => "mongodb_query_failed",
resource => Unique,
reason => Reason}),
ignore;
Doc ->
@ -155,10 +155,10 @@ authenticate(#{password := Password} = Credential,
ok ->
{ok, #{is_superuser => is_superuser(Doc, State)}};
{error, {cannot_find_password_hash_field, PasswordHashField}} ->
?SLOG(error, #{msg => "can't find password hash field",
unique => Unique,
?SLOG(error, #{msg => "cannot_find_password_hash_field",
resource => Unique,
password_hash_field => PasswordHashField}),
{error, bad_username_or_password};
ignore;
{error, Reason} ->
{error, Reason}
end

View File

@ -119,13 +119,16 @@ authenticate(#{password := Password} = Credential,
{ok, _Columns, []} -> ignore;
{ok, Columns, Rows} ->
Selected = maps:from_list(lists:zip(Columns, Rows)),
case check_password(Password, Selected, State) of
case emqx_authn_utils:check_password(Password, Selected, State) of
ok ->
{ok, #{is_superuser => maps:get(<<"is_superuser">>, Selected, false)}};
{ok, emqx_authn_utils:is_superuser(Selected)};
{error, Reason} ->
{error, Reason}
end;
{error, _Reason} ->
{error, Reason} ->
?SLOG(error, #{msg => "mysql_query_failed",
resource => Unique,
reason => Reason}),
ignore
end.
@ -137,24 +140,24 @@ destroy(#{'_unique' := Unique}) ->
%% Internal functions
%%------------------------------------------------------------------------------
check_password(undefined, _Selected, _State) ->
{error, bad_username_or_password};
check_password(Password,
#{<<"password_hash">> := Hash},
#{password_hash_algorithm := bcrypt}) ->
case {ok, Hash} =:= bcrypt:hashpw(Password, Hash) of
true -> ok;
false -> {error, bad_username_or_password}
end;
check_password(Password,
#{<<"password_hash">> := Hash} = Selected,
#{password_hash_algorithm := Algorithm,
salt_position := SaltPosition}) ->
Salt = maps:get(<<"salt">>, Selected, <<>>),
case Hash =:= emqx_authn_utils:hash(Algorithm, Password, Salt, SaltPosition) of
true -> ok;
false -> {error, bad_username_or_password}
end.
% check_password(undefined, _Selected, _State) ->
% {error, bad_username_or_password};
% check_password(Password,
% #{<<"password_hash">> := Hash},
% #{password_hash_algorithm := bcrypt}) ->
% case {ok, Hash} =:= bcrypt:hashpw(Password, Hash) of
% true -> ok;
% false -> {error, bad_username_or_password}
% end;
% check_password(Password,
% #{<<"password_hash">> := Hash} = Selected,
% #{password_hash_algorithm := Algorithm,
% salt_position := SaltPosition}) ->
% Salt = maps:get(<<"salt">>, Selected, <<>>),
% case Hash =:= emqx_authn_utils:hash(Algorithm, Password, Salt, SaltPosition) of
% true -> ok;
% false -> {error, bad_username_or_password}
% end.
%% TODO: Support prepare
parse_query(Query) ->

View File

@ -109,13 +109,16 @@ authenticate(#{password := Password} = Credential,
{ok, Columns, Rows} ->
NColumns = [Name || #column{name = Name} <- Columns],
Selected = maps:from_list(lists:zip(NColumns, Rows)),
case check_password(Password, Selected, State) of
case emqx_authn_utils:check_password(Password, Selected, State) of
ok ->
{ok, #{is_superuser => maps:get(<<"is_superuser">>, Selected, false)}};
{ok, emqx_authn_utils:is_superuser(Selected)};
{error, Reason} ->
{error, Reason}
end;
{error, _Reason} ->
{error, Reason} ->
?SLOG(error, #{msg => "postgresql_query_failed",
resource => Unique,
reason => Reason}),
ignore
end.
@ -127,25 +130,6 @@ destroy(#{'_unique' := Unique}) ->
%% Internal functions
%%------------------------------------------------------------------------------
check_password(undefined, _Selected, _State) ->
{error, bad_username_or_password};
check_password(Password,
#{<<"password_hash">> := Hash},
#{password_hash_algorithm := bcrypt}) ->
case {ok, Hash} =:= bcrypt:hashpw(Password, Hash) of
true -> ok;
false -> {error, bad_username_or_password}
end;
check_password(Password,
#{<<"password_hash">> := Hash} = Selected,
#{password_hash_algorithm := Algorithm,
salt_position := SaltPosition}) ->
Salt = maps:get(<<"salt">>, Selected, <<>>),
case Hash =:= emqx_authn_utils:hash(Algorithm, Password, Salt, SaltPosition) of
true -> ok;
false -> {error, bad_username_or_password}
end.
%% TODO: Support prepare
parse_query(Query) ->
case re:run(Query, ?RE_PLACEHOLDER, [global, {capture, all, binary}]) of

View File

@ -130,16 +130,22 @@ authenticate(#{password := Password} = Credential,
NKey = binary_to_list(iolist_to_binary(replace_placeholders(Key, Credential))),
case emqx_resource:query(Unique, {cmd, [Command, NKey | Fields]}) of
{ok, Values} ->
Selected = merge(Fields, Values),
case check_password(Password, Selected, State) of
ok ->
{ok, #{is_superuser => maps:get("is_superuser", Selected, false)}};
{error, Reason} ->
{error, Reason}
case merge(Fields, Values) of
#{<<"password_hash">> := _} = Selected ->
case emqx_authn_utils:check_password(Password, Selected, State) of
ok ->
{ok, emqx_authn_utils:is_superuser(Selected)};
{error, Reason} ->
{error, Reason}
end;
_ ->
?SLOG(error, #{msg => "cannot_find_password_hash_field",
resource => Unique}),
ignore
end;
{error, Reason} ->
?SLOG(error, #{msg => "query failed",
unique => Unique,
?SLOG(error, #{msg => "redis_query_failed",
resource => Unique,
reason => Reason}),
ignore
end.
@ -205,27 +211,5 @@ merge(Fields, Value) when not is_list(Value) ->
merge(Fields, [Value]);
merge(Fields, Values) ->
maps:from_list(
lists:filter(fun({_, V}) ->
V =/= undefined
end, lists:zip(Fields, Values))).
check_password(undefined, _Selected, _State) ->
{error, bad_username_or_password};
check_password(Password,
#{"password_hash" := PasswordHash},
#{password_hash_algorithm := bcrypt}) ->
case {ok, PasswordHash} =:= bcrypt:hashpw(Password, PasswordHash) of
true -> ok;
false -> {error, bad_username_or_password}
end;
check_password(Password,
#{"password_hash" := PasswordHash} = Selected,
#{password_hash_algorithm := Algorithm,
salt_position := SaltPosition}) ->
Salt = maps:get("salt", Selected, <<>>),
case PasswordHash =:= emqx_authn_utils:hash(Algorithm, Password, Salt, SaltPosition) of
true -> ok;
false -> {error, bad_username_or_password}
end;
check_password(_Password, _Selected, _State) ->
ignore.
[{list_to_binary(K), V}
|| {K, V} <- lists:zip(Fields, Values), V =/= undefined]).