From 3022ee081d31b989dcd654a7211ea20c0c68574f Mon Sep 17 00:00:00 2001 From: JianBo He Date: Fri, 1 Apr 2022 09:55:02 +0800 Subject: [PATCH] style(authn): reformat authn subdir source files --- .../emqx_enhanced_authn_scram_mnesia.erl | 298 +++++++++++------- .../src/proto/emqx_authn_proto_v1.erl | 13 +- .../src/simple_authn/emqx_authn_http.erl | 241 ++++++++------ .../emqx_authn_jwks_connector.erl | 160 ++++++---- .../src/simple_authn/emqx_authn_jwt.erl | 252 ++++++++------- .../src/simple_authn/emqx_authn_mnesia.erl | 291 ++++++++++------- .../src/simple_authn/emqx_authn_mongodb.erl | 169 ++++++---- .../src/simple_authn/emqx_authn_mysql.erl | 116 ++++--- .../src/simple_authn/emqx_authn_pgsql.erl | 95 ++++-- .../src/simple_authn/emqx_authn_redis.erl | 133 +++++--- 10 files changed, 1076 insertions(+), 692 deletions(-) diff --git a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl b/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl index 23fc99ecb..535ff30a9 100644 --- a/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl +++ b/apps/emqx_authn/src/enhanced_authn/emqx_enhanced_authn_scram_mnesia.erl @@ -23,47 +23,54 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). --export([ add_user/2 - , delete_user/2 - , update_user/3 - , lookup_user/2 - , list_users/2 - ]). +-export([ + add_user/2, + delete_user/2, + update_user/3, + lookup_user/2, + list_users/2 +]). --export([ query/4 - , format_user_info/1 - , group_match_spec/1]). +-export([ + query/4, + format_user_info/1, + group_match_spec/1 +]). -define(TAB, ?MODULE). --define(AUTHN_QSCHEMA, [ {<<"like_username">>, binary} - , {<<"user_group">>, binary}]). +-define(AUTHN_QSCHEMA, [ + {<<"like_username">>, binary}, + {<<"user_group">>, binary} +]). -define(QUERY_FUN, {?MODULE, query}). --type(user_group() :: binary()). +-type user_group() :: binary(). -export([mnesia/1]). -boot_mnesia({mnesia, [boot]}). --record(user_info, - { user_id - , stored_key - , server_key - , salt - , is_superuser - }). +-record(user_info, { + user_id, + stored_key, + server_key, + salt, + is_superuser +}). -reflect_type([user_group/0]). @@ -72,14 +79,15 @@ %%------------------------------------------------------------------------------ %% @doc Create or replicate tables. --spec(mnesia(boot | copy) -> ok). +-spec mnesia(boot | copy) -> ok. mnesia(boot) -> ok = mria:create_table(?TAB, [ - {rlog_shard, ?AUTH_SHARD}, - {storage, disc_copies}, - {record_name, user_info}, - {attributes, record_info(fields, user_info)}, - {storage_properties, [{ets, [{read_concurrency, true}]}]}]). + {rlog_shard, ?AUTH_SHARD}, + {storage, disc_copies}, + {record_name, user_info}, + {attributes, record_info(fields, user_info)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]} + ]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -90,10 +98,11 @@ namespace() -> "authn-scram-builtin_db". roots() -> [?CONF_NS]. fields(?CONF_NS) -> - [ {mechanism, emqx_authn_schema:mechanism('scram')} - , {backend, emqx_authn_schema:backend('built_in_database')} - , {algorithm, fun algorithm/1} - , {iteration_count, fun iteration_count/1} + [ + {mechanism, emqx_authn_schema:mechanism('scram')}, + {backend, emqx_authn_schema:backend('built_in_database')}, + {algorithm, fun algorithm/1}, + {iteration_count, fun iteration_count/1} ] ++ emqx_authn_schema:common_fields(). algorithm(type) -> hoconsc:enum([sha256, sha512]); @@ -109,23 +118,33 @@ iteration_count(_) -> undefined. %%------------------------------------------------------------------------------ refs() -> - [hoconsc:ref(?MODULE, ?CONF_NS)]. + [hoconsc:ref(?MODULE, ?CONF_NS)]. -create(AuthenticatorID, - #{algorithm := Algorithm, - iteration_count := IterationCount}) -> - State = #{user_group => AuthenticatorID, - algorithm => Algorithm, - iteration_count => IterationCount}, +create( + AuthenticatorID, + #{ + algorithm := Algorithm, + iteration_count := IterationCount + } +) -> + State = #{ + user_group => AuthenticatorID, + algorithm => Algorithm, + iteration_count => IterationCount + }, {ok, State}. - update(Config, #{user_group := ID}) -> create(ID, Config). -authenticate(#{auth_method := AuthMethod, - auth_data := AuthData, - auth_cache := AuthCache}, State) -> +authenticate( + #{ + auth_method := AuthMethod, + auth_data := AuthData, + auth_cache := AuthCache + }, + State +) -> case ensure_auth_method(AuthMethod, State) of true -> case AuthCache of @@ -144,13 +163,22 @@ destroy(#{user_group := UserGroup}) -> MatchSpec = group_match_spec(UserGroup), trans( fun() -> - ok = lists:foreach(fun(UserInfo) -> - mnesia:delete_object(?TAB, UserInfo, write) - end, mnesia:select(?TAB, MatchSpec, write)) - end). + ok = lists:foreach( + fun(UserInfo) -> + mnesia:delete_object(?TAB, UserInfo, write) + end, + mnesia:select(?TAB, MatchSpec, write) + ) + end + ). -add_user(#{user_id := UserID, - password := Password} = UserInfo, #{user_group := UserGroup} = State) -> +add_user( + #{ + user_id := UserID, + password := Password + } = UserInfo, + #{user_group := UserGroup} = State +) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of @@ -161,7 +189,8 @@ add_user(#{user_id := UserID, [_] -> {error, already_exist} end - end). + end + ). delete_user(UserID, #{user_group := UserGroup}) -> trans( @@ -172,30 +201,42 @@ delete_user(UserID, #{user_group := UserGroup}) -> [_] -> mnesia:delete(?TAB, {UserGroup, UserID}, write) end - end). + end + ). -update_user(UserID, User, - #{user_group := UserGroup} = State) -> +update_user( + UserID, + User, + #{user_group := UserGroup} = State +) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> {error, not_found}; [#user_info{is_superuser = IsSuperuser} = UserInfo] -> - UserInfo1 = UserInfo#user_info{is_superuser = maps:get(is_superuser, User, IsSuperuser)}, - UserInfo2 = case maps:get(password, User, undefined) of - undefined -> - UserInfo1; - Password -> - {StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info(Password, State), - UserInfo1#user_info{stored_key = StoredKey, - server_key = ServerKey, - salt = Salt} - end, + UserInfo1 = UserInfo#user_info{ + is_superuser = maps:get(is_superuser, User, IsSuperuser) + }, + UserInfo2 = + case maps:get(password, User, undefined) of + undefined -> + UserInfo1; + Password -> + {StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info( + Password, State + ), + UserInfo1#user_info{ + stored_key = StoredKey, + server_key = ServerKey, + salt = Salt + } + end, mnesia:write(?TAB, UserInfo2, write), {ok, format_user_info(UserInfo2)} end - end). + end + ). lookup_user(UserID, #{user_group := UserGroup}) -> case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of @@ -214,14 +255,23 @@ list_users(QueryString, #{user_group := UserGroup}) -> query(Tab, {QString, []}, Continuation, Limit) -> Ms = ms_from_qstring(QString), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_user_info/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_user_info/1 + ); query(Tab, {QString, FuzzyQString}, Continuation, Limit) -> Ms = ms_from_qstring(QString), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_user_info/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_user_info/1 + ). %%-------------------------------------------------------------------- %% Match funcs @@ -229,14 +279,18 @@ query(Tab, {QString, FuzzyQString}, Continuation, Limit) -> %% Fuzzy username funcs fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> true; -run_fuzzy_filter( E = #user_info{user_id = {_, UserID}} - , [{username, like, UsernameSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = #user_info{user_id = {_, UserID}}, + [{username, like, UsernameSubStr} | Fuzzy] +) -> binary:match(UserID, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy). %%------------------------------------------------------------------------------ @@ -252,13 +306,17 @@ ensure_auth_method(_, _) -> check_client_first_message(Bin, _Cache, #{iteration_count := IterationCount} = State) -> RetrieveFun = fun(Username) -> - retrieve(Username, State) - end, - case esasl_scram:check_client_first_message( - Bin, - #{iteration_count => IterationCount, - retrieve => RetrieveFun} - ) of + retrieve(Username, State) + end, + case + esasl_scram:check_client_first_message( + Bin, + #{ + iteration_count => IterationCount, + retrieve => RetrieveFun + } + ) + of {continue, ServerFirstMessage, Cache} -> {continue, ServerFirstMessage, Cache}; ignore -> @@ -268,10 +326,12 @@ check_client_first_message(Bin, _Cache, #{iteration_count := IterationCount} = S end. check_client_final_message(Bin, #{is_superuser := IsSuperuser} = Cache, #{algorithm := Alg}) -> - case esasl_scram:check_client_final_message( - Bin, - Cache#{algorithm => Alg} - ) of + case + esasl_scram:check_client_final_message( + Bin, + Cache#{algorithm => Alg} + ) + of {ok, ServerFinalMessage} -> {ok, #{is_superuser => IsSuperuser}, ServerFinalMessage}; {error, _Reason} -> @@ -280,23 +340,31 @@ check_client_final_message(Bin, #{is_superuser := IsSuperuser} = Cache, #{algori add_user(UserGroup, UserID, Password, IsSuperuser, State) -> {StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info(Password, State), - UserInfo = #user_info{user_id = {UserGroup, UserID}, - stored_key = StoredKey, - server_key = ServerKey, - salt = Salt, - is_superuser = IsSuperuser}, + UserInfo = #user_info{ + user_id = {UserGroup, UserID}, + stored_key = StoredKey, + server_key = ServerKey, + salt = Salt, + is_superuser = IsSuperuser + }, mnesia:write(?TAB, UserInfo, write). retrieve(UserID, #{user_group := UserGroup}) -> case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of - [#user_info{stored_key = StoredKey, - server_key = ServerKey, - salt = Salt, - is_superuser = IsSuperuser}] -> - {ok, #{stored_key => StoredKey, - server_key => ServerKey, - salt => Salt, - is_superuser => IsSuperuser}}; + [ + #user_info{ + stored_key = StoredKey, + server_key = ServerKey, + salt = Salt, + is_superuser = IsSuperuser + } + ] -> + {ok, #{ + stored_key => StoredKey, + server_key => ServerKey, + salt => Salt, + is_superuser => IsSuperuser + }}; [] -> {error, not_found} end. @@ -315,15 +383,21 @@ format_user_info(#user_info{user_id = {_, UserID}, is_superuser = IsSuperuser}) #{user_id => UserID, is_superuser => IsSuperuser}. ms_from_qstring(QString) -> - [Ms] = lists:foldl(fun({user_group, '=:=', UserGroup}, AccIn) -> - [group_match_spec(UserGroup) | AccIn]; - (_, AccIn) -> - AccIn - end, [], QString), + [Ms] = lists:foldl( + fun + ({user_group, '=:=', UserGroup}, AccIn) -> + [group_match_spec(UserGroup) | AccIn]; + (_, AccIn) -> + AccIn + end, + [], + QString + ), Ms. group_match_spec(UserGroup) -> ets:fun2ms( - fun(#user_info{user_id = {Group, _}} = User) when Group =:= UserGroup -> - User - end). + fun(#user_info{user_id = {Group, _}} = User) when Group =:= UserGroup -> + User + end + ). diff --git a/apps/emqx_authn/src/proto/emqx_authn_proto_v1.erl b/apps/emqx_authn/src/proto/emqx_authn_proto_v1.erl index 1d93ded8e..4f23e0eb8 100644 --- a/apps/emqx_authn/src/proto/emqx_authn_proto_v1.erl +++ b/apps/emqx_authn/src/proto/emqx_authn_proto_v1.erl @@ -18,9 +18,10 @@ -behaviour(emqx_bpapi). --export([ introduced_in/0 - , lookup_from_all_nodes/3 - ]). +-export([ + introduced_in/0, + lookup_from_all_nodes/3 +]). -include_lib("emqx/include/bpapi.hrl"). @@ -30,6 +31,8 @@ introduced_in() -> "5.0.0". -spec lookup_from_all_nodes([node()], atom(), binary()) -> - emqx_rpc:erpc_multicall(). + emqx_rpc:erpc_multicall(). lookup_from_all_nodes(Nodes, ChainName, AuthenticatorID) -> - erpc:multicall(Nodes, emqx_authn_api, lookup_from_local_node, [ChainName, AuthenticatorID], ?TIMEOUT). + erpc:multicall( + Nodes, emqx_authn_api, lookup_from_local_node, [ChainName, AuthenticatorID], ?TIMEOUT + ). diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl index 94a295f65..31fff4940 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -24,18 +24,20 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - , validations/0 - ]). +-export([ + namespace/0, + roots/0, + fields/1, + validations/0 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -44,35 +46,47 @@ namespace() -> "authn-http". roots() -> - [ {?CONF_NS, - hoconsc:mk(hoconsc:union(refs()), - #{})} + [ + {?CONF_NS, + hoconsc:mk( + hoconsc:union(refs()), + #{} + )} ]. fields(get) -> - [ {method, #{type => get, default => post}} - , {headers, fun headers_no_content_type/1} + [ + {method, #{type => get, default => post}}, + {headers, fun headers_no_content_type/1} ] ++ common_fields(); - fields(post) -> - [ {method, #{type => post, default => post}} - , {headers, fun headers/1} + [ + {method, #{type => post, default => post}}, + {headers, fun headers/1} ] ++ common_fields(). common_fields() -> - [ {mechanism, emqx_authn_schema:mechanism('password_based')} - , {backend, emqx_authn_schema:backend(http)} - , {url, fun url/1} - , {body, map([{fuzzy, term(), binary()}])} - , {request_timeout, fun request_timeout/1} - ] ++ emqx_authn_schema:common_fields() - ++ maps:to_list(maps:without([ base_url - , pool_type], - maps:from_list(emqx_connector_http:fields(config)))). + [ + {mechanism, emqx_authn_schema:mechanism('password_based')}, + {backend, emqx_authn_schema:backend(http)}, + {url, fun url/1}, + {body, map([{fuzzy, term(), binary()}])}, + {request_timeout, fun request_timeout/1} + ] ++ emqx_authn_schema:common_fields() ++ + maps:to_list( + maps:without( + [ + base_url, + pool_type + ], + maps:from_list(emqx_connector_http:fields(config)) + ) + ). validations() -> - [ {check_ssl_opts, fun check_ssl_opts/1} - , {check_headers, fun check_headers/1} + [ + {check_ssl_opts, fun check_ssl_opts/1}, + {check_headers, fun check_headers/1} ]. url(type) -> binary(); @@ -80,21 +94,27 @@ url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; url(required) -> true; url(_) -> undefined. -headers(type) -> map(); +headers(type) -> + map(); headers(converter) -> fun(Headers) -> - maps:merge(default_headers(), transform_header_name(Headers)) + maps:merge(default_headers(), transform_header_name(Headers)) end; -headers(default) -> default_headers(); -headers(_) -> undefined. +headers(default) -> + default_headers(); +headers(_) -> + undefined. -headers_no_content_type(type) -> map(); +headers_no_content_type(type) -> + map(); headers_no_content_type(converter) -> fun(Headers) -> - maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) + maps:merge(default_headers_no_content_type(), transform_header_name(Headers)) end; -headers_no_content_type(default) -> default_headers_no_content_type(); -headers_no_content_type(_) -> undefined. +headers_no_content_type(default) -> + default_headers_no_content_type(); +headers_no_content_type(_) -> + undefined. request_timeout(type) -> emqx_schema:duration_ms(); request_timeout(default) -> <<"5s">>; @@ -105,36 +125,51 @@ request_timeout(_) -> undefined. %%------------------------------------------------------------------------------ refs() -> - [ hoconsc:ref(?MODULE, get) - , hoconsc:ref(?MODULE, post) + [ + hoconsc:ref(?MODULE, get), + hoconsc:ref(?MODULE, post) ]. create(_AuthenticatorID, Config) -> create(Config). -create(#{method := Method, - url := RawURL, - headers := Headers, - body := Body, - request_timeout := RequestTimeout} = Config) -> +create( + #{ + method := Method, + url := RawURL, + headers := Headers, + body := Body, + request_timeout := RequestTimeout + } = Config +) -> {BsaeUrlWithPath, Query} = parse_fullpath(RawURL), URIMap = parse_url(BsaeUrlWithPath), ResourceId = emqx_authn_utils:make_resource_id(?MODULE), - State = #{method => Method, - path => maps:get(path, URIMap), - base_query_template => emqx_authn_utils:parse_deep( - cow_qs:parse_qs(to_bin(Query))), - headers => maps:to_list(Headers), - body_template => emqx_authn_utils:parse_deep( - maps:to_list(Body)), - request_timeout => RequestTimeout, - resource_id => ResourceId}, - case emqx_resource:create_local(ResourceId, - ?RESOURCE_GROUP, - emqx_connector_http, - Config#{base_url => maps:remove(query, URIMap), - pool_type => random}, - #{}) of + State = #{ + method => Method, + path => maps:get(path, URIMap), + base_query_template => emqx_authn_utils:parse_deep( + cow_qs:parse_qs(to_bin(Query)) + ), + headers => maps:to_list(Headers), + body_template => emqx_authn_utils:parse_deep( + maps:to_list(Body) + ), + request_timeout => RequestTimeout, + resource_id => ResourceId + }, + case + emqx_resource:create_local( + ResourceId, + ?RESOURCE_GROUP, + emqx_connector_http, + Config#{ + base_url => maps:remove(query, URIMap), + pool_type => random + }, + #{} + ) + of {ok, already_created} -> {ok, State}; {ok, _} -> @@ -154,13 +189,20 @@ update(Config, State) -> authenticate(#{auth_method := _}, _) -> ignore; -authenticate(Credential, #{resource_id := ResourceId, - method := Method, - request_timeout := RequestTimeout} = State) -> +authenticate( + Credential, + #{ + resource_id := ResourceId, + method := Method, + request_timeout := RequestTimeout + } = State +) -> Request = generate_request(Credential, State), case emqx_resource:query(ResourceId, {Method, Request, RequestTimeout}) of - {ok, 204, _Headers} -> {ok, #{is_superuser => false}}; - {ok, 200, _Headers} -> {ok, #{is_superuser => false}}; + {ok, 204, _Headers} -> + {ok, #{is_superuser => false}}; + {ok, 200, _Headers} -> + {ok, #{is_superuser => false}}; {ok, 200, Headers, Body} -> ContentType = proplists:get_value(<<"content-type">>, Headers, <<"application/json">>), case safely_parse_body(ContentType, Body) of @@ -173,24 +215,32 @@ authenticate(Credential, #{resource_id := ResourceId, {ok, #{is_superuser => false}} end; {error, Reason} -> - ?SLOG(error, #{msg => "http_server_query_failed", - resource => ResourceId, - reason => Reason}), + ?SLOG(error, #{ + msg => "http_server_query_failed", + resource => ResourceId, + reason => Reason + }), ignore; Other -> Output = may_append_body(#{resource => ResourceId}, Other), case erlang:element(2, Other) of Code5xx when Code5xx >= 500 andalso Code5xx < 600 -> - ?SLOG(error, Output#{msg => "http_server_error", - code => Code5xx}), + ?SLOG(error, Output#{ + msg => "http_server_error", + code => Code5xx + }), ignore; Code4xx when Code4xx >= 400 andalso Code4xx < 500 -> - ?SLOG(warning, Output#{msg => "refused_by_http_server", - code => Code4xx}), + ?SLOG(warning, Output#{ + msg => "refused_by_http_server", + code => Code4xx + }), {error, not_authorized}; OtherCode -> - ?SLOG(error, Output#{msg => "undesired_response_code", - code => OtherCode}), + ?SLOG(error, Output#{ + msg => "undesired_response_code", + code => OtherCode + }), ignore end end. @@ -207,22 +257,29 @@ parse_fullpath(RawURL) -> cow_http:parse_fullpath(to_bin(RawURL)). default_headers() -> - maps:put(<<"content-type">>, - <<"application/json">>, - default_headers_no_content_type()). + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). default_headers_no_content_type() -> - #{ <<"accept">> => <<"application/json">> - , <<"cache-control">> => <<"no-cache">> - , <<"connection">> => <<"keep-alive">> - , <<"keep-alive">> => <<"timeout=30, max=1000">> - }. + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. transform_header_name(Headers) -> - maps:fold(fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, #{}, Headers). + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). check_ssl_opts(Conf) -> {BaseUrlWithPath, _Query} = parse_fullpath(get_conf_val("url", Conf)), @@ -250,11 +307,13 @@ parse_url(URL) -> URIMap end. -generate_request(Credential, #{method := Method, - path := Path, - base_query_template := BaseQueryTemplate, - headers := Headers, - body_template := BodyTemplate}) -> +generate_request(Credential, #{ + method := Method, + path := Path, + base_query_template := BaseQueryTemplate, + headers := Headers, + body_template := BodyTemplate +}) -> Body = emqx_authn_utils:render_deep(BodyTemplate, Credential), NBaseQuery = emqx_authn_utils:render_deep(BaseQueryTemplate, Credential), case Method of diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwks_connector.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwks_connector.erl index c8526c73c..b2b249b27 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwks_connector.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwks_connector.erl @@ -22,23 +22,25 @@ -include_lib("jose/include/jose_jwk.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). +-export([ + start_link/1, + stop/1 +]). --export([ start_link/1 - , stop/1 - ]). - --export([ get_jwks/1 - , update/2 - ]). +-export([ + get_jwks/1, + update/2 +]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). %%-------------------------------------------------------------------- %% APIs @@ -67,11 +69,9 @@ init([Opts]) -> handle_call(get_cached_jwks, _From, #{jwks := Jwks} = State) -> {reply, {ok, Jwks}, State}; - handle_call({update, Opts}, _From, _State) -> NewState = handle_options(Opts), {reply, ok, refresh_jwks(NewState)}; - handle_call(_Req, _From, State) -> {reply, ok, State}. @@ -80,47 +80,53 @@ handle_cast(_Msg, State) -> handle_info({refresh_jwks, _TRef, refresh}, #{request_id := RequestID} = State) -> case RequestID of - undefined -> ok; + undefined -> + ok; _ -> ok = httpc:cancel_request(RequestID), receive {http, _} -> ok after 0 -> - ok + ok end end, {noreply, refresh_jwks(State)}; - -handle_info({http, {RequestID, Result}}, - #{request_id := RequestID, endpoint := Endpoint} = State0) -> +handle_info( + {http, {RequestID, Result}}, + #{request_id := RequestID, endpoint := Endpoint} = State0 +) -> ?tp(debug, jwks_endpoint_response, #{request_id => RequestID}), State1 = State0#{request_id := undefined}, - NewState = case Result of - {error, Reason} -> - ?SLOG(warning, #{msg => "failed_to_request_jwks_endpoint", - endpoint => Endpoint, - reason => Reason}), - State1; - {StatusLine, Headers, Body} -> - try - JWKS = jose_jwk:from(emqx_json:decode(Body, [return_maps])), - {_, JWKs} = JWKS#jose_jwk.keys, - State1#{jwks := JWKs} - catch _:_ -> - ?SLOG(warning, #{msg => "invalid_jwks_returned", - endpoint => Endpoint, - status => StatusLine, - headers => Headers, - body => Body}), - State1 - end - end, + NewState = + case Result of + {error, Reason} -> + ?SLOG(warning, #{ + msg => "failed_to_request_jwks_endpoint", + endpoint => Endpoint, + reason => Reason + }), + State1; + {StatusLine, Headers, Body} -> + try + JWKS = jose_jwk:from(emqx_json:decode(Body, [return_maps])), + {_, JWKs} = JWKS#jose_jwk.keys, + State1#{jwks := JWKs} + catch + _:_ -> + ?SLOG(warning, #{ + msg => "invalid_jwks_returned", + endpoint => Endpoint, + status => StatusLine, + headers => Headers, + body => Body + }), + State1 + end + end, {noreply, NewState}; - handle_info({http, {_, _}}, State) -> %% ignore {noreply, State}; - handle_info(_Info, State) -> {noreply, State}. @@ -135,32 +141,50 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- -handle_options(#{endpoint := Endpoint, - refresh_interval := RefreshInterval0, - ssl_opts := SSLOpts}) -> - #{endpoint => Endpoint, - refresh_interval => limit_refresh_interval(RefreshInterval0), - ssl_opts => maps:to_list(SSLOpts), - jwks => [], - request_id => undefined}. +handle_options(#{ + endpoint := Endpoint, + refresh_interval := RefreshInterval0, + ssl_opts := SSLOpts +}) -> + #{ + endpoint => Endpoint, + refresh_interval => limit_refresh_interval(RefreshInterval0), + ssl_opts => maps:to_list(SSLOpts), + jwks => [], + request_id => undefined + }. -refresh_jwks(#{endpoint := Endpoint, - ssl_opts := SSLOpts} = State) -> - 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} -> - ?tp(warning, jwks_endpoint_request_fail, #{endpoint => Endpoint, - http_opts => HTTPOpts, - reason => Reason}), - State; - {ok, RequestID} -> - ?tp(debug, jwks_endpoint_request_ok, #{request_id => RequestID}), - State#{request_id := RequestID} - end, +refresh_jwks( + #{ + endpoint := Endpoint, + ssl_opts := SSLOpts + } = State +) -> + 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} -> + ?tp(warning, jwks_endpoint_request_fail, #{ + endpoint => Endpoint, + http_opts => HTTPOpts, + reason => Reason + }), + State; + {ok, RequestID} -> + ?tp(debug, jwks_endpoint_request_ok, #{request_id => RequestID}), + State#{request_id := RequestID} + end, ensure_expiry_timer(NState). ensure_expiry_timer(State = #{refresh_interval := Interval}) -> diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl index ecbe599d7..b12a7b041 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl @@ -23,17 +23,19 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -42,49 +44,56 @@ namespace() -> "authn-jwt". roots() -> - [ {?CONF_NS, - hoconsc:mk(hoconsc:union(refs()), - #{})} + [ + {?CONF_NS, + hoconsc:mk( + hoconsc:union(refs()), + #{} + )} ]. fields('hmac-based') -> - [ {use_jwks, {enum, [false]}} - , {algorithm, {enum, ['hmac-based']}} - , {secret, fun secret/1} - , {secret_base64_encoded, fun secret_base64_encoded/1} + [ + {use_jwks, {enum, [false]}}, + {algorithm, {enum, ['hmac-based']}}, + {secret, fun secret/1}, + {secret_base64_encoded, fun secret_base64_encoded/1} ] ++ common_fields(); - fields('public-key') -> - [ {use_jwks, {enum, [false]}} - , {algorithm, {enum, ['public-key']}} - , {certificate, fun certificate/1} + [ + {use_jwks, {enum, [false]}}, + {algorithm, {enum, ['public-key']}}, + {certificate, fun certificate/1} ] ++ common_fields(); - fields('jwks') -> - [ {use_jwks, {enum, [true]}} - , {endpoint, fun endpoint/1} - , {refresh_interval, fun refresh_interval/1} - , {ssl, #{type => hoconsc:union([ hoconsc:ref(?MODULE, ssl_enable) - , hoconsc:ref(?MODULE, ssl_disable) - ]), - default => #{<<"enable">> => false}}} + [ + {use_jwks, {enum, [true]}}, + {endpoint, fun endpoint/1}, + {refresh_interval, fun refresh_interval/1}, + {ssl, #{ + type => hoconsc:union([ + hoconsc:ref(?MODULE, ssl_enable), + hoconsc:ref(?MODULE, ssl_disable) + ]), + default => #{<<"enable">> => false} + }} ] ++ common_fields(); - fields(ssl_enable) -> - [ {enable, #{type => true}} - , {cacertfile, fun cacertfile/1} - , {certfile, fun certfile/1} - , {keyfile, fun keyfile/1} - , {verify, fun verify/1} - , {server_name_indication, fun server_name_indication/1} + [ + {enable, #{type => true}}, + {cacertfile, fun cacertfile/1}, + {certfile, fun certfile/1}, + {keyfile, fun keyfile/1}, + {verify, fun verify/1}, + {server_name_indication, fun server_name_indication/1} ]; - fields(ssl_disable) -> - [ {enable, #{type => false}} ]. + [{enable, #{type => false}}]. common_fields() -> - [ {mechanism, emqx_authn_schema:mechanism('jwt')} - , {verify_claims, fun verify_claims/1} + [ + {mechanism, emqx_authn_schema:mechanism('jwt')}, + {verify_claims, fun verify_claims/1} ] ++ emqx_authn_schema:common_fields(). secret(type) -> binary(); @@ -121,24 +130,29 @@ verify(_) -> undefined. server_name_indication(type) -> string(); server_name_indication(_) -> undefined. -verify_claims(type) -> list(); -verify_claims(default) -> #{}; -verify_claims(validator) -> [fun do_check_verify_claims/1]; +verify_claims(type) -> + list(); +verify_claims(default) -> + #{}; +verify_claims(validator) -> + [fun do_check_verify_claims/1]; verify_claims(converter) -> fun(VerifyClaims) -> [{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)] end; -verify_claims(_) -> undefined. +verify_claims(_) -> + undefined. %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ refs() -> - [ hoconsc:ref(?MODULE, 'hmac-based') - , hoconsc:ref(?MODULE, 'public-key') - , hoconsc:ref(?MODULE, 'jwks') - ]. + [ + hoconsc:ref(?MODULE, 'hmac-based'), + hoconsc:ref(?MODULE, 'public-key'), + hoconsc:ref(?MODULE, 'jwks') + ]. create(_AuthenticatorID, Config) -> create(Config). @@ -146,18 +160,22 @@ create(_AuthenticatorID, Config) -> create(#{verify_claims := VerifyClaims} = Config) -> create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}). -update(#{use_jwks := false} = Config, - #{jwk := Connector}) - when is_pid(Connector) -> +update( + #{use_jwks := false} = Config, + #{jwk := Connector} +) when + is_pid(Connector) +-> _ = emqx_authn_jwks_connector:stop(Connector), create(Config); - update(#{use_jwks := false} = Config, _State) -> create(Config); - -update(#{use_jwks := true} = Config, - #{jwk := Connector} = State) - when is_pid(Connector) -> +update( + #{use_jwks := true} = Config, + #{jwk := Connector} = State +) when + is_pid(Connector) +-> ok = emqx_authn_jwks_connector:update(Connector, connector_opts(Config)), case maps:get(verify_cliams, Config, undefined) of undefined -> @@ -165,21 +183,23 @@ update(#{use_jwks := true} = Config, VerifyClaims -> {ok, State#{verify_claims => handle_verify_claims(VerifyClaims)}} end; - update(#{use_jwks := true} = Config, _State) -> create(Config). authenticate(#{auth_method := _}, _) -> ignore; -authenticate(Credential = #{password := JWT}, #{jwk := JWK, - verify_claims := VerifyClaims0}) -> - JWKs = case erlang:is_pid(JWK) of - false -> - [JWK]; - true -> - {ok, JWKs0} = emqx_authn_jwks_connector:get_jwks(JWK), - JWKs0 - end, +authenticate(Credential = #{password := JWT}, #{ + jwk := JWK, + verify_claims := VerifyClaims0 +}) -> + JWKs = + case erlang:is_pid(JWK) of + false -> + [JWK]; + true -> + {ok, JWKs0} = emqx_authn_jwks_connector:get_jwks(JWK), + JWKs0 + end, VerifyClaims = replace_placeholder(VerifyClaims0, Credential), case verify(JWT, JWKs, VerifyClaims) of {ok, Extra} -> {ok, Extra}; @@ -197,41 +217,54 @@ destroy(_) -> %% Internal functions %%-------------------------------------------------------------------- -create2(#{use_jwks := false, - algorithm := 'hmac-based', - secret := Secret0, - secret_base64_encoded := Base64Encoded, - verify_claims := VerifyClaims}) -> +create2(#{ + use_jwks := false, + algorithm := 'hmac-based', + secret := Secret0, + secret_base64_encoded := Base64Encoded, + verify_claims := VerifyClaims +}) -> case may_decode_secret(Base64Encoded, Secret0) of {error, Reason} -> {error, Reason}; Secret -> JWK = jose_jwk:from_oct(Secret), - {ok, #{jwk => JWK, - verify_claims => VerifyClaims}} + {ok, #{ + jwk => JWK, + verify_claims => VerifyClaims + }} end; - -create2(#{use_jwks := false, - algorithm := 'public-key', - certificate := Certificate, - verify_claims := VerifyClaims}) -> +create2(#{ + use_jwks := false, + algorithm := 'public-key', + certificate := Certificate, + verify_claims := VerifyClaims +}) -> JWK = create_jwk_from_pem_or_file(Certificate), - {ok, #{jwk => JWK, - verify_claims => VerifyClaims}}; - -create2(#{use_jwks := true, - verify_claims := VerifyClaims} = Config) -> + {ok, #{ + jwk => JWK, + verify_claims => VerifyClaims + }}; +create2( + #{ + use_jwks := true, + verify_claims := VerifyClaims + } = Config +) -> case emqx_authn_jwks_connector:start_link(connector_opts(Config)) of {ok, Connector} -> - {ok, #{jwk => Connector, - verify_claims => VerifyClaims}}; + {ok, #{ + jwk => Connector, + verify_claims => VerifyClaims + }}; {error, Reason} -> {error, Reason} end. -create_jwk_from_pem_or_file(CertfileOrFilePath) - when is_binary(CertfileOrFilePath); - is_list(CertfileOrFilePath) -> +create_jwk_from_pem_or_file(CertfileOrFilePath) when + is_binary(CertfileOrFilePath); + is_list(CertfileOrFilePath) +-> case filelib:is_file(CertfileOrFilePath) of true -> jose_jwk:from_pem_file(CertfileOrFilePath); @@ -240,18 +273,20 @@ create_jwk_from_pem_or_file(CertfileOrFilePath) end. connector_opts(#{ssl := #{enable := Enable} = SSL} = Config) -> - SSLOpts = case Enable of - true -> maps:without([enable], SSL); - false -> #{} - end, + SSLOpts = + case Enable of + true -> maps:without([enable], SSL); + false -> #{} + end, Config#{ssl_opts => SSLOpts}. - -may_decode_secret(false, Secret) -> Secret; +may_decode_secret(false, Secret) -> + Secret; may_decode_secret(true, Secret) -> - try base64:decode(Secret) + try + base64:decode(Secret) catch - error : _ -> + error:_ -> {error, {invalid_parameter, secret}} end. @@ -288,15 +323,18 @@ verify(JWS, [JWK | More], VerifyClaims) -> verify_claims(Claims, VerifyClaims0) -> Now = os:system_time(seconds), - VerifyClaims = [{<<"exp">>, fun(ExpireTime) -> - Now < ExpireTime - end}, - {<<"iat">>, fun(IssueAt) -> - IssueAt =< Now - end}, - {<<"nbf">>, fun(NotBefore) -> - NotBefore =< Now - end}] ++ VerifyClaims0, + VerifyClaims = + [ + {<<"exp">>, fun(ExpireTime) -> + Now < ExpireTime + end}, + {<<"iat">>, fun(IssueAt) -> + IssueAt =< Now + end}, + {<<"nbf">>, fun(NotBefore) -> + NotBefore =< Now + end} + ] ++ VerifyClaims0, do_verify_claims(Claims, VerifyClaims). do_verify_claims(_Claims, []) -> @@ -327,8 +365,8 @@ do_check_verify_claims([]) -> true; do_check_verify_claims([{Name, Expected} | More]) -> check_claim_name(Name) andalso - check_claim_expected(Expected) andalso - do_check_verify_claims(More). + check_claim_expected(Expected) andalso + do_check_verify_claims(More). check_claim_name(exp) -> false; diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl index 9036147ef..b4aeb6759 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl @@ -23,40 +23,45 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). --export([ import_users/2 - , add_user/2 - , delete_user/2 - , update_user/3 - , lookup_user/2 - , list_users/2 - ]). +-export([ + import_users/2, + add_user/2, + delete_user/2, + update_user/3, + lookup_user/2, + list_users/2 +]). --export([ query/4 - , format_user_info/1 - , group_match_spec/1]). +-export([ + query/4, + format_user_info/1, + group_match_spec/1 +]). -type user_id_type() :: clientid | username. -type user_group() :: binary(). -type user_id() :: binary(). --record(user_info, - { user_id :: {user_group(), user_id()} - , password_hash :: binary() - , salt :: binary() - , is_superuser :: boolean() - }). +-record(user_info, { + user_id :: {user_group(), user_id()}, + password_hash :: binary(), + salt :: binary(), + is_superuser :: boolean() +}). -reflect_type([user_id_type/0]). @@ -65,9 +70,11 @@ -boot_mnesia({mnesia, [boot]}). -define(TAB, ?MODULE). --define(AUTHN_QSCHEMA, [ {<<"like_username">>, binary} - , {<<"like_clientid">>, binary} - , {<<"user_group">>, binary}]). +-define(AUTHN_QSCHEMA, [ + {<<"like_username">>, binary}, + {<<"like_clientid">>, binary}, + {<<"user_group">>, binary} +]). -define(QUERY_FUN, {?MODULE, query}). %%------------------------------------------------------------------------------ @@ -75,14 +82,15 @@ %%------------------------------------------------------------------------------ %% @doc Create or replicate tables. --spec(mnesia(boot | copy) -> ok). +-spec mnesia(boot | copy) -> ok. mnesia(boot) -> ok = mria:create_table(?TAB, [ - {rlog_shard, ?AUTH_SHARD}, - {storage, disc_copies}, - {record_name, user_info}, - {attributes, record_info(fields, user_info)}, - {storage_properties, [{ets, [{read_concurrency, true}]}]}]). + {rlog_shard, ?AUTH_SHARD}, + {storage, disc_copies}, + {record_name, user_info}, + {attributes, record_info(fields, user_info)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]} + ]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -93,10 +101,11 @@ namespace() -> "authn-builtin_db". roots() -> [?CONF_NS]. fields(?CONF_NS) -> - [ {mechanism, emqx_authn_schema:mechanism('password_based')} - , {backend, emqx_authn_schema:backend('built_in_database')} - , {user_id_type, fun user_id_type/1} - , {password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1} + [ + {mechanism, emqx_authn_schema:mechanism('password_based')}, + {backend, emqx_authn_schema:backend('built_in_database')}, + {user_id_type, fun user_id_type/1}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1} ] ++ emqx_authn_schema:common_fields(). user_id_type(type) -> user_id_type(); @@ -108,15 +117,21 @@ user_id_type(_) -> undefined. %%------------------------------------------------------------------------------ refs() -> - [hoconsc:ref(?MODULE, ?CONF_NS)]. + [hoconsc:ref(?MODULE, ?CONF_NS)]. -create(AuthenticatorID, - #{user_id_type := Type, - password_hash_algorithm := Algorithm}) -> +create( + AuthenticatorID, + #{ + user_id_type := Type, + password_hash_algorithm := Algorithm + } +) -> ok = emqx_authn_password_hashing:init(Algorithm), - State = #{user_group => AuthenticatorID, - user_id_type => Type, - password_hash_algorithm => Algorithm}, + State = #{ + user_group => AuthenticatorID, + user_id_type => Type, + password_hash_algorithm => Algorithm + }, {ok, State}. update(Config, #{user_group := ID}) -> @@ -124,17 +139,24 @@ update(Config, #{user_group := ID}) -> authenticate(#{auth_method := _}, _) -> ignore; -authenticate(#{password := Password} = Credential, - #{user_group := UserGroup, - user_id_type := Type, - password_hash_algorithm := Algorithm}) -> +authenticate( + #{password := Password} = Credential, + #{ + user_group := UserGroup, + user_id_type := Type, + password_hash_algorithm := Algorithm + } +) -> UserID = get_user_identity(Credential, Type), case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of [] -> ignore; [#user_info{password_hash = PasswordHash, salt = Salt, is_superuser = IsSuperuser}] -> - case emqx_authn_password_hashing:check_password( - Algorithm, Salt, PasswordHash, Password) of + case + emqx_authn_password_hashing:check_password( + Algorithm, Salt, PasswordHash, Password + ) + of true -> {ok, #{is_superuser => IsSuperuser}}; false -> {error, bad_username_or_password} end @@ -142,14 +164,15 @@ authenticate(#{password := Password} = Credential, destroy(#{user_group := UserGroup}) -> trans( - fun() -> - ok = lists:foreach( - fun(User) -> - mnesia:delete_object(?TAB, User, write) - end, - mnesia:select(?TAB, group_match_spec(UserGroup), write)) - end). - + fun() -> + ok = lists:foreach( + fun(User) -> + mnesia:delete_object(?TAB, User, write) + end, + mnesia:select(?TAB, group_match_spec(UserGroup), write) + ) + end + ). import_users(Filename0, State) -> Filename = to_binary(Filename0), @@ -164,10 +187,16 @@ import_users(Filename0, State) -> {error, {unsupported_file_format, Extension}} end. -add_user(#{user_id := UserID, - password := Password} = UserInfo, - #{user_group := UserGroup, - password_hash_algorithm := Algorithm}) -> +add_user( + #{ + user_id := UserID, + password := Password + } = UserInfo, + #{ + user_group := UserGroup, + password_hash_algorithm := Algorithm + } +) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of @@ -179,7 +208,8 @@ add_user(#{user_id := UserID, [_] -> {error, already_exist} end - end). + end + ). delete_user(UserID, #{user_group := UserGroup}) -> trans( @@ -190,31 +220,44 @@ delete_user(UserID, #{user_group := UserGroup}) -> [_] -> mnesia:delete(?TAB, {UserGroup, UserID}, write) end - end). + end + ). -update_user(UserID, UserInfo, - #{user_group := UserGroup, - password_hash_algorithm := Algorithm}) -> +update_user( + UserID, + UserInfo, + #{ + user_group := UserGroup, + password_hash_algorithm := Algorithm + } +) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of [] -> {error, not_found}; - [#user_info{ password_hash = PasswordHash - , salt = Salt - , is_superuser = IsSuperuser}] -> + [ + #user_info{ + password_hash = PasswordHash, + salt = Salt, + is_superuser = IsSuperuser + } + ] -> NSuperuser = maps:get(is_superuser, UserInfo, IsSuperuser), - {NPasswordHash, NSalt} = case UserInfo of - #{password := Password} -> - emqx_authn_password_hashing:hash( - Algorithm, Password); - #{} -> - {PasswordHash, Salt} - end, + {NPasswordHash, NSalt} = + case UserInfo of + #{password := Password} -> + emqx_authn_password_hashing:hash( + Algorithm, Password + ); + #{} -> + {PasswordHash, Salt} + end, insert_user(UserGroup, UserID, NPasswordHash, NSalt, NSuperuser), {ok, #{user_id => UserID, is_superuser => NSuperuser}} end - end). + end + ). lookup_user(UserID, #{user_group := UserGroup}) -> case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of @@ -233,14 +276,23 @@ list_users(QueryString, #{user_group := UserGroup}) -> query(Tab, {QString, []}, Continuation, Limit) -> Ms = ms_from_qstring(QString), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_user_info/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_user_info/1 + ); query(Tab, {QString, FuzzyQString}, Continuation, Limit) -> Ms = ms_from_qstring(QString), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_user_info/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_user_info/1 + ). %%-------------------------------------------------------------------- %% Match funcs @@ -248,17 +300,23 @@ query(Tab, {QString, FuzzyQString}, Continuation, Limit) -> %% Fuzzy username funcs fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> true; -run_fuzzy_filter( E = #user_info{user_id = {_, UserID}} - , [{username, like, UsernameSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = #user_info{user_id = {_, UserID}}, + [{username, like, UsernameSubStr} | Fuzzy] +) -> binary:match(UserID, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy); -run_fuzzy_filter( E = #user_info{user_id = {_, UserID}} - , [{clientid, like, ClientIDSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = #user_info{user_id = {_, UserID}}, + [{clientid, like, ClientIDSubStr} | Fuzzy] +) -> binary:match(UserID, ClientIDSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy). %%------------------------------------------------------------------------------ @@ -297,9 +355,15 @@ import_users_from_csv(Filename, #{user_group := UserGroup}) -> import(_UserGroup, []) -> ok; -import(UserGroup, [#{<<"user_id">> := UserID, - <<"password_hash">> := PasswordHash} = UserInfo | More]) - when is_binary(UserID) andalso is_binary(PasswordHash) -> +import(UserGroup, [ + #{ + <<"user_id">> := UserID, + <<"password_hash">> := PasswordHash + } = UserInfo + | More +]) when + is_binary(UserID) andalso is_binary(PasswordHash) +-> Salt = maps:get(<<"salt">>, UserInfo, <<>>), IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false), insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser), @@ -313,8 +377,11 @@ import(UserGroup, File, Seq) -> {ok, Line} -> Fields = binary:split(Line, [<<",">>, <<" ">>, <<"\n">>], [global, trim_all]), case get_user_info_by_seq(Fields, Seq) of - {ok, #{user_id := UserID, - password_hash := PasswordHash} = UserInfo} -> + {ok, + #{ + user_id := UserID, + password_hash := PasswordHash + } = UserInfo} -> Salt = maps:get(salt, UserInfo, <<>>), IsSuperuser = maps:get(is_superuser, UserInfo, false), insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser), @@ -360,10 +427,12 @@ get_user_info_by_seq(_, _, _) -> {error, bad_format}. insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser) -> - UserInfo = #user_info{user_id = {UserGroup, UserID}, - password_hash = PasswordHash, - salt = Salt, - is_superuser = IsSuperuser}, + UserInfo = #user_info{ + user_id = {UserGroup, UserID}, + password_hash = PasswordHash, + salt = Salt, + is_superuser = IsSuperuser + }, mnesia:write(?TAB, UserInfo, write). %% TODO: Support other type @@ -392,15 +461,21 @@ format_user_info(#user_info{user_id = {_, UserID}, is_superuser = IsSuperuser}) #{user_id => UserID, is_superuser => IsSuperuser}. ms_from_qstring(QString) -> - [Ms] = lists:foldl(fun({user_group, '=:=', UserGroup}, AccIn) -> - [group_match_spec(UserGroup) | AccIn]; - (_, AccIn) -> - AccIn - end, [], QString), + [Ms] = lists:foldl( + fun + ({user_group, '=:=', UserGroup}, AccIn) -> + [group_match_spec(UserGroup) | AccIn]; + (_, AccIn) -> + AccIn + end, + [], + QString + ), Ms. group_match_spec(UserGroup) -> ets:fun2ms( - fun(#user_info{user_id = {Group, _}} = User) when Group =:= UserGroup -> - User - end). + fun(#user_info{user_id = {Group, _}} = User) when Group =:= UserGroup -> + User + end + ). diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl index 43ed029da..c177316db 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl @@ -23,18 +23,20 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - , desc/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1, + desc/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -43,16 +45,18 @@ namespace() -> "authn-mongodb". roots() -> - [ {?CONF_NS, hoconsc:mk(hoconsc:union(refs()), - #{})} + [ + {?CONF_NS, + hoconsc:mk( + hoconsc:union(refs()), + #{} + )} ]. fields(standalone) -> common_fields() ++ emqx_connector_mongo:fields(single); - fields('replica-set') -> common_fields() ++ emqx_connector_mongo:fields(rs); - fields('sharded-cluster') -> common_fields() ++ emqx_connector_mongo:fields(sharded). @@ -66,26 +70,30 @@ desc(_) -> undefined. common_fields() -> - [ {mechanism, emqx_authn_schema:mechanism('password_based')} - , {backend, emqx_authn_schema:backend(mongodb)} - , {collection, fun collection/1} - , {selector, fun selector/1} - , {password_hash_field, fun password_hash_field/1} - , {salt_field, fun salt_field/1} - , {is_superuser_field, fun is_superuser_field/1} - , {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} + [ + {mechanism, emqx_authn_schema:mechanism('password_based')}, + {backend, emqx_authn_schema:backend(mongodb)}, + {collection, fun collection/1}, + {selector, fun selector/1}, + {password_hash_field, fun password_hash_field/1}, + {salt_field, fun salt_field/1}, + {is_superuser_field, fun is_superuser_field/1}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} ] ++ emqx_authn_schema:common_fields(). collection(type) -> binary(); collection(desc) -> "Collection used to store authentication data."; collection(_) -> undefined. -selector(type) -> map(); -selector(desc) -> "Statement that is executed during the authentication process. " - "Commands can support following wildcards:\n" - " - `${username}`: substituted with client's username\n" - " - `${clientid}`: substituted with the clientid"; -selector(_) -> undefined. +selector(type) -> + map(); +selector(desc) -> + "Statement that is executed during the authentication process. " + "Commands can support following wildcards:\n" + " - `${username}`: substituted with client's username\n" + " - `${clientid}`: substituted with the clientid"; +selector(_) -> + undefined. password_hash_field(type) -> binary(); password_hash_field(desc) -> "Document field that contains password hash."; @@ -106,9 +114,10 @@ is_superuser_field(_) -> undefined. %%------------------------------------------------------------------------------ refs() -> - [ hoconsc:ref(?MODULE, standalone) - , hoconsc:ref(?MODULE, 'replica-set') - , hoconsc:ref(?MODULE, 'sharded-cluster') + [ + hoconsc:ref(?MODULE, standalone), + hoconsc:ref(?MODULE, 'replica-set'), + hoconsc:ref(?MODULE, 'sharded-cluster') ]. create(_AuthenticatorID, Config) -> @@ -117,24 +126,32 @@ create(_AuthenticatorID, Config) -> create(#{selector := Selector} = Config) -> SelectorTemplate = emqx_authn_utils:parse_deep(Selector), State = maps:with( - [collection, - password_hash_field, - salt_field, - is_superuser_field, - password_hash_algorithm, - salt_position], - Config), + [ + collection, + password_hash_field, + salt_field, + is_superuser_field, + password_hash_algorithm, + salt_position + ], + Config + ), #{password_hash_algorithm := Algorithm} = State, ok = emqx_authn_password_hashing:init(Algorithm), ResourceId = emqx_authn_utils:make_resource_id(?MODULE), NState = State#{ - selector_template => SelectorTemplate, - resource_id => ResourceId}, - case emqx_resource:create_local(ResourceId, - ?RESOURCE_GROUP, - emqx_connector_mongo, - Config, - #{}) of + selector_template => SelectorTemplate, + resource_id => ResourceId + }, + case + emqx_resource:create_local( + ResourceId, + ?RESOURCE_GROUP, + emqx_connector_mongo, + Config, + #{} + ) + of {ok, already_created} -> {ok, NState}; {ok, _} -> @@ -154,30 +171,39 @@ update(Config, State) -> authenticate(#{auth_method := _}, _) -> ignore; -authenticate(#{password := Password} = Credential, - #{collection := Collection, - selector_template := SelectorTemplate, - resource_id := ResourceId} = State) -> +authenticate( + #{password := Password} = Credential, + #{ + collection := Collection, + selector_template := SelectorTemplate, + resource_id := ResourceId + } = State +) -> Selector = emqx_authn_utils:render_deep(SelectorTemplate, Credential), case emqx_resource:query(ResourceId, {find_one, Collection, Selector, #{}}) of - undefined -> ignore; + undefined -> + ignore; {error, Reason} -> - ?SLOG(error, #{msg => "mongodb_query_failed", - resource => ResourceId, - collection => Collection, - selector => Selector, - reason => Reason}), + ?SLOG(error, #{ + msg => "mongodb_query_failed", + resource => ResourceId, + collection => Collection, + selector => Selector, + reason => Reason + }), ignore; Doc -> case check_password(Password, Doc, State) of ok -> {ok, is_superuser(Doc, State)}; {error, {cannot_find_password_hash_field, PasswordHashField}} -> - ?SLOG(error, #{msg => "cannot_find_password_hash_field", - resource => ResourceId, - collection => Collection, - selector => Selector, - password_hash_field => PasswordHashField}), + ?SLOG(error, #{ + msg => "cannot_find_password_hash_field", + resource => ResourceId, + collection => Collection, + selector => Selector, + password_hash_field => PasswordHashField + }), ignore; {error, Reason} -> {error, Reason} @@ -194,18 +220,23 @@ destroy(#{resource_id := ResourceId}) -> check_password(undefined, _Selected, _State) -> {error, bad_username_or_password}; -check_password(Password, - Doc, - #{password_hash_algorithm := Algorithm, - password_hash_field := PasswordHashField} = State) -> +check_password( + Password, + Doc, + #{ + password_hash_algorithm := Algorithm, + password_hash_field := PasswordHashField + } = State +) -> case maps:get(PasswordHashField, Doc, undefined) of undefined -> {error, {cannot_find_password_hash_field, PasswordHashField}}; Hash -> - Salt = case maps:get(salt_field, State, undefined) of - undefined -> <<>>; - SaltField -> maps:get(SaltField, Doc, <<>>) - end, + Salt = + case maps:get(salt_field, State, undefined) of + undefined -> <<>>; + SaltField -> maps:get(SaltField, Doc, <<>>) + end, case emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password) of true -> ok; false -> {error, bad_username_or_password} diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl index 4c677719e..f848e661c 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl @@ -23,17 +23,19 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -44,13 +46,14 @@ namespace() -> "authn-mysql". roots() -> [?CONF_NS]. fields(?CONF_NS) -> - [ {mechanism, emqx_authn_schema:mechanism('password_based')} - , {backend, emqx_authn_schema:backend(mysql)} - , {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} - , {query, fun query/1} - , {query_timeout, fun query_timeout/1} - ] ++ emqx_authn_schema:common_fields() - ++ emqx_connector_mysql:fields(config). + [ + {mechanism, emqx_authn_schema:mechanism('password_based')}, + {backend, emqx_authn_schema:backend(mysql)}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1}, + {query, fun query/1}, + {query_timeout, fun query_timeout/1} + ] ++ emqx_authn_schema:common_fields() ++ + emqx_connector_mysql:fields(config). query(type) -> string(); query(_) -> undefined. @@ -64,28 +67,37 @@ query_timeout(_) -> undefined. %%------------------------------------------------------------------------------ refs() -> - [hoconsc:ref(?MODULE, ?CONF_NS)]. + [hoconsc:ref(?MODULE, ?CONF_NS)]. create(_AuthenticatorID, Config) -> create(Config). -create(#{password_hash_algorithm := Algorithm, - query := Query0, - query_timeout := QueryTimeout - } = Config) -> +create( + #{ + password_hash_algorithm := Algorithm, + query := Query0, + query_timeout := QueryTimeout + } = Config +) -> ok = emqx_authn_password_hashing:init(Algorithm), {Query, PlaceHolders} = emqx_authn_utils:parse_sql(Query0, '?'), ResourceId = emqx_authn_utils:make_resource_id(?MODULE), - State = #{password_hash_algorithm => Algorithm, - query => Query, - placeholders => PlaceHolders, - query_timeout => QueryTimeout, - resource_id => ResourceId}, - case emqx_resource:create_local(ResourceId, - ?RESOURCE_GROUP, - emqx_connector_mysql, - Config, - #{}) of + State = #{ + password_hash_algorithm => Algorithm, + query => Query, + placeholders => PlaceHolders, + query_timeout => QueryTimeout, + resource_id => ResourceId + }, + case + emqx_resource:create_local( + ResourceId, + ?RESOURCE_GROUP, + emqx_connector_mysql, + Config, + #{} + ) + of {ok, already_created} -> {ok, State}; {ok, _} -> @@ -105,31 +117,41 @@ update(Config, State) -> authenticate(#{auth_method := _}, _) -> ignore; -authenticate(#{password := Password} = Credential, - #{placeholders := PlaceHolders, - query := Query, - query_timeout := Timeout, - resource_id := ResourceId, - password_hash_algorithm := Algorithm}) -> +authenticate( + #{password := Password} = Credential, + #{ + placeholders := PlaceHolders, + query := Query, + query_timeout := Timeout, + resource_id := ResourceId, + password_hash_algorithm := Algorithm + } +) -> Params = emqx_authn_utils:render_sql_params(PlaceHolders, Credential), case emqx_resource:query(ResourceId, {sql, Query, Params, Timeout}) of - {ok, _Columns, []} -> ignore; + {ok, _Columns, []} -> + ignore; {ok, Columns, [Row | _]} -> Selected = maps:from_list(lists:zip(Columns, Row)), - case emqx_authn_utils:check_password_from_selected_map( - Algorithm, Selected, Password) of + 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, #{msg => "mysql_query_failed", - resource => ResourceId, - query => Query, - params => Params, - timeout => Timeout, - reason => Reason}), + ?SLOG(error, #{ + msg => "mysql_query_failed", + resource => ResourceId, + query => Query, + params => Params, + timeout => Timeout, + reason => Reason + }), ignore end. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl index 74794c10e..d795a4b64 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl @@ -24,17 +24,19 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). -ifdef(TEST). -compile(export_all). @@ -50,13 +52,14 @@ namespace() -> "authn-postgresql". roots() -> [?CONF_NS]. fields(?CONF_NS) -> - [ {mechanism, emqx_authn_schema:mechanism('password_based')} - , {backend, emqx_authn_schema:backend(postgresql)} - , {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} - , {query, fun query/1} + [ + {mechanism, emqx_authn_schema:mechanism('password_based')}, + {backend, emqx_authn_schema:backend(postgresql)}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1}, + {query, fun query/1} ] ++ - emqx_authn_schema:common_fields() ++ - proplists:delete(named_queries, emqx_connector_pgsql:fields(config)). + emqx_authn_schema:common_fields() ++ + proplists:delete(named_queries, emqx_connector_pgsql:fields(config)). query(type) -> string(); query(_) -> undefined. @@ -71,17 +74,29 @@ refs() -> create(_AuthenticatorID, Config) -> create(Config). -create(#{query := Query0, - password_hash_algorithm := Algorithm} = Config) -> +create( + #{ + query := Query0, + password_hash_algorithm := Algorithm + } = Config +) -> ok = emqx_authn_password_hashing:init(Algorithm), {Query, PlaceHolders} = emqx_authn_utils:parse_sql(Query0, '$n'), ResourceId = emqx_authn_utils:make_resource_id(?MODULE), - State = #{placeholders => PlaceHolders, - password_hash_algorithm => Algorithm, - resource_id => ResourceId}, - case emqx_resource:create_local(ResourceId, ?RESOURCE_GROUP, emqx_connector_pgsql, - Config#{named_queries => #{ResourceId => Query}}, - #{}) of + State = #{ + placeholders => PlaceHolders, + password_hash_algorithm => Algorithm, + resource_id => ResourceId + }, + case + emqx_resource:create_local( + ResourceId, + ?RESOURCE_GROUP, + emqx_connector_pgsql, + Config#{named_queries => #{ResourceId => Query}}, + #{} + ) + of {ok, already_created} -> {ok, State}; {ok, _} -> @@ -101,28 +116,38 @@ update(Config, State) -> authenticate(#{auth_method := _}, _) -> ignore; -authenticate(#{password := Password} = Credential, - #{placeholders := PlaceHolders, - resource_id := ResourceId, - password_hash_algorithm := Algorithm}) -> +authenticate( + #{password := Password} = Credential, + #{ + placeholders := PlaceHolders, + resource_id := ResourceId, + password_hash_algorithm := Algorithm + } +) -> Params = emqx_authn_utils:render_sql_params(PlaceHolders, Credential), case emqx_resource:query(ResourceId, {prepared_query, ResourceId, Params}) of - {ok, _Columns, []} -> ignore; + {ok, _Columns, []} -> + ignore; {ok, Columns, [Row | _]} -> NColumns = [Name || #column{name = Name} <- Columns], Selected = maps:from_list(lists:zip(NColumns, erlang:tuple_to_list(Row))), - case emqx_authn_utils:check_password_from_selected_map( - Algorithm, Selected, Password) of + 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, #{msg => "postgresql_query_failed", - resource => ResourceId, - params => Params, - reason => Reason}), + ?SLOG(error, #{ + msg => "postgresql_query_failed", + resource => ResourceId, + params => Params, + reason => Reason + }), ignore end. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl index df95922d0..1a71fafe6 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl @@ -23,17 +23,19 @@ -behaviour(hocon_schema). -behaviour(emqx_authentication). --export([ namespace/0 - , roots/0 - , fields/1 - ]). +-export([ + namespace/0, + roots/0, + fields/1 +]). --export([ refs/0 - , create/2 - , update/2 - , authenticate/2 - , destroy/1 - ]). +-export([ + refs/0, + create/2, + update/2, + authenticate/2, + destroy/1 +]). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -42,24 +44,27 @@ namespace() -> "authn-redis". roots() -> - [ {?CONF_NS, hoconsc:mk(hoconsc:union(refs()), - #{})} + [ + {?CONF_NS, + hoconsc:mk( + hoconsc:union(refs()), + #{} + )} ]. fields(standalone) -> common_fields() ++ emqx_connector_redis:fields(single); - fields(cluster) -> common_fields() ++ emqx_connector_redis:fields(cluster); - fields(sentinel) -> common_fields() ++ emqx_connector_redis:fields(sentinel). common_fields() -> - [ {mechanism, emqx_authn_schema:mechanism('password_based')} - , {backend, emqx_authn_schema:backend(redis)} - , {cmd, fun cmd/1} - , {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} + [ + {mechanism, emqx_authn_schema:mechanism('password_based')}, + {backend, emqx_authn_schema:backend(redis)}, + {cmd, fun cmd/1}, + {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} ] ++ emqx_authn_schema:common_fields(). cmd(type) -> string(); @@ -70,30 +75,43 @@ cmd(_) -> undefined. %%------------------------------------------------------------------------------ refs() -> - [ hoconsc:ref(?MODULE, standalone) - , hoconsc:ref(?MODULE, cluster) - , hoconsc:ref(?MODULE, sentinel) + [ + hoconsc:ref(?MODULE, standalone), + hoconsc:ref(?MODULE, cluster), + hoconsc:ref(?MODULE, sentinel) ]. create(_AuthenticatorID, Config) -> create(Config). -create(#{cmd := Cmd, - password_hash_algorithm := Algorithm} = Config) -> +create( + #{ + cmd := Cmd, + password_hash_algorithm := Algorithm + } = Config +) -> ok = emqx_authn_password_hashing:init(Algorithm), try NCmd = parse_cmd(Cmd), ok = emqx_authn_utils:ensure_apps_started(Algorithm), State = maps:with( - [password_hash_algorithm, salt_position], - Config), + [password_hash_algorithm, salt_position], + Config + ), ResourceId = emqx_authn_utils:make_resource_id(?MODULE), NState = State#{ - cmd => NCmd, - resource_id => ResourceId}, - case emqx_resource:create_local(ResourceId, ?RESOURCE_GROUP, - emqx_connector_redis, Config, - #{}) of + cmd => NCmd, + resource_id => ResourceId + }, + case + emqx_resource:create_local( + ResourceId, + ?RESOURCE_GROUP, + emqx_connector_redis, + Config, + #{} + ) + of {ok, already_created} -> {ok, NState}; {ok, _} -> @@ -121,38 +139,50 @@ update(Config, State) -> authenticate(#{auth_method := _}, _) -> ignore; -authenticate(#{password := Password} = Credential, - #{cmd := {Command, KeyTemplate, Fields}, - resource_id := ResourceId, - password_hash_algorithm := Algorithm}) -> +authenticate( + #{password := Password} = Credential, + #{ + cmd := {Command, KeyTemplate, Fields}, + resource_id := ResourceId, + password_hash_algorithm := Algorithm + } +) -> NKey = emqx_authn_utils:render_str(KeyTemplate, Credential), case emqx_resource:query(ResourceId, {cmd, [Command, NKey | Fields]}) of - {ok, []} -> ignore; + {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 + 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}), + ?SLOG(error, #{ + msg => "cannot_find_password_hash_field", + cmd => Command, + keys => NKey, + fields => Fields, + resource => ResourceId + }), ignore end; {error, Reason} -> - ?SLOG(error, #{msg => "redis_query_failed", - resource => ResourceId, - cmd => Command, - keys => NKey, - fields => Fields, - reason => Reason}), + ?SLOG(error, #{ + msg => "redis_query_failed", + resource => ResourceId, + cmd => Command, + keys => NKey, + fields => Fields, + reason => Reason + }), ignore end. @@ -191,5 +221,8 @@ merge(Fields, Value) when not is_list(Value) -> merge(Fields, [Value]); merge(Fields, Values) -> maps:from_list( - [{list_to_binary(K), V} - || {K, V} <- lists:zip(Fields, Values), V =/= undefined]). + [ + {list_to_binary(K), V} + || {K, V} <- lists:zip(Fields, Values), V =/= undefined + ] + ).