From a5a596e3ac80d6bcd1d2edc69f5edccc34d7e4dc Mon Sep 17 00:00:00 2001 From: zhouzb Date: Sat, 17 Jul 2021 09:53:22 +0800 Subject: [PATCH 1/7] fix(schema): fix config schema for authn --- .../emqx_enhanced_authn_scram_mnesia.erl | 2 +- .../src/simple_authn/emqx_authn_mysql.erl | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 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 d1d564bf3..14b25558a 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 @@ -80,7 +80,7 @@ server_type(type) -> hoconsc:enum(['built-in-database']); server_type(default) -> 'built-in-database'; server_type(_) -> undefined. -algorithm(type) -> hoconsc:enum([sha256, sha256]); +algorithm(type) -> hoconsc:enum([sha256, sha512]); algorithm(default) -> sha256; algorithm(_) -> undefined. 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 c76ece1c4..4f18e32da 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl @@ -38,15 +38,29 @@ structs() -> [config]. fields(config) -> [ {server_type, {enum, [mysql]}} , {password_hash_algorithm, fun password_hash_algorithm/1} - , {salt_position, {enum, [prefix, suffix]}} + , {salt_position, fun salt_position/1} , {query, fun query/1} , {query_timeout, fun query_timeout/1} ] ++ emqx_connector_schema_lib:relational_db_fields() - ++ emqx_connector_schema_lib:ssl_fields(). + ++ emqx_connector_schema_lib:ssl_fields(); -password_hash_algorithm(type) -> string(); +fields(bcrypt) -> + [ {name, {enum, [bcrypt]}} + , {salt_rounds, fun salt_rounds/1} + ]; + +fields(other_algorithms) -> + [ {name, {enum, [plain, md5, sha, sha256, sha512]}} + ]. + +password_hash_algorithm(type) -> {union, [hoconsc:ref(bcrypt), hoconsc:ref(other_algorithms)]}; +password_hash_algorithm(default) -> #{<<"name">> => sha256}; password_hash_algorithm(_) -> undefined. +salt_position(type) -> {enum, [prefix, suffix]}; +salt_position(default) -> prefix; +salt_position(_) -> undefined. + query(type) -> string(); query(nullable) -> false; query(_) -> undefined. From 2a594b1a7371acfd205facd197e546d3e86875a0 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Mon, 19 Jul 2021 15:56:39 +0800 Subject: [PATCH 2/7] fix(authn): fix some bugs --- apps/emqx_authn/src/emqx_authn_api.erl | 716 ++++++++++-------- .../src/simple_authn/emqx_authn_http.erl | 112 +-- .../src/simple_authn/emqx_authn_jwt.erl | 23 +- .../src/simple_authn/emqx_authn_mysql.erl | 4 + 4 files changed, 466 insertions(+), 389 deletions(-) diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index c875cc717..de2755bef 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -16,338 +16,414 @@ -module(emqx_authn_api). +-behavior(minirest_api). + -include("emqx_authn.hrl"). --export([ create_authenticator/2 - , delete_authenticator/2 - , update_authenticator/2 - , lookup_authenticator/2 - , list_authenticators/2 - , move_authenticator/2 - , import_users/2 - , add_user/2 - , delete_user/2 - , update_user/2 - , lookup_user/2 - , list_users/2 - ]). +-export([ api_spec/0 ]). --rest_api(#{name => create_authenticator, - method => 'POST', - path => "/authentication/authenticators", - func => create_authenticator, - descr => "Create authenticator" - }). +api_spec() -> + {[authenticator_api()], definitions()}. --rest_api(#{name => delete_authenticator, - method => 'DELETE', - path => "/authentication/authenticators/:bin:name", - func => delete_authenticator, - descr => "Delete authenticator" - }). - --rest_api(#{name => update_authenticator, - method => 'PUT', - path => "/authentication/authenticators/:bin:name", - func => update_authenticator, - descr => "Update authenticator" - }). - --rest_api(#{name => lookup_authenticator, - method => 'GET', - path => "/authentication/authenticators/:bin:name", - func => lookup_authenticator, - descr => "Lookup authenticator" - }). - --rest_api(#{name => list_authenticators, - method => 'GET', - path => "/authentication/authenticators", - func => list_authenticators, - descr => "List authenticators" - }). - --rest_api(#{name => move_authenticator, - method => 'POST', - path => "/authentication/authenticators/:bin:name/position", - func => move_authenticator, - descr => "Change the order of authenticators" - }). - --rest_api(#{name => import_users, - method => 'POST', - path => "/authentication/authenticators/:bin:name/import-users", - func => import_users, - descr => "Import users" - }). - --rest_api(#{name => add_user, - method => 'POST', - path => "/authentication/authenticators/:bin:name/users", - func => add_user, - descr => "Add user" - }). - --rest_api(#{name => delete_user, - method => 'DELETE', - path => "/authentication/authenticators/:bin:name/users/:bin:user_id", - func => delete_user, - descr => "Delete user" - }). - --rest_api(#{name => update_user, - method => 'PUT', - path => "/authentication/authenticators/:bin:name/users/:bin:user_id", - func => update_user, - descr => "Update user" - }). - --rest_api(#{name => lookup_user, - method => 'GET', - path => "/authentication/authenticators/:bin:name/users/:bin:user_id", - func => lookup_user, - descr => "Lookup user" - }). - -%% TODO: Support pagination --rest_api(#{name => list_users, - method => 'GET', - path => "/authentication/authenticators/:bin:name/users", - func => list_users, - descr => "List all users" - }). - -create_authenticator(Binding, Params) -> - do_create_authenticator(uri_decode(Binding), lists_to_map(Params)). - -do_create_authenticator(_Binding, Authenticator0) -> - Config = #{<<"emqx_authn">> => #{ - <<"authenticators">> => [Authenticator0] - }}, - #{emqx_authn := #{authenticators := [Authenticator1]}} - = hocon_schema:check_plain(emqx_authn_schema, Config, - #{atom_key => true, nullable => true}), - case emqx_authn:create_authenticator(?CHAIN, Authenticator1) of - {ok, Authenticator2} -> - return({ok, Authenticator2}); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -delete_authenticator(Binding, Params) -> - do_delete_authenticator(uri_decode(Binding), maps:from_list(Params)). - -do_delete_authenticator(#{name := Name}, _Params) -> - case emqx_authn:delete_authenticator(?CHAIN, Name) of - ok -> - return(ok); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -%% TODO: Support incremental update -update_authenticator(Binding, Params) -> - do_update_authenticator(uri_decode(Binding), lists_to_map(Params)). - -%% TOOD: PUT method supports creation and update -do_update_authenticator(#{name := Name}, NewConfig0) -> - case emqx_authn:lookup_authenticator(?CHAIN, Name) of - {ok, #{mechanism := Mechanism}} -> - Authenticator = #{<<"name">> => Name, - <<"mechanism">> => Mechanism, - <<"config">> => NewConfig0}, - Config = #{<<"emqx_authn">> => #{ - <<"authenticators">> => [Authenticator] - }}, - #{ - emqx_authn := #{ - authenticators := [#{ - config := NewConfig1 - }] +authenticator_api() -> + Example1 = #{name => <<"example">>, + mechanism => <<"password-based">>, + config => #{ + server_type => <<"built-in-example">>, + user_id_type => <<"username">>, + password_hash_algorithm => #{ + name => <<"sha256">> + } + }}, + Example2 = #{name => <<"example">>, + mechanism => <<"password-based">>, + config => #{ + server_type => <<"http-server">>, + method => <<"post">>, + url => <<"http://localhost:80/login">>, + headers => #{ + <<"content-type">> => <<"application/json">> + }, + form_data => #{ + <<"username">> => <<"${mqtt-username}">>, + <<"password">> => <<"${mqtt-password}">> + } + }}, + Example3 = #{name => <<"example">>, + mechanism => <<"jwt">>, + config => #{ + use_jwks => false, + algorithm => <<"hmac-based">>, + secret => <<"mysecret">>, + secret_base64_encoded => false, + verify_claims => #{ + <<"username">> => <<"${mqtt-username}">> + } + }}, + Metadata = #{ + post => #{ + description => "Create authenticator", + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"authenticator">>), + examples => #{ + default => #{ + summary => <<"Default">>, + value => emqx_json:encode(Example1) + }, + http => #{ + summary => <<"Authentication provided by HTTP Server">>, + value => emqx_json:encode(Example2) + }, + jwt => #{ + summary => <<"JWT Authentication">>, + value => emqx_json:encode(Example3) + } + } + } } - } = hocon_schema:check_plain(emqx_authn_schema, Config, - #{atom_key => true, nullable => true}), - case emqx_authn:update_authenticator(?CHAIN, Name, NewConfig1) of - {ok, NAuthenticator} -> - return({ok, NAuthenticator}); - {error, Reason} -> - return(serialize_error(Reason)) - end; - {error, Reason} -> - return(serialize_error(Reason)) - end. + }, + responses => #{ + <<"201">> => #{ + description => <<"Created successfully">>, + content => #{} + } + } + } + }, + {"/authentication/authenticators", Metadata, clients}. -lookup_authenticator(Binding, Params) -> - do_lookup_authenticator(uri_decode(Binding), maps:from_list(Params)). +definitions() -> + AuthenticatorDef = #{ + allOf => [ + #{ + type => object, + required => [name], + properties => #{ + name => #{ + type => string, + example => "exmaple" + } + } + }, + #{ + oneOf => [ minirest:ref(<<"password_based">>) + , minirest:ref(<<"jwt">>) + , minirest:ref(<<"scram">>) + ] + } + ] + }, -do_lookup_authenticator(#{name := Name}, _Params) -> - case emqx_authn:lookup_authenticator(?CHAIN, Name) of - {ok, Authenticator} -> - return({ok, Authenticator}); - {error, Reason} -> - return(serialize_error(Reason)) - end. + PasswordBasedDef = #{ + type => object, + properties => #{ + mechanism => #{ + type => string, + enum => [<<"password-based">>], + example => <<"password-based">> + }, + config => #{ + oneOf => [ minirest:ref(<<"password_based_built_in_database">>) + , minirest:ref(<<"password_based_mysql">>) + , minirest:ref(<<"password_based_pgsql">>) + , minirest:ref(<<"password_based_http_server">>) + ] + } + } + }, -list_authenticators(Binding, Params) -> - do_list_authenticators(uri_decode(Binding), maps:from_list(Params)). + JWTDef = #{ + type => object, + properties => #{ + mechanism => #{ + type => string, + enum => [<<"jwt">>], + example => <<"jwt">> + }, + config => #{ + type => object, + properties => #{ + use_jwks => #{ + type => boolean, + default => false, + example => false + }, + algorithm => #{ + type => string, + enum => [<<"hmac-based">>, <<"public-key">>], + default => <<"hmac-based">>, + example => <<"hmac-based">> + }, + secret => #{ + type => string + }, + secret_base64_encoded => #{ + type => boolean, + default => false + }, + certificate => #{ + type => string + }, + verify_claims => #{ + type => object, + additionalProperties => #{ + type => string + } + }, + ssl => minirest:ref(<<"ssl">>) + } + } + } + }, + + SCRAMDef = #{ + type => object, + properties => #{ + mechanism => #{ + type => string, + enum => [<<"scram">>], + example => <<"scram">> + }, + config => #{ + type => object, + properties => #{ + server_type => #{ + type => string, + enum => [<<"built-in-database">>], + default => <<"built-in-database">> + }, + algorithm => #{ + type => string, + enum => [<<"sha256">>, <<"sha512">>], + default => <<"sha256">> + }, + iteration_count => #{ + type => integer, + default => 4096 + } + } + } + } + }, -do_list_authenticators(_Binding, _Params) -> - case emqx_authn:list_authenticators(?CHAIN) of - {ok, Authenticators} -> - return({ok, Authenticators}); - {error, Reason} -> - return(serialize_error(Reason)) - end. + PasswordBasedBuiltInDatabaseDef = #{ + type => object, + properties => #{ + server_type => #{ + type => string, + enum => [<<"built-in-database">>], + example => <<"built-in-database">> + }, + user_id_type => #{ + type => string, + enum => [<<"username">>, <<"clientid">>], + default => <<"username">>, + example => <<"username">> + }, + password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>) + } + }, -move_authenticator(Binding, Params) -> - do_move_authenticator(uri_decode(Binding), maps:from_list(Params)). + PasswordBasedMySQLDef = #{ + type => object, + properties => #{ + server_type => #{ + type => string, + enum => [<<"mysql">>], + example => <<"mysql">> + }, + server => #{ + type => string, + example => <<"localhost:3306">> + }, + database => #{ + type => string + }, + pool_size => #{ + type => integer, + default => 8 + }, + username => #{ + type => string + }, + password => #{ + type => string + }, + auto_reconnect => #{ + type => boolean, + default => true + }, + ssl => minirest:ref(<<"ssl">>), + password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>), + salt_position => #{ + type => string, + enum => [<<"prefix">>, <<"suffix">>], + default => <<"prefix">> + }, + query => #{ + type => string, + example => <<"SELECT password_hash FROM mqtt_user WHERE username = ${mqtt-username}">> + }, + query_timeout => #{ + type => integer, + description => <<"Query timeout, Unit: Milliseconds">>, + default => 5000 + } + } + }, -do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the front">>}) -> - case emqx_authn:move_authenticator_to_the_front(?CHAIN, Name) of - ok -> - return(ok); - {error, Reason} -> - return(serialize_error(Reason)) - end; -do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the end">>}) -> - case emqx_authn:move_authenticator_to_the_end(?CHAIN, Name) of - ok -> - return(ok); - {error, Reason} -> - return(serialize_error(Reason)) - end; -do_move_authenticator(#{name := Name}, #{<<"position">> := N}) when is_number(N) -> - case emqx_authn:move_authenticator_to_the_nth(?CHAIN, Name, N) of - ok -> - return(ok); - {error, Reason} -> - return(serialize_error(Reason)) - end; -do_move_authenticator(_Binding, _Params) -> - return(serialize_error({missing_parameter, <<"position">>})). + PasswordBasedPgSQLDef = #{ + type => object, + properties => #{ + server_type => #{ + type => string, + enum => [<<"pgsql">>], + example => <<"pgsql">> + }, + server => #{ + type => string, + example => <<"localhost:5432">> + }, + database => #{ + type => string + }, + pool_size => #{ + type => integer, + default => 8 + }, + username => #{ + type => string + }, + password => #{ + type => string + }, + auto_reconnect => #{ + type => boolean, + default => true + }, + password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>), + salt_position => #{ + type => string, + enum => [<<"prefix">>, <<"suffix">>], + default => <<"prefix">> + }, + query => #{ + type => string, + example => <<"SELECT password_hash FROM mqtt_user WHERE username = ${mqtt-username}">> + } + } + }, -import_users(Binding, Params) -> - do_import_users(uri_decode(Binding), maps:from_list(Params)). + PasswordBasedHTTPServerDef = #{ + type => object, + properties => #{ + server_type => #{ + type => string, + enum => [<<"http-server">>], + example => <<"http-server">> + }, + method => #{ + type => string, + enum => [<<"get">>, <<"post">>], + default => <<"post">> + }, + url => #{ + type => string, + example => <<"http://localhost:80/login">> + }, + headers => #{ + type => object, + additionalProperties => #{ + type => string + } + }, + format_data => #{ + type => string + }, + connect_timeout => #{ + type => integer, + default => 5000 + }, + max_retries => #{ + type => integer, + default => 5 + }, + retry_interval => #{ + type => integer, + default => 1000 + }, + request_timout => #{ + type => integer, + default => 5000 + }, + pool_size => #{ + type => integer, + default => 8 + } + } + }, -do_import_users(#{name := Name}, - #{<<"filename">> := Filename}) -> - case emqx_authn:import_users(?CHAIN, Name, Filename) of - ok -> - return(ok); - {error, Reason} -> - return(serialize_error(Reason)) - end; -do_import_users(_Binding, Params) -> - Missed = get_missed_params(Params, [<<"filename">>, <<"file_format">>]), - return(serialize_error({missing_parameter, Missed})). + PasswordHashAlgorithmDef = #{ + type => object, + required => [name], + properties => #{ + name => #{ + type => string, + enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>], + default => <<"sha256">> + }, + salt_rounds => #{ + type => integer, + default => 10 + } + } + }, -add_user(Binding, Params) -> - do_add_user(uri_decode(Binding), maps:from_list(Params)). + SSLDef = #{ + type => object, + properties => #{ + enable => #{ + type => boolean, + default => false + }, + certfile => #{ + type => string + }, + keyfile => #{ + type => string + }, + cacertfile => #{ + type => string + }, + verify => #{ + type => boolean, + default => true + }, + server_name_indication => #{ + type => object, + properties => #{ + enable => #{ + type => boolean, + default => false + }, + hostname => #{ + type => string + } + } + } + } + }, -do_add_user(#{name := Name}, UserInfo) -> - case emqx_authn:add_user(?CHAIN, Name, UserInfo) of - {ok, User} -> - return({ok, User}); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -delete_user(Binding, Params) -> - do_delete_user(uri_decode(Binding), maps:from_list(Params)). - -do_delete_user(#{name := Name, - user_id := UserID}, _Params) -> - case emqx_authn:delete_user(?CHAIN, Name, UserID) of - ok -> - return(ok); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -update_user(Binding, Params) -> - do_update_user(uri_decode(Binding), maps:from_list(Params)). - -do_update_user(#{name := Name, - user_id := UserID}, NewUserInfo) -> - case emqx_authn:update_user(?CHAIN, Name, UserID, NewUserInfo) of - {ok, User} -> - return({ok, User}); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -lookup_user(Binding, Params) -> - do_lookup_user(uri_decode(Binding), maps:from_list(Params)). - -do_lookup_user(#{name := Name, - user_id := UserID}, _Params) -> - case emqx_authn:lookup_user(?CHAIN, Name, UserID) of - {ok, User} -> - return({ok, User}); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -list_users(Binding, Params) -> - do_list_users(uri_decode(Binding), maps:from_list(Params)). - -do_list_users(#{name := Name}, _Params) -> - case emqx_authn:list_users(?CHAIN, Name) of - {ok, Users} -> - return({ok, Users}); - {error, Reason} -> - return(serialize_error(Reason)) - end. - -%%------------------------------------------------------------------------------ -%% Internal functions -%%------------------------------------------------------------------------------ - -uri_decode(Params) -> - maps:fold(fun(K, V, Acc) -> - Acc#{K => emqx_http_lib:uri_decode(V)} - end, #{}, Params). - -lists_to_map(L) -> - lists_to_map(L, #{}). - -lists_to_map([], Acc) -> - Acc; -lists_to_map([{K, V} | More], Acc) when is_list(V) -> - NV = lists_to_map(V), - lists_to_map(More, Acc#{K => NV}); -lists_to_map([{K, V} | More], Acc) -> - lists_to_map(More, Acc#{K => V}); -lists_to_map([_ | _] = L, _) -> - L. - -serialize_error({already_exists, {Type, ID}}) -> - {error, <<"ALREADY_EXISTS">>, list_to_binary(io_lib:format("~s '~s' already exists", [serialize_type(Type), ID]))}; -serialize_error({not_found, {Type, ID}}) -> - {error, <<"NOT_FOUND">>, list_to_binary(io_lib:format("~s '~s' not found", [serialize_type(Type), ID]))}; -serialize_error({duplicate, Name}) -> - {error, <<"INVALID_PARAMETER">>, list_to_binary(io_lib:format("Authenticator name '~s' is duplicated", [Name]))}; -serialize_error({missing_parameter, Names = [_ | Rest]}) -> - Format = ["~s," || _ <- Rest] ++ ["~s"], - NFormat = binary_to_list(iolist_to_binary(Format)), - {error, <<"MISSING_PARAMETER">>, list_to_binary(io_lib:format("The input parameters " ++ NFormat ++ " that are mandatory for processing this request are not supplied.", Names))}; -serialize_error({missing_parameter, Name}) -> - {error, <<"MISSING_PARAMETER">>, list_to_binary(io_lib:format("The input parameter '~s' that is mandatory for processing this request is not supplied.", [Name]))}; -serialize_error(_) -> - {error, <<"UNKNOWN_ERROR">>, <<"Unknown error">>}. - -serialize_type(authenticator) -> - "Authenticator". - -get_missed_params(Actual, Expected) -> - Keys = lists:foldl(fun(Key, Acc) -> - case maps:is_key(Key, Actual) of - true -> Acc; - false -> [Key | Acc] - end - end, [], Expected), - lists:reverse(Keys). - -return(_) -> -%% TODO: V5 API - ok. + [ #{<<"authenticator">> => AuthenticatorDef} + , #{<<"password_based">> => PasswordBasedDef} + , #{<<"jwt">> => JWTDef} + , #{<<"scram">> => SCRAMDef} + , #{<<"password_based_built_in_database">> => PasswordBasedBuiltInDatabaseDef} + , #{<<"password_based_mysql">> => PasswordBasedMySQLDef} + , #{<<"password_based_pgsql">> => PasswordBasedPgSQLDef} + , #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef} + , #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef} + , #{<<"ssl">> => SSLDef} + ]. \ No newline at end of file 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 14240b578..4d3740a8e 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -26,13 +26,6 @@ , validations/0 ]). --type accept() :: 'application/json' | 'application/x-www-form-urlencoded'. --type content_type() :: accept(). - --reflect_type([ accept/0 - , content_type/0 - ]). - -export([ create/3 , update/4 , authenticate/2 @@ -53,45 +46,53 @@ fields("") -> fields(get) -> [ {method, #{type => get, - default => get}} + default => post}} + , {headers, fun headers_no_content_type/1} ] ++ common_fields(); fields(post) -> [ {method, #{type => post, - default => get}} - , {content_type, fun content_type/1} + default => post}} + , {headers, fun headers/1} ] ++ common_fields(). common_fields() -> [ {server_type, {enum, ['http-server']}} , {url, fun url/1} - , {accept, fun accept/1} - , {headers, fun headers/1} , {form_data, fun form_data/1} , {request_timeout, fun request_timeout/1} - ] ++ proplists:delete(base_url, emqx_connector_http:fields(config)). + ] ++ 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_ssl_opts, fun check_ssl_opts/1} + , {check_headers, fun check_headers/1} + ]. url(type) -> binary(); url(nullable) -> false; url(validate) -> [fun check_url/1]; url(_) -> undefined. -accept(type) -> accept(); -accept(default) -> 'application/json'; -accept(_) -> undefined. - -content_type(type) -> content_type(); -content_type(default) -> 'application/json'; -content_type(_) -> undefined. - -headers(type) -> list(); -headers(default) -> []; +headers(type) -> map(); +headers(converter) -> + fun(Headers) -> + maps:merge(default_headers(), transform_header_name(Headers)) + end; +headers(default) -> default_headers(); headers(_) -> undefined. -form_data(type) -> binary(); +headers_no_content_type(type) -> map(); +headers_no_content_type(converter) -> + fun(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. + +%% TODO: Using map() +form_data(type) -> map(); form_data(nullable) -> false; form_data(validate) -> [fun check_form_data/1]; form_data(_) -> undefined. @@ -107,28 +108,17 @@ request_timeout(_) -> undefined. create(ChainID, AuthenticatorName, #{method := Method, url := URL, - accept := Accept, headers := Headers, form_data := FormData, request_timeout := RequestTimeout} = Config) -> - ContentType = maps:get(content_type, Config, undefined), - DefaultHeader0 = case ContentType of - undefined -> #{}; - _ -> #{<<"content-type">> => atom_to_binary(ContentType, utf8)} - end, - DefaultHeader = DefaultHeader0#{<<"accept">> => atom_to_binary(Accept, utf8)}, - NHeaders = maps:to_list(maps:merge(DefaultHeader, maps:from_list(Headers))), - NFormData = preprocess_form_data(FormData), #{path := Path, query := Query} = URIMap = parse_url(URL), BaseURL = generate_base_url(URIMap), State = #{method => Method, path => Path, base_query => cow_qs:parse_qs(list_to_binary(Query)), - accept => Accept, - content_type => ContentType, - headers => NHeaders, - form_data => NFormData, + headers => maps:to_list(Headers), + form_data => FormData, request_timeout => RequestTimeout}, ResourceID = <>, case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of @@ -182,26 +172,38 @@ check_url(URL) -> end. check_form_data(FormData) -> - KVs = binary:split(FormData, [<<"&">>], [global]), - case false =:= lists:any(fun(T) -> T =:= <<>> end, KVs) of - true -> - NKVs = [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs], - false =:= - lists:any(fun({K, V}) -> - K =:= <<>> orelse V =:= <<>>; - (_) -> - true - end, NKVs); - false -> - false - end. + lists:any(fun({_, V}) -> + not is_binary(V) + end, maps:to_list(FormData)). + +default_headers() -> + 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=5">> + }. + +transform_header_name(Headers) -> + maps:fold(fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(binary_to_list(K0))), + maps:put(K, V, Acc) + end, #{}, Headers). check_ssl_opts(Conf) -> emqx_connector_http:check_ssl_opts("url", Conf). -preprocess_form_data(FormData) -> - KVs = binary:split(FormData, [<<"&">>], [global]), - [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs]. +check_headers(Conf) -> + Method = hocon_schema:get_value("method", Conf), + Headers = hocon_schema:get_value("headers", Conf), + case Method =:= get andalso maps:get(<<"content-type">>, Headers, undefined) =/= undefined of + true -> false; + false -> true + end. parse_url(URL) -> {ok, URIMap} = emqx_http_lib:uri_parse(URL), @@ -220,7 +222,6 @@ generate_base_url(#{scheme := Scheme, generate_request(Credential, #{method := Method, path := Path, base_query := BaseQuery, - content_type := ContentType, headers := Headers, form_data := FormData0}) -> FormData = replace_placeholders(FormData0, Credential), @@ -230,6 +231,7 @@ generate_request(Credential, #{method := Method, {NPath, Headers}; post -> NPath = append_query(Path, BaseQuery), + ContentType = proplists:get_value(<<"content-type">>, Headers), Body = serialize_body(ContentType, FormData), {NPath, Headers, Body} end. 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 437dac72d..2e4e473c0 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl @@ -22,7 +22,6 @@ -export([ structs/0 , fields/1 - , validations/0 ]). -export([ create/3 @@ -81,19 +80,13 @@ fields(ssl_enable) -> ]; fields(ssl_disable) -> - [ {enable, #{type => false}} ]; - -fields(claim) -> - [ {"$name", fun expected_claim_value/1} ]. - -validations() -> - [ {check_verify_claims, fun check_verify_claims/1} ]. + [ {enable, #{type => false}} ]. secret(type) -> string(); secret(_) -> undefined. secret_base64_encoded(type) -> boolean(); -secret_base64_encoded(defualt) -> false; +secret_base64_encoded(default) -> false; secret_base64_encoded(_) -> undefined. certificate(type) -> string(); @@ -123,13 +116,15 @@ verify(_) -> undefined. server_name_indication(type) -> string(); server_name_indication(_) -> undefined. -verify_claims(type) -> hoconsc:array(hoconsc:ref(claim)); -verify_claims(default) -> []; +verify_claims(type) -> list(); +verify_claims(default) -> #{}; +verify_claims(validate) -> [fun check_verify_claims/1]; +verify_claims(converter) -> + fun(VerifyClaims) -> + maps:to_list(VerifyClaims) + end; verify_claims(_) -> undefined. -expected_claim_value(type) -> string(); -expected_claim_value(_) -> undefined. - %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ 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 4f18e32da..c112b9666 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl @@ -57,6 +57,10 @@ password_hash_algorithm(type) -> {union, [hoconsc:ref(bcrypt), hoconsc:ref(other password_hash_algorithm(default) -> #{<<"name">> => sha256}; password_hash_algorithm(_) -> undefined. +salt_rounds(type) -> integer(); +salt_rounds(default) -> 10; +salt_rounds(_) -> undefined. + salt_position(type) -> {enum, [prefix, suffix]}; salt_position(default) -> prefix; salt_position(_) -> undefined. From 3e515d8a8a8922d22406f068b4eaeb544987feb8 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 20 Jul 2021 17:26:23 +0800 Subject: [PATCH 3/7] fix(authn): fix bugs for http authn --- .../src/simple_authn/emqx_authn_http.erl | 22 ++++++++++--------- .../src/emqx_connector_http.erl | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) 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 4d3740a8e..48d123b70 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -113,15 +113,17 @@ create(ChainID, AuthenticatorName, request_timeout := RequestTimeout} = Config) -> #{path := Path, query := Query} = URIMap = parse_url(URL), - BaseURL = generate_base_url(URIMap), State = #{method => Method, path => Path, base_query => cow_qs:parse_qs(list_to_binary(Query)), - headers => maps:to_list(Headers), - form_data => FormData, + headers => normalize_headers(Headers), + form_data => maps:to_list(FormData), request_timeout => RequestTimeout}, ResourceID = <>, - case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of + case emqx_resource:create_local(ResourceID, + emqx_connector_http, + Config#{base_url => maps:remove(query, URIMap), + pool_type => random}) of {ok, _} -> {ok, State#{resource_id => ResourceID}}; {error, already_created} -> @@ -214,10 +216,8 @@ parse_url(URL) -> URIMap end. -generate_base_url(#{scheme := Scheme, - host := Host, - port := Port}) -> - iolist_to_binary(io_lib:format("~p://~s:~p", [Scheme, Host, Port])). +normalize_headers(Headers) -> + [{atom_to_binary(K), V} || {K, V} <- maps:to_list(Headers)]. generate_request(Credential, #{method := Method, path := Path, @@ -251,6 +251,8 @@ replace_placeholder(<<"${mqtt-username}">>, Credential) -> maps:get(username, Credential, undefined); replace_placeholder(<<"${mqtt-clientid}">>, Credential) -> maps:get(clientid, Credential, undefined); +replace_placeholder(<<"${mqtt-password}">>, Credential) -> + maps:get(password, Credential, undefined); replace_placeholder(<<"${ip-address}">>, Credential) -> maps:get(peerhost, Credential, undefined); replace_placeholder(<<"${cert-subject}">>, Credential) -> @@ -274,9 +276,9 @@ qs([], Acc) -> qs([{K, V} | More], Acc) -> qs(More, [["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] | Acc]). -serialize_body('application/json', FormData) -> +serialize_body(<<"application/json">>, FormData) -> emqx_json:encode(FormData); -serialize_body('application/x-www-form-urlencoded', FormData) -> +serialize_body(<<"application/x-www-form-urlencoded">>, FormData) -> qs(FormData). safely_parse_body(ContentType, Body) -> diff --git a/apps/emqx_connector/src/emqx_connector_http.erl b/apps/emqx_connector/src/emqx_connector_http.erl index d6d30eb76..30480eb35 100644 --- a/apps/emqx_connector/src/emqx_connector_http.erl +++ b/apps/emqx_connector/src/emqx_connector_http.erl @@ -147,7 +147,7 @@ on_start(InstId, #{base_url := #{scheme := Scheme, , {pool_type, PoolType} , {pool_size, PoolSize} , {transport, Transport} - , {transport, NTransportOpts}], + , {transport_opts, NTransportOpts}], PoolName = emqx_plugin_libs_pool:pool_name(InstId), {ok, _} = ehttpc_sup:start_pool(PoolName, PoolOpts), {ok, #{pool_name => PoolName, From 7d2aac7e24fa19a14ee28dc7489ee456082d751e Mon Sep 17 00:00:00 2001 From: zhouzb Date: Wed, 21 Jul 2021 11:32:38 +0800 Subject: [PATCH 4/7] feat(http pipelining): support to switch http pipelining --- apps/emqx_authn/src/emqx_authn_api.erl | 8 +++++-- .../src/emqx_connector_http.erl | 21 ++++++++++++------- rebar.config | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index de2755bef..561f69861 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -92,7 +92,7 @@ authenticator_api() -> } } }, - {"/authentication/authenticators", Metadata, clients}. + {"/authentication/authenticators", Metadata, authenticators}. definitions() -> AuthenticatorDef = #{ @@ -361,6 +361,10 @@ definitions() -> pool_size => #{ type => integer, default => 8 + }, + enable_pipelining => #{ + type => boolean, + default => true } } }, @@ -426,4 +430,4 @@ definitions() -> , #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef} , #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef} , #{<<"ssl">> => SSLDef} - ]. \ No newline at end of file + ]. diff --git a/apps/emqx_connector/src/emqx_connector_http.erl b/apps/emqx_connector/src/emqx_connector_http.erl index 30480eb35..11860f32d 100644 --- a/apps/emqx_connector/src/emqx_connector_http.erl +++ b/apps/emqx_connector/src/emqx_connector_http.erl @@ -53,14 +53,15 @@ fields("") -> [{config, #{type => hoconsc:ref(?MODULE, config)}}]; fields(config) -> - [ {base_url, fun base_url/1} - , {connect_timeout, fun connect_timeout/1} - , {max_retries, fun max_retries/1} - , {retry_interval, fun retry_interval/1} - , {pool_type, fun pool_type/1} - , {pool_size, fun pool_size/1} - , {ssl_opts, #{type => hoconsc:ref(?MODULE, ssl_opts), - default => #{}}} + [ {base_url, fun base_url/1} + , {connect_timeout, fun connect_timeout/1} + , {max_retries, fun max_retries/1} + , {retry_interval, fun retry_interval/1} + , {pool_type, fun pool_type/1} + , {pool_size, fun pool_size/1} + , {enable_pipelining, fun enable_pipelining/1} + , {ssl_opts, #{type => hoconsc:ref(?MODULE, ssl_opts), + default => #{}}} ]; fields(ssl_opts) -> @@ -101,6 +102,10 @@ pool_size(type) -> non_neg_integer(); pool_size(default) -> 8; pool_size(_) -> undefined. +enable_pipelining(type) -> boolean(); +enable_pipelining(default) -> true; +enable_pipelining(_) -> undefined. + cacertfile(type) -> string(); cacertfile(nullable) -> true; cacertfile(_) -> undefined. diff --git a/rebar.config b/rebar.config index 66d8b8f27..cdbf1869a 100644 --- a/rebar.config +++ b/rebar.config @@ -43,7 +43,7 @@ {deps, [ {gpb, "4.11.2"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps - , {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.7"}}} + , {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.8"}}} , {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}} , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}} From 327ff8636f9965fd95193d7e21df39cb26dbe7bc Mon Sep 17 00:00:00 2001 From: zhouzb Date: Mon, 26 Jul 2021 14:37:06 +0800 Subject: [PATCH 5/7] feat(authn http api): provide http api for authn and improve update mechanism --- apps/emqx_authn/etc/emqx_authn.conf | 14 +- apps/emqx_authn/include/emqx_authn.hrl | 14 +- apps/emqx_authn/src/emqx_authn.erl | 298 +++---- apps/emqx_authn/src/emqx_authn_api.erl | 818 ++++++++++++++++-- apps/emqx_authn/src/emqx_authn_app.erl | 8 +- apps/emqx_authn/src/emqx_authn_schema.erl | 59 +- .../emqx_enhanced_authn_scram_mnesia.erl | 22 +- .../src/simple_authn/emqx_authn_http.erl | 52 +- .../src/simple_authn/emqx_authn_jwt.erl | 34 +- .../src/simple_authn/emqx_authn_mnesia.erl | 34 +- .../src/simple_authn/emqx_authn_mysql.erl | 47 +- .../src/simple_authn/emqx_authn_pgsql.erl | 36 +- 12 files changed, 1065 insertions(+), 371 deletions(-) diff --git a/apps/emqx_authn/etc/emqx_authn.conf b/apps/emqx_authn/etc/emqx_authn.conf index 3e69ae46d..30ef427ae 100644 --- a/apps/emqx_authn/etc/emqx_authn.conf +++ b/apps/emqx_authn/etc/emqx_authn.conf @@ -1,13 +1,11 @@ emqx_authn: { enable: false authenticators: [ - # { - # name: "authenticator1" - # mechanism: password-based - # config: { - # server_type: built-in-database - # user_id_type: clientid - # } - # } + { + name: "authenticator1" + mechanism: password-based + server_type: built-in-database + user_id_type: clientid + } ] } diff --git a/apps/emqx_authn/include/emqx_authn.hrl b/apps/emqx_authn/include/emqx_authn.hrl index bb353348f..ef3e2893c 100644 --- a/apps/emqx_authn/include/emqx_authn.hrl +++ b/apps/emqx_authn/include/emqx_authn.hrl @@ -17,21 +17,21 @@ -define(APP, emqx_authn). -define(CHAIN, <<"mqtt">>). --type chain_id() :: binary(). --type authenticator_name() :: binary(). --type mechanism() :: 'password-based' | jwt | scram. +-define(VER_1, <<"1">>). +-define(VER_2, <<"2">>). -record(authenticator, - { name :: authenticator_name() - , mechanism :: mechanism() + { id :: binary() + , name :: binary() , provider :: module() , config :: map() , state :: map() + , version :: binary() }). -record(chain, - { id :: chain_id() - , authenticators :: [{authenticator_name(), #authenticator{}}] + { id :: binary() + , authenticators :: [{binary(), binary(), #authenticator{}}] , created_at :: integer() }). diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl index 731ac31fe..95ace3800 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_authn/src/emqx_authn.erl @@ -31,10 +31,9 @@ , create_authenticator/2 , delete_authenticator/2 , update_authenticator/3 + , update_or_create_authenticator/3 , lookup_authenticator/2 , list_authenticators/1 - , move_authenticator_to_the_front/2 - , move_authenticator_to_the_end/2 , move_authenticator_to_the_nth/3 ]). @@ -95,7 +94,7 @@ authenticate(Credential, _AuthResult) -> do_authenticate([], _) -> {stop, {error, not_authorized}}; -do_authenticate([{_, #authenticator{provider = Provider, state = State}} | More], Credential) -> +do_authenticate([{_, _, #authenticator{provider = Provider, state = State}} | More], Credential) -> case Provider:authenticate(Credential, State) of ignore -> do_authenticate(More, Credential); @@ -130,7 +129,7 @@ delete_chain(ID) -> [] -> {error, {not_found, {chain, ID}}}; [#chain{authenticators = Authenticators}] -> - _ = [do_delete_authenticator(Authenticator) || {_, Authenticator} <- Authenticators], + _ = [do_delete_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators], mnesia:delete(?CHAIN_TAB, ID, write) end end). @@ -147,25 +146,21 @@ list_chains() -> Chains = ets:tab2list(?CHAIN_TAB), {ok, [serialize_chain(Chain) || Chain <- Chains]}. -create_authenticator(ChainID, #{name := Name, - mechanism := Mechanism, - config := Config}) -> +create_authenticator(ChainID, #{name := Name} = Config) -> UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) -> - case lists:keymember(Name, 1, Authenticators) of + case lists:keymember(Name, 2, Authenticators) of true -> - {error, {already_exists, {authenticator, Name}}}; + {error, name_has_be_used}; false -> - Provider = authenticator_provider(Mechanism, Config), - case Provider:create(ChainID, Name, Config) of - {ok, State} -> - Authenticator = #authenticator{name = Name, - mechanism = Mechanism, - provider = Provider, - config = Config, - state = State}, - NChain = Chain#chain{authenticators = Authenticators ++ [{Name, Authenticator}]}, - ok = mnesia:write(?CHAIN_TAB, NChain, write), + AlreadyExist = fun(ID) -> + lists:keymember(ID, 1, Authenticators) + end, + AuthenticatorID = gen_id(AlreadyExist), + case do_create_authenticator(ChainID, AuthenticatorID, Config) of + {ok, Authenticator} -> + NAuthenticators = Authenticators ++ [{AuthenticatorID, Name, Authenticator}], + ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write), {ok, serialize_authenticator(Authenticator)}; {error, Reason} -> {error, Reason} @@ -174,12 +169,12 @@ create_authenticator(ChainID, #{name := Name, end, update_chain(ChainID, UpdateFun). -delete_authenticator(ChainID, AuthenticatorName) -> +delete_authenticator(ChainID, AuthenticatorID) -> UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) -> - case lists:keytake(AuthenticatorName, 1, Authenticators) of + case lists:keytake(AuthenticatorID, 1, Authenticators) of false -> - {error, {not_found, {authenticator, AuthenticatorName}}}; - {value, {_, Authenticator}, NAuthenticators} -> + {error, {not_found, {authenticator, AuthenticatorID}}}; + {value, {_, _, Authenticator}, NAuthenticators} -> _ = do_delete_authenticator(Authenticator), NChain = Chain#chain{authenticators = NAuthenticators}, mnesia:write(?CHAIN_TAB, NChain, write) @@ -187,38 +182,80 @@ delete_authenticator(ChainID, AuthenticatorName) -> end, update_chain(ChainID, UpdateFun). -update_authenticator(ChainID, AuthenticatorName, Config) -> +update_authenticator(ChainID, AuthenticatorID, Config) -> + do_update_authenticator(ChainID, AuthenticatorID, Config, false). + +update_or_create_authenticator(ChainID, AuthenticatorID, Config) -> + do_update_authenticator(ChainID, AuthenticatorID, Config, true). + +do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, CreateWhenNotFound) -> UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) -> - case proplists:get_value(AuthenticatorName, Authenticators, undefined) of - undefined -> - {error, {not_found, {authenticator, AuthenticatorName}}}; - #authenticator{provider = Provider, - config = OriginalConfig, - state = State} = Authenticator -> - NewConfig = maps:merge(OriginalConfig, Config), - case Provider:update(ChainID, AuthenticatorName, NewConfig, State) of - {ok, NState} -> - NAuthenticator = Authenticator#authenticator{config = NewConfig, - state = NState}, - NAuthenticators = update_value(AuthenticatorName, NAuthenticator, Authenticators), - ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write), - {ok, serialize_authenticator(NAuthenticator)}; - {error, Reason} -> - {error, Reason} + case lists:keytake(AuthenticatorID, 1, Authenticators) of + false -> + case CreateWhenNotFound of + true -> + case do_create_authenticator(ChainID, AuthenticatorID, Config) of + {ok, Authenticator} -> + NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}], + ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write), + {ok, serialize_authenticator(Authenticator)}; + {error, Reason} -> + {error, Reason} + end; + false -> + {error, {not_found, {authenticator, AuthenticatorID}}} + end; + {value, + {_, _, #authenticator{provider = Provider, + state = #{version := Version} = State}}, + Others} -> + case lists:keymember(NewName, 2, Others) of + true -> + {error, name_has_be_used}; + false -> + case (NewProvider = authenticator_provider(Config)) =:= Provider of + true -> + Unique = {ChainID, AuthenticatorID, Version}, + case Provider:update(Config#{'_unique' => Unique}, State) of + {ok, NewState} -> + NewAuthenticator = #authenticator{name = NewName, + config = Config, + state = switch_version(NewState)}, + NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators), + ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write), + {ok, serialize_authenticator(NewAuthenticator)}; + {error, Reason} -> + {error, Reason} + end; + false -> + case NewProvider:create(Config#{'_unique' => {ChainID, AuthenticatorID, Version}}) of + {ok, NewState} -> + NewAuthenticator = #authenticator{name = NewName, + provider = NewProvider, + config = Config, + state = switch_version(NewState)}, + NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators), + ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write), + _ = Provider:destroy(State), + {ok, serialize_authenticator(NewAuthenticator)}; + {error, Reason} -> + {error, Reason} + end + end end end - end, + end, update_chain(ChainID, UpdateFun). -lookup_authenticator(ChainID, AuthenticatorName) -> +lookup_authenticator(ChainID, AuthenticatorID) -> case mnesia:dirty_read(?CHAIN_TAB, ChainID) of [] -> {error, {not_found, {chain, ChainID}}}; [#chain{authenticators = Authenticators}] -> - case proplists:get_value(AuthenticatorName, Authenticators, undefined) of - undefined -> - {error, {not_found, {authenticator, AuthenticatorName}}}; - Authenticator -> + case lists:keyfind(AuthenticatorID, 1, Authenticators) of + false -> + {error, {not_found, {authenticator, AuthenticatorID}}}; + {_, _, Authenticator} -> {ok, serialize_authenticator(Authenticator)} end end. @@ -231,9 +268,9 @@ list_authenticators(ChainID) -> {ok, serialize_authenticators(Authenticators)} end. -move_authenticator_to_the_front(ChainID, AuthenticatorName) -> +move_authenticator_to_the_nth(ChainID, AuthenticatorID, N) -> UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) -> - case move_authenticator_to_the_front_(AuthenticatorName, Authenticators) of + case move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N) of {ok, NAuthenticators} -> NChain = Chain#chain{authenticators = NAuthenticators}, mnesia:write(?CHAIN_TAB, NChain, write); @@ -243,108 +280,94 @@ move_authenticator_to_the_front(ChainID, AuthenticatorName) -> end, update_chain(ChainID, UpdateFun). -move_authenticator_to_the_end(ChainID, AuthenticatorName) -> - UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) -> - case move_authenticator_to_the_end_(AuthenticatorName, Authenticators) of - {ok, NAuthenticators} -> - NChain = Chain#chain{authenticators = NAuthenticators}, - mnesia:write(?CHAIN_TAB, NChain, write); - {error, Reason} -> - {error, Reason} - end - end, - update_chain(ChainID, UpdateFun). +import_users(ChainID, AuthenticatorID, Filename) -> + call_authenticator(ChainID, AuthenticatorID, import_users, [Filename]). -move_authenticator_to_the_nth(ChainID, AuthenticatorName, N) -> - UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) -> - case move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N) of - {ok, NAuthenticators} -> - NChain = Chain#chain{authenticators = NAuthenticators}, - mnesia:write(?CHAIN_TAB, NChain, write); - {error, Reason} -> - {error, Reason} - end - end, - update_chain(ChainID, UpdateFun). +add_user(ChainID, AuthenticatorID, UserInfo) -> + call_authenticator(ChainID, AuthenticatorID, add_user, [UserInfo]). -import_users(ChainID, AuthenticatorName, Filename) -> - call_authenticator(ChainID, AuthenticatorName, import_users, [Filename]). +delete_user(ChainID, AuthenticatorID, UserID) -> + call_authenticator(ChainID, AuthenticatorID, delete_user, [UserID]). -add_user(ChainID, AuthenticatorName, UserInfo) -> - call_authenticator(ChainID, AuthenticatorName, add_user, [UserInfo]). +update_user(ChainID, AuthenticatorID, UserID, NewUserInfo) -> + call_authenticator(ChainID, AuthenticatorID, update_user, [UserID, NewUserInfo]). -delete_user(ChainID, AuthenticatorName, UserID) -> - call_authenticator(ChainID, AuthenticatorName, delete_user, [UserID]). +lookup_user(ChainID, AuthenticatorID, UserID) -> + call_authenticator(ChainID, AuthenticatorID, lookup_user, [UserID]). -update_user(ChainID, AuthenticatorName, UserID, NewUserInfo) -> - call_authenticator(ChainID, AuthenticatorName, update_user, [UserID, NewUserInfo]). - -lookup_user(ChainID, AuthenticatorName, UserID) -> - call_authenticator(ChainID, AuthenticatorName, lookup_user, [UserID]). - -list_users(ChainID, AuthenticatorName) -> - call_authenticator(ChainID, AuthenticatorName, list_users, []). +list_users(ChainID, AuthenticatorID) -> + call_authenticator(ChainID, AuthenticatorID, list_users, []). %%------------------------------------------------------------------------------ %% Internal functions %%------------------------------------------------------------------------------ -authenticator_provider('password-based', #{server_type := 'built-in-database'}) -> +authenticator_provider(#{mechanism := 'password-based', server_type := 'built-in-database'}) -> emqx_authn_mnesia; -authenticator_provider('password-based', #{server_type := 'mysql'}) -> +authenticator_provider(#{mechanism := 'password-based', server_type := 'mysql'}) -> emqx_authn_mysql; -authenticator_provider('password-based', #{server_type := 'pgsql'}) -> +authenticator_provider(#{mechanism := 'password-based', server_type := 'pgsql'}) -> emqx_authn_pgsql; -authenticator_provider('password-based', #{server_type := 'http-server'}) -> +authenticator_provider(#{mechanism := 'password-based', server_type := 'http-server'}) -> emqx_authn_http; -authenticator_provider(jwt, _) -> +authenticator_provider(#{mechanism := jwt}) -> emqx_authn_jwt; -authenticator_provider(scram, #{server_type := 'built-in-database'}) -> +authenticator_provider(#{mechanism := scram, server_type := 'built-in-database'}) -> emqx_enhanced_authn_scram_mnesia. +gen_id(AlreadyExist) -> + ID = list_to_binary(emqx_rule_id:gen()), + case AlreadyExist(ID) of + true -> gen_id(AlreadyExist); + false -> ID + end. + +switch_version(State = #{version := ?VER_1}) -> + State#{version := ?VER_2}; +switch_version(State = #{version := ?VER_2}) -> + State#{version := ?VER_1}; +switch_version(State) -> + State#{version => ?VER_1}. + +do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) -> + Provider = authenticator_provider(Config), + Unique = <>, + case Provider:create(Config#{'_unique' => Unique}) of + {ok, State} -> + Authenticator = #authenticator{id = AuthenticatorID, + name = Name, + provider = Provider, + config = Config, + state = switch_version(State)}, + {ok, Authenticator}; + {error, Reason} -> + {error, Reason} + end. + do_delete_authenticator(#authenticator{provider = Provider, state = State}) -> - Provider:destroy(State). + _ = Provider:destroy(State), + ok. -update_value(Key, Value, List) -> - lists:keyreplace(Key, 1, List, {Key, Value}). +replace_authenticator(ID, #authenticator{name = Name} = Authenticator, Authenticators) -> + lists:keyreplace(ID, 1, Authenticators, {ID, Name, Authenticator}). -move_authenticator_to_the_front_(AuthenticatorName, Authenticators) -> - move_authenticator_to_the_front_(AuthenticatorName, Authenticators, []). - -move_authenticator_to_the_front_(AuthenticatorName, [], _) -> - {error, {not_found, {authenticator, AuthenticatorName}}}; -move_authenticator_to_the_front_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], Passed) -> - {ok, [Authenticator | (lists:reverse(Passed) ++ More)]}; -move_authenticator_to_the_front_(AuthenticatorName, [Authenticator | More], Passed) -> - move_authenticator_to_the_front_(AuthenticatorName, More, [Authenticator | Passed]). - -move_authenticator_to_the_end_(AuthenticatorName, Authenticators) -> - move_authenticator_to_the_end_(AuthenticatorName, Authenticators, []). - -move_authenticator_to_the_end_(AuthenticatorName, [], _) -> - {error, {not_found, {authenticator, AuthenticatorName}}}; -move_authenticator_to_the_end_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], Passed) -> - {ok, lists:reverse(Passed) ++ More ++ [Authenticator]}; -move_authenticator_to_the_end_(AuthenticatorName, [Authenticator | More], Passed) -> - move_authenticator_to_the_end_(AuthenticatorName, More, [Authenticator | Passed]). - -move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N) +move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N) when N =< length(Authenticators) andalso N > 0 -> - move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N, []); + move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N, []); move_authenticator_to_the_nth_(_, _, _) -> {error, out_of_range}. -move_authenticator_to_the_nth_(AuthenticatorName, [], _, _) -> - {error, {not_found, {authenticator, AuthenticatorName}}}; -move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed) +move_authenticator_to_the_nth_(AuthenticatorID, [], _, _) -> + {error, {not_found, {authenticator, AuthenticatorID}}}; +move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed) when N =< length(Passed) -> {L1, L2} = lists:split(N - 1, lists:reverse(Passed)), {ok, L1 ++ [Authenticator] ++ L2 ++ More}; -move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed) -> +move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed) -> {L1, L2} = lists:split(N - length(Passed) - 1, More), {ok, lists:reverse(Passed) ++ L1 ++ [Authenticator] ++ L2}; -move_authenticator_to_the_nth_(AuthenticatorName, [Authenticator | More], N, Passed) -> - move_authenticator_to_the_nth_(AuthenticatorName, More, N, [Authenticator | Passed]). +move_authenticator_to_the_nth_(AuthenticatorID, [Authenticator | More], N, Passed) -> + move_authenticator_to_the_nth_(AuthenticatorID, More, N, [Authenticator | Passed]). update_chain(ChainID, UpdateFun) -> trans( @@ -357,24 +380,15 @@ update_chain(ChainID, UpdateFun) -> end end). -% lookup_chain_by_listener(ListenerID, AuthNType) -> -% case mnesia:dirty_read(?BINDING_TAB, {ListenerID, AuthNType}) of -% [] -> -% {error, not_found}; -% [#binding{chain_id = ChainID}] -> -% {ok, ChainID} -% end. - - -call_authenticator(ChainID, AuthenticatorName, Func, Args) -> +call_authenticator(ChainID, AuthenticatorID, Func, Args) -> case mnesia:dirty_read(?CHAIN_TAB, ChainID) of [] -> {error, {not_found, {chain, ChainID}}}; [#chain{authenticators = Authenticators}] -> - case proplists:get_value(AuthenticatorName, Authenticators, undefined) of - undefined -> - {error, {not_found, {authenticator, AuthenticatorName}}}; - #authenticator{provider = Provider, state = State} -> + case lists:keyfind(AuthenticatorID, 1, Authenticators) of + false -> + {error, {not_found, {authenticator, AuthenticatorID}}}; + {_, _, #authenticator{provider = Provider, state = State}} -> case erlang:function_exported(Provider, Func, length(Args) + 1) of true -> erlang:apply(Provider, Func, Args ++ [State]); @@ -391,20 +405,12 @@ serialize_chain(#chain{id = ID, authenticators => serialize_authenticators(Authenticators), created_at => CreatedAt}. -% serialize_binding(#binding{bound = {ListenerID, _}, -% chain_id = ChainID}) -> -% #{listener_id => ListenerID, -% chain_id => ChainID}. - serialize_authenticators(Authenticators) -> - [serialize_authenticator(Authenticator) || {_, Authenticator} <- Authenticators]. + [serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators]. -serialize_authenticator(#authenticator{name = Name, - mechanism = Mechanism, +serialize_authenticator(#authenticator{id = ID, config = Config}) -> - #{name => Name, - mechanism => Mechanism, - config => Config}. + Config#{id => ID}. trans(Fun) -> trans(Fun, []). diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 561f69861..c87df2dc5 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -20,12 +20,25 @@ -include("emqx_authn.hrl"). --export([ api_spec/0 ]). +-export([ api_spec/0 + , authenticators/2 + , authenticators2/2 + , position/2 + , import_users/2 + , users/2 + , users2/2 + ]). api_spec() -> - {[authenticator_api()], definitions()}. + {[ authenticators_api() + , authenticators_api2() + , position_api() + , import_users_api() + , users_api() + , users2_api() + ], definitions()}. -authenticator_api() -> +authenticators_api() -> Example1 = #{name => <<"example">>, mechanism => <<"password-based">>, config => #{ @@ -86,24 +99,506 @@ authenticator_api() -> }, responses => #{ <<"201">> => #{ - description => <<"Created successfully">>, - content => #{} + description => <<"Created">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"returned_authenticator">>) + } + } + } + } + }, + get => #{ + description => "List authenticators", + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => minirest:ref(<<"returned_authenticator">>) + } + } + } } } } }, {"/authentication/authenticators", Metadata, authenticators}. +authenticators_api2() -> + Metadata = #{ + get => #{ + description => "Get authenicator by id", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"returned_authenticator">>) + } + } + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + }, + put => #{ + description => "Update authenticator", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => #{ + oneOf => [ minirest:ref(<<"password_based">>) + , minirest:ref(<<"jwt">>) + , minirest:ref(<<"scram">>) + ] + } + } + } + }, + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"returned_authenticator">>) + } + } + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + }, + delete => #{ + description => "Delete authenticator", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"204">> => #{ + description => <<"No Content">> + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + } + }, + {"/authentication/authenticators/:id", Metadata, authenticators2}. + +position_api() -> + Metadata = #{ + post => #{ + description => "Change the order of authenticators", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => #{ + type => object, + required => [position], + properties => #{ + position => #{ + type => integer, + example => 1 + } + } + } + } + } + }, + responses => #{ + <<"204">> => #{ + description => <<"No Content">> + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + } + }, + {"/authentication/authenticators/:id/position", Metadata, position}. + +import_users_api() -> + Metadata = #{ + post => #{ + description => "Import users from json/csv file", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => #{ + type => object, + required => [filename], + properties => #{ + filename => #{ + type => string + } + } + } + } + } + }, + responses => #{ + <<"204">> => #{ + description => <<"No Content">> + }, + <<"400">> => #{ + description => <<"Bad Request">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + } + }, + {"/authentication/authenticators/:id/import-users", Metadata, import_users}. + +users_api() -> + Metadata = #{ + post => #{ + description => "Add user", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => #{ + type => object, + required => [user_id, password], + properties => #{ + user_id => #{ + type => string + }, + password => #{ + type => string + } + } + } + } + } + }, + responses => #{ + <<"201">> => #{ + description => <<"Created">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => object, + required => [user_id], + properties => #{ + user_id => #{ + type => string + } + } + } + } + } + }, + <<"400">> => #{ + description => <<"Bad Request">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + }, + get => #{ + description => "List users", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => #{ + type => object, + required => [user_id], + properties => #{ + user_id => #{ + type => string + } + } + } + } + } + } + } + } + } + }, + {"/authentication/authenticators/:id/users", Metadata, users}. + +users2_api() -> + Metadata = #{ + patch => #{ + description => "Update user", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + requestBody => #{ + content => #{ + 'application/json' => #{ + schema => #{ + type => object, + required => [password], + properties => #{ + password => #{ + type => string + } + } + } + } + } + }, + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => #{ + type => object, + required => [user_id], + properties => #{ + user_id => #{ + type => string + } + } + } + } + } + } + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + }, + get => #{ + description => "Get user info", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"200">> => #{ + description => <<"OK">>, + content => #{ + 'application/json' => #{ + schema => #{ + type => array, + items => #{ + type => object, + required => [user_id], + properties => #{ + user_id => #{ + type => string + } + } + } + } + } + } + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + }, + delete => #{ + description => "Delete user", + parameters => [ + #{ + name => id, + in => path, + schema => #{ + type => string + }, + required => true + }, + #{ + name => user_id, + in => path, + schema => #{ + type => string + }, + required => true + } + ], + responses => #{ + <<"204">> => #{ + description => <<"No Content">> + }, + <<"404">> => #{ + description => <<"Not Found">>, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>) + } + } + } + } + } + }, + {"/authentication/authenticators/:id/users/:user_id", Metadata, users2}. + + definitions() -> AuthenticatorDef = #{ + oneOf => [ minirest:ref(<<"password_based">>) + , minirest:ref(<<"jwt">>) + , minirest:ref(<<"scram">>) + ] + }, + + ReturnedAuthenticatorDef = #{ allOf => [ #{ type => object, - required => [name], properties => #{ - name => #{ - type => string, - example => "exmaple" + id => #{ + type => string } } }, @@ -111,99 +606,108 @@ definitions() -> oneOf => [ minirest:ref(<<"password_based">>) , minirest:ref(<<"jwt">>) , minirest:ref(<<"scram">>) - ] + ] } ] }, PasswordBasedDef = #{ - type => object, - properties => #{ - mechanism => #{ - type => string, - enum => [<<"password-based">>], - example => <<"password-based">> + allOf => [ + #{ + type => object, + required => [name, mechanism], + properties => #{ + name => #{ + type => string, + example => "exmaple" + }, + mechanism => #{ + type => string, + enum => [<<"password-based">>], + example => <<"password-based">> + } + } }, - config => #{ + #{ oneOf => [ minirest:ref(<<"password_based_built_in_database">>) , minirest:ref(<<"password_based_mysql">>) , minirest:ref(<<"password_based_pgsql">>) , minirest:ref(<<"password_based_http_server">>) - ] + ] } - } + ] }, JWTDef = #{ type => object, + required => [name, mechanism], properties => #{ + name => #{ + type => string, + example => "exmaple" + }, mechanism => #{ type => string, enum => [<<"jwt">>], example => <<"jwt">> }, - config => #{ + use_jwks => #{ + type => boolean, + default => false, + example => false + }, + algorithm => #{ + type => string, + enum => [<<"hmac-based">>, <<"public-key">>], + default => <<"hmac-based">>, + example => <<"hmac-based">> + }, + secret => #{ + type => string + }, + secret_base64_encoded => #{ + type => boolean, + default => false + }, + certificate => #{ + type => string + }, + verify_claims => #{ type => object, - properties => #{ - use_jwks => #{ - type => boolean, - default => false, - example => false - }, - algorithm => #{ - type => string, - enum => [<<"hmac-based">>, <<"public-key">>], - default => <<"hmac-based">>, - example => <<"hmac-based">> - }, - secret => #{ - type => string - }, - secret_base64_encoded => #{ - type => boolean, - default => false - }, - certificate => #{ - type => string - }, - verify_claims => #{ - type => object, - additionalProperties => #{ - type => string - } - }, - ssl => minirest:ref(<<"ssl">>) - } - } + additionalProperties => #{ + type => string + } + }, + ssl => minirest:ref(<<"ssl">>) } }, SCRAMDef = #{ type => object, + required => [name, mechanism], properties => #{ + name => #{ + type => string, + example => "exmaple" + }, mechanism => #{ type => string, enum => [<<"scram">>], example => <<"scram">> }, - config => #{ - type => object, - properties => #{ - server_type => #{ - type => string, - enum => [<<"built-in-database">>], - default => <<"built-in-database">> - }, - algorithm => #{ - type => string, - enum => [<<"sha256">>, <<"sha512">>], - default => <<"sha256">> - }, - iteration_count => #{ - type => integer, - default => 4096 - } - } + server_type => #{ + type => string, + enum => [<<"built-in-database">>], + default => <<"built-in-database">> + }, + algorithm => #{ + type => string, + enum => [<<"sha256">>, <<"sha512">>], + default => <<"sha256">> + }, + iteration_count => #{ + type => integer, + default => 4096 } } }, @@ -410,7 +914,7 @@ definitions() -> properties => #{ enable => #{ type => boolean, - default => false + default => false }, hostname => #{ type => string @@ -420,7 +924,22 @@ definitions() -> } }, + ErrorDef = #{ + type => object, + properties => #{ + code => #{ + type => string, + enum => [<<"NOT_FOUND">>], + example => <<"NOT_FOUND">> + }, + message => #{ + type => string + } + } + }, + [ #{<<"authenticator">> => AuthenticatorDef} + , #{<<"returned_authenticator">> => ReturnedAuthenticatorDef} , #{<<"password_based">> => PasswordBasedDef} , #{<<"jwt">> => JWTDef} , #{<<"scram">> => SCRAMDef} @@ -430,4 +949,163 @@ definitions() -> , #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef} , #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef} , #{<<"ssl">> => SSLDef} + , #{<<"error">> => ErrorDef} ]. + +authenticators(post, Request) -> + {ok, Body, _} = cowboy_req:read_body(Request), + AuthenticatorConfig = emqx_json:decode(Body, [return_maps]), + Config = #{<<"emqx_authn">> => #{ + <<"authenticators">> => [AuthenticatorConfig] + }}, + NConfig = hocon_schema:check_plain(emqx_authn_schema, Config, + #{nullable => true}), + #{emqx_authn := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig), + case emqx_authn:create_authenticator(?CHAIN, NAuthenticatorConfig) of + {ok, Authenticator2} -> + {201, Authenticator2}; + {error, Reason} -> + serialize_error(Reason) + end; +authenticators(get, _Request) -> + {ok, Authenticators} = emqx_authn:list_authenticators(?CHAIN), + {200, Authenticators}. + +authenticators2(get, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + case emqx_authn:lookup_authenticator(?CHAIN, AuthenticatorID) of + {ok, Authenticator} -> + {200, Authenticator}; + {error, Reason} -> + serialize_error(Reason) + end; +authenticators2(put, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + {ok, Body, _} = cowboy_req:read_body(Request), + AuthenticatorConfig = emqx_json:decode(Body, [return_maps]), + Config = #{<<"emqx_authn">> => #{ + <<"authenticators">> => [AuthenticatorConfig] + }}, + NConfig = hocon_schema:check_plain(emqx_authn_schema, Config, + #{nullable => true}), + #{emqx_authn := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig), + case emqx_authn:update_or_create_authenticator(?CHAIN, AuthenticatorID, NAuthenticatorConfig) of + {ok, Authenticator} -> + {200, Authenticator}; + {error, Reason} -> + serialize_error(Reason) + end; +authenticators2(delete, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + case emqx_authn:delete_authenticator(?CHAIN, AuthenticatorID) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) + end. + +position(post, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + {ok, Body, _} = cowboy_req:read_body(Request), + case emqx_json:decode(Body, [return_maps]) of + #{<<"position">> := Position} when is_integer(Position) -> + case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) + end; + _ -> + serialize_error({missing_parameter, position}) + end. + +import_users(post, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + {ok, Body, _} = cowboy_req:read_body(Request), + case emqx_json:decode(Body, [return_maps]) of + #{<<"filename">> := Filename} when is_binary(Filename) -> + case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) + end; + _ -> + serialize_error({missing_parameter, filename}) + end. + +users(post, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + {ok, Body, _} = cowboy_req:read_body(Request), + case emqx_json:decode(Body, [return_maps]) of + #{<<"user_id">> := _, + <<"password">> := _} = UserInfo -> + case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of + {ok, User} -> + {201, User}; + {error, Reason} -> + serialize_error(Reason) + end; + _ -> + serialize_error({missing_parameter, user_id}) + end; +users(get, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + case emqx_authn:list_users(?CHAIN, AuthenticatorID) of + {ok, Users} -> + {200, Users}; + {error, Reason} -> + serialize_error(Reason) + end. + +users2(patch, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + UserID = cowboy_req:binding(user_id, Request), + {ok, Body, _} = cowboy_req:read_body(Request), + case emqx_json:decode(Body, [return_maps]) of + #{<<"password">> := _} = UserInfo -> + case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserID, UserInfo) of + {ok, User} -> + {200, User}; + {error, Reason} -> + serialize_error(Reason) + end; + _ -> + serialize_error({missing_parameter, password}) + end; +users2(get, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + UserID = cowboy_req:binding(user_id, Request), + case emqx_authn:lookup_user(?CHAIN, AuthenticatorID, UserID) of + {ok, User} -> + {200, User}; + {error, Reason} -> + serialize_error(Reason) + end; +users2(delete, Request) -> + AuthenticatorID = cowboy_req:binding(id, Request), + UserID = cowboy_req:binding(user_id, Request), + case emqx_authn:delete_user(?CHAIN, AuthenticatorID, UserID) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) + end. + +serialize_error({not_found, {authenticator, ID}}) -> + {404, #{code => <<"NOT_FOUND">>, + message => list_to_binary(io_lib:format("Authenticator '~s' does not exist", [ID]))}}; +serialize_error(name_has_be_used) -> + {409, #{code => <<"ALREADY_EXISTS">>, + message => <<"Name has be used">>}}; +serialize_error(out_of_range) -> + {400, #{code => <<"OUT_OF_RANGE">>, + message => <<"Out of range">>}}; +serialize_error({missing_parameter, Name}) -> + {400, #{code => <<"MISSING_PARAMETER">>, + message => list_to_binary( + io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name]) + )}}; +serialize_error(_) -> + {400, #{code => <<"BAD_REQUEST">>, + message => <<"Todo">>}}. \ No newline at end of file diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_authn/src/emqx_authn_app.erl index 225969cd2..a279ff5e7 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_authn/src/emqx_authn_app.erl @@ -42,16 +42,16 @@ initialize() -> authenticators => []}), initialize(AuthNConfig). -initialize(#{enable := Enable, authenticators := Authenticators}) -> +initialize(#{enable := Enable, authenticators := AuthenticatorsConfig}) -> {ok, _} = emqx_authn:create_chain(#{id => ?CHAIN}), - initialize_authenticators(Authenticators), + initialize_authenticators(AuthenticatorsConfig), Enable =:= true andalso emqx_authn:enable(), ok. initialize_authenticators([]) -> ok; -initialize_authenticators([#{name := Name} = Authenticator | More]) -> - case emqx_authn:create_authenticator(?CHAIN, Authenticator) of +initialize_authenticators([#{name := Name} = AuthenticatorConfig | More]) -> + case emqx_authn:create_authenticator(?CHAIN, AuthenticatorConfig) of {ok, _} -> initialize_authenticators(More); {error, Reason} -> diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index 7ed5a9999..030867ed7 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -25,57 +25,34 @@ , fields/1 ]). --reflect_type([ authenticator_name/0 - ]). +-export([ authenticator_name/1 + ]). -structs() -> ["emqx_authn"]. +structs() -> [ "emqx_authn" ]. fields("emqx_authn") -> [ {enable, fun enable/1} , {authenticators, fun authenticators/1} - ]; - -fields('password-based') -> - [ {name, fun authenticator_name/1} - , {mechanism, {enum, ['password-based']}} - , {config, hoconsc:t(hoconsc:union( - [ hoconsc:ref(emqx_authn_mnesia, config) - , hoconsc:ref(emqx_authn_mysql, config) - , hoconsc:ref(emqx_authn_pgsql, config) - , hoconsc:ref(emqx_authn_http, get) - , hoconsc:ref(emqx_authn_http, post) - ]))} - ]; - -fields(jwt) -> - [ {name, fun authenticator_name/1} - , {mechanism, {enum, [jwt]}} - , {config, hoconsc:t(hoconsc:union( - [ hoconsc:ref(emqx_authn_jwt, 'hmac-based') - , hoconsc:ref(emqx_authn_jwt, 'public-key') - , hoconsc:ref(emqx_authn_jwt, 'jwks') - ]))} - ]; - -fields(scram) -> - [ {name, fun authenticator_name/1} - , {mechanism, {enum, [scram]}} - , {config, hoconsc:t(hoconsc:union( - [ hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config) - ]))} ]. +authenticator_name(type) -> binary(); +authenticator_name(nullable) -> false; +authenticator_name(_) -> undefined. + enable(type) -> boolean(); -enable(defualt) -> false; +enable(default) -> false; enable(_) -> undefined. authenticators(type) -> - hoconsc:array({union, [ hoconsc:ref(?MODULE, 'password-based') - , hoconsc:ref(?MODULE, jwt) - , hoconsc:ref(?MODULE, scram)]}); + hoconsc:array({union, [ hoconsc:ref(emqx_authn_mnesia, config) + , hoconsc:ref(emqx_authn_mysql, config) + , hoconsc:ref(emqx_authn_pgsql, config) + , hoconsc:ref(emqx_authn_http, get) + , hoconsc:ref(emqx_authn_http, post) + , hoconsc:ref(emqx_authn_jwt, 'hmac-based') + , hoconsc:ref(emqx_authn_jwt, 'public-key') + , hoconsc:ref(emqx_authn_jwt, 'jwks') + , hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config) + ]}); authenticators(default) -> []; authenticators(_) -> undefined. - -authenticator_name(type) -> authenticator_name(); -authenticator_name(nullable) -> false; -authenticator_name(_) -> undefined. 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 14b25558a..9b147dceb 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 @@ -26,8 +26,8 @@ , fields/1 ]). --export([ create/3 - , update/4 +-export([ create/1 + , update/2 , authenticate/2 , destroy/1 ]). @@ -71,7 +71,9 @@ mnesia(copy) -> structs() -> [config]. fields(config) -> - [ {server_type, fun server_type/1} + [ {name, fun emqx_authn_schema:authenticator_name/1} + , {mechanism, {enum, [scram]}} + , {server_type, fun server_type/1} , {algorithm, fun algorithm/1} , {iteration_count, fun iteration_count/1} ]. @@ -92,16 +94,18 @@ iteration_count(_) -> undefined. %% APIs %%------------------------------------------------------------------------------ -create(ChainID, Authenticator, #{algorithm := Algorithm, - iteration_count := IterationCount}) -> - State = #{user_group => {ChainID, Authenticator}, +create(#{ algorithm := Algorithm + , iteration_count := IterationCount + , '_unique' := Unique + }) -> + State = #{user_group => Unique, algorithm => Algorithm, iteration_count => IterationCount}, {ok, State}. -update(_ChainID, _Authenticator, _Config, _State) -> - {error, update_not_suppored}. - +update(Config, #{user_group := Unique}) -> + create(Config#{'_unique' => Unique}). + authenticate(#{auth_method := AuthMethod, auth_data := AuthData, auth_cache := AuthCache}, State) -> 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 48d123b70..964e78a02 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -26,8 +26,8 @@ , validations/0 ]). --export([ create/3 - , update/4 +-export([ create/1 + , update/2 , authenticate/2 , destroy/1 ]). @@ -57,7 +57,9 @@ fields(post) -> ] ++ common_fields(). common_fields() -> - [ {server_type, {enum, ['http-server']}} + [ {name, fun emqx_authn_schema:authenticator_name/1} + , {mechanism, {enum, ['password-based']}} + , {server_type, {enum, ['http-server']}} , {url, fun url/1} , {form_data, fun form_data/1} , {request_timeout, fun request_timeout/1} @@ -105,37 +107,41 @@ request_timeout(_) -> undefined. %% APIs %%------------------------------------------------------------------------------ -create(ChainID, AuthenticatorName, - #{method := Method, - url := URL, - headers := Headers, - form_data := FormData, - request_timeout := RequestTimeout} = Config) -> +create(#{ method := Method + , url := URL + , headers := Headers + , form_data := FormData + , request_timeout := RequestTimeout + , '_unique' := Unique + } = Config) -> #{path := Path, query := Query} = URIMap = parse_url(URL), - State = #{method => Method, - path => Path, - base_query => cow_qs:parse_qs(list_to_binary(Query)), - headers => normalize_headers(Headers), - form_data => maps:to_list(FormData), - request_timeout => RequestTimeout}, - ResourceID = <>, - case emqx_resource:create_local(ResourceID, + State = #{ method => Method + , path => Path + , base_query => cow_qs:parse_qs(list_to_binary(Query)) + , headers => normalize_headers(Headers) + , form_data => maps:to_list(FormData) + , request_timeout => RequestTimeout + }, + case emqx_resource:create_local(Unique, emqx_connector_http, Config#{base_url => maps:remove(query, URIMap), pool_type => random}) of {ok, _} -> - {ok, State#{resource_id => ResourceID}}; + {ok, State#{resource_id => Unique}}; {error, already_created} -> - {ok, State#{resource_id => ResourceID}}; + {ok, State#{resource_id => Unique}}; {error, Reason} -> {error, Reason} end. -update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) -> - case emqx_resource:update_local(ResourceID, emqx_connector_http, Config, []) of - {ok, _} -> {ok, State}; - {error, Reason} -> {error, Reason} +update(Config, State) -> + case create(Config) of + {ok, NewState} -> + ok = destroy(State), + {ok, NewState}; + {error, Reason} -> + {error, Reason} end. authenticate(#{auth_method := _}, _) -> 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 2e4e473c0..fe034994e 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl @@ -24,8 +24,8 @@ , fields/1 ]). --export([ create/3 - , update/4 +-export([ create/1 + , update/2 , authenticate/2 , destroy/1 ]). @@ -48,27 +48,24 @@ fields('hmac-based') -> , {algorithm, {enum, ['hmac-based']}} , {secret, fun secret/1} , {secret_base64_encoded, fun secret_base64_encoded/1} - , {verify_claims, fun verify_claims/1} - ]; + ] ++ common_fields(); fields('public-key') -> [ {use_jwks, {enum, [false]}} , {algorithm, {enum, ['public-key']}} , {certificate, fun certificate/1} - , {verify_claims, fun verify_claims/1} - ]; + ] ++ common_fields(); fields('jwks') -> [ {use_jwks, {enum, [true]}} , {endpoint, fun endpoint/1} , {refresh_interval, fun refresh_interval/1} - , {verify_claims, fun verify_claims/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}} @@ -82,6 +79,12 @@ fields(ssl_enable) -> fields(ssl_disable) -> [ {enable, #{type => false}} ]. +common_fields() -> + [ {name, fun emqx_authn_schema:authenticator_name/1} + , {mechanism, {enum, [jwt]}} + , {verify_claims, fun verify_claims/1} + ]. + secret(type) -> string(); secret(_) -> undefined. @@ -129,18 +132,18 @@ verify_claims(_) -> undefined. %% APIs %%------------------------------------------------------------------------------ -create(_ChainID, _AuthenticatorName, Config) -> - create(Config). +create(#{verify_claims := VerifyClaims} = Config) -> + create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}). -update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, #{jwk := Connector}) +update(#{use_jwks := false} = Config, #{jwk := Connector}) when is_pid(Connector) -> _ = emqx_authn_jwks_connector:stop(Connector), create(Config); -update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, _) -> +update(#{use_jwks := false} = Config, _) -> create(Config); -update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, #{jwk := Connector} = State) +update(#{use_jwks := true} = Config, #{jwk := Connector} = State) when is_pid(Connector) -> ok = emqx_authn_jwks_connector:update(Connector, Config), case maps:get(verify_cliams, Config, undefined) of @@ -150,7 +153,7 @@ update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, #{jwk := Conn {ok, State#{verify_claims => handle_verify_claims(VerifyClaims)}} end; -update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, _) -> +update(#{use_jwks := true} = Config, _) -> create(Config). authenticate(#{auth_method := _}, _) -> @@ -181,9 +184,6 @@ destroy(_) -> %% Internal functions %%-------------------------------------------------------------------- -create(#{verify_claims := VerifyClaims} = Config) -> - create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}). - create2(#{use_jwks := false, algorithm := 'hmac-based', secret := Secret0, 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 4b1bcbb76..3d5ebe7a0 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl @@ -23,8 +23,8 @@ -export([ structs/0, fields/1 ]). --export([ create/3 - , update/4 +-export([ create/1 + , update/2 , authenticate/2 , destroy/1 ]). @@ -39,7 +39,7 @@ -type user_id_type() :: clientid | username. --type user_group() :: {chain_id(), authenticator_name()}. +-type user_group() :: {binary(), binary()}. -type user_id() :: binary(). -record(user_info, @@ -81,8 +81,10 @@ mnesia(copy) -> structs() -> [config]. fields(config) -> - [ {server_type, {enum, ['built-in-database']}} - , {user_id_type, fun user_id_type/1} + [ {name, fun emqx_authn_schema:authenticator_name/1} + , {mechanism, {enum, ['password-based']}} + , {server_type, {enum, ['built-in-database']}} + , {user_id_type, fun user_id_type/1} , {password_hash_algorithm, fun password_hash_algorithm/1} ]; @@ -111,25 +113,29 @@ salt_rounds(_) -> undefined. %% APIs %%------------------------------------------------------------------------------ -create(ChainID, AuthenticatorName, #{user_id_type := Type, - password_hash_algorithm := #{name := bcrypt, - salt_rounds := SaltRounds}}) -> +create(#{ user_id_type := Type + , password_hash_algorithm := #{name := bcrypt, + salt_rounds := SaltRounds} + , '_unique' := Unique + }) -> {ok, _} = application:ensure_all_started(bcrypt), - State = #{user_group => {ChainID, AuthenticatorName}, + State = #{user_group => Unique, user_id_type => Type, password_hash_algorithm => bcrypt, salt_rounds => SaltRounds}, {ok, State}; -create(ChainID, AuthenticatorName, #{user_id_type := Type, - password_hash_algorithm := #{name := Name}}) -> - State = #{user_group => {ChainID, AuthenticatorName}, +create(#{ user_id_type := Type + , password_hash_algorithm := #{name := Name} + , '_unique' := Unique + }) -> + State = #{user_group => Unique, user_id_type => Type, password_hash_algorithm => Name}, {ok, State}. -update(ChainID, AuthenticatorName, Config, _State) -> - create(ChainID, AuthenticatorName, Config). +update(Config, #{user_group := Unique}) -> + create(Config#{'_unique' => Unique}). authenticate(#{auth_method := _}, _) -> ignore; 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 c112b9666..200d7afc4 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl @@ -21,10 +21,12 @@ -behaviour(hocon_schema). --export([ structs/0, fields/1 ]). +-export([ structs/0 + , fields/1 + ]). --export([ create/3 - , update/4 +-export([ create/1 + , update/2 , authenticate/2 , destroy/1 ]). @@ -36,7 +38,9 @@ structs() -> [config]. fields(config) -> - [ {server_type, {enum, [mysql]}} + [ {name, fun emqx_authn_schema:authenticator_name/1} + , {mechanism, {enum, ['password-based']}} + , {server_type, {enum, [mysql]}} , {password_hash_algorithm, fun password_hash_algorithm/1} , {salt_position, fun salt_position/1} , {query, fun query/1} @@ -70,34 +74,41 @@ query(nullable) -> false; query(_) -> undefined. query_timeout(type) -> integer(); -query_timeout(defualt) -> 5000; +query_timeout(default) -> 5000; query_timeout(_) -> undefined. %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ -create(ChainID, AuthenticatorName, - #{query := Query0, - password_hash_algorithm := Algorithm} = Config) -> +create(#{ password_hash_algorithm := Algorithm + , salt_position := SaltPosition + , query := Query0 + , query_timeout := QueryTimeout + , '_unique' := Unique + } = Config) -> {Query, PlaceHolders} = parse_query(Query0), - ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, AuthenticatorName])), - State = #{query => Query, + State = #{password_hash_algorithm => Algorithm, + salt_position => SaltPosition, + query => Query, placeholders => PlaceHolders, - password_hash_algorithm => Algorithm}, - case emqx_resource:create_local(ResourceID, emqx_connector_mysql, Config) of + query_timeout => QueryTimeout}, + case emqx_resource:create_local(Unique, emqx_connector_mysql, Config) of {ok, _} -> - {ok, State#{resource_id => ResourceID}}; + {ok, State#{resource_id => Unique}}; {error, already_created} -> - {ok, State#{resource_id => ResourceID}}; + {ok, State#{resource_id => Unique}}; {error, Reason} -> {error, Reason} end. -update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) -> - case emqx_resource:update_local(ResourceID, emqx_connector_mysql, Config, []) of - {ok, _} -> {ok, State}; - {error, Reason} -> {error, Reason} +update(Config, State) -> + case create(Config) of + {ok, NewState} -> + ok = destroy(State), + {ok, NewState}; + {error, Reason} -> + {error, Reason} end. authenticate(#{auth_method := _}, _) -> 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 700298c46..100e5cce7 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl @@ -23,8 +23,8 @@ -export([ structs/0, fields/1 ]). --export([ create/3 - , update/4 +-export([ create/1 + , update/2 , authenticate/2 , destroy/1 ]). @@ -36,7 +36,9 @@ structs() -> [config]. fields(config) -> - [ {server_type, {enum, [pgsql]}} + [ {name, fun emqx_authn_schema:authenticator_name/1} + , {mechanism, {enum, ['password-based']}} + , {server_type, {enum, [pgsql]}} , {password_hash_algorithm, fun password_hash_algorithm/1} , {salt_position, {enum, [prefix, suffix]}} , {query, fun query/1} @@ -54,26 +56,32 @@ query(_) -> undefined. %% APIs %%------------------------------------------------------------------------------ -create(ChainID, ServiceName, #{query := Query0, - password_hash_algorithm := Algorithm} = Config) -> +create(#{ query := Query0 + , password_hash_algorithm := Algorithm + , salt_position := SaltPosition + , '_unique' := Unique + } = Config) -> {Query, PlaceHolders} = parse_query(Query0), - ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, ServiceName])), State = #{query => Query, placeholders => PlaceHolders, - password_hash_algorithm => Algorithm}, - case emqx_resource:create_local(ResourceID, emqx_connector_pgsql, Config) of + password_hash_algorithm => Algorithm, + salt_position => SaltPosition}, + case emqx_resource:create_local(Unique, emqx_connector_pgsql, Config) of {ok, _} -> - {ok, State#{resource_id => ResourceID}}; + {ok, State#{resource_id => Unique}}; {error, already_created} -> - {ok, State#{resource_id => ResourceID}}; + {ok, State#{resource_id => Unique}}; {error, Reason} -> {error, Reason} end. -update(_ChainID, _ServiceName, Config, #{resource_id := ResourceID} = State) -> - case emqx_resource:update_local(ResourceID, emqx_connector_pgsql, Config, []) of - {ok, _} -> {ok, State}; - {error, Reason} -> {error, Reason} +update(Config, State) -> + case create(Config) of + {ok, NewState} -> + ok = destroy(State), + {ok, NewState}; + {error, Reason} -> + {error, Reason} end. authenticate(#{auth_method := _}, _) -> From 7febcb852a8c41bdf0868c1b9a70bd7ddb83b5b6 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 27 Jul 2021 09:54:08 +0800 Subject: [PATCH 6/7] feat(authn http api): add test case and improve http api spec --- apps/emqx_authn/etc/emqx_authn.conf | 12 +- apps/emqx_authn/include/emqx_authn.hrl | 1 - apps/emqx_authn/src/emqx_authn.erl | 42 ++- apps/emqx_authn/src/emqx_authn_api.erl | 340 +++++++++--------- .../emqx_enhanced_authn_scram_mnesia.erl | 6 +- .../src/simple_authn/emqx_authn_mnesia.erl | 6 +- .../simple_authn/emqx_authn_other_schema.erl | 58 +++ apps/emqx_authn/test/emqx_authn_SUITE.erl | 60 ++-- apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl | 26 +- .../test/emqx_authn_mnesia_SUITE.erl | 94 +++-- 10 files changed, 361 insertions(+), 284 deletions(-) create mode 100644 apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl diff --git a/apps/emqx_authn/etc/emqx_authn.conf b/apps/emqx_authn/etc/emqx_authn.conf index 30ef427ae..7c1bc711c 100644 --- a/apps/emqx_authn/etc/emqx_authn.conf +++ b/apps/emqx_authn/etc/emqx_authn.conf @@ -1,11 +1,11 @@ emqx_authn: { enable: false authenticators: [ - { - name: "authenticator1" - mechanism: password-based - server_type: built-in-database - user_id_type: clientid - } + # { + # name: "authenticator1" + # mechanism: password-based + # server_type: built-in-database + # user_id_type: clientid + # } ] } diff --git a/apps/emqx_authn/include/emqx_authn.hrl b/apps/emqx_authn/include/emqx_authn.hrl index ef3e2893c..5a8bfb66a 100644 --- a/apps/emqx_authn/include/emqx_authn.hrl +++ b/apps/emqx_authn/include/emqx_authn.hrl @@ -26,7 +26,6 @@ , provider :: module() , config :: map() , state :: map() - , version :: binary() }). -record(chain, diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl index 95ace3800..f41921e04 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_authn/src/emqx_authn.erl @@ -194,20 +194,25 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C false -> case CreateWhenNotFound of true -> - case do_create_authenticator(ChainID, AuthenticatorID, Config) of - {ok, Authenticator} -> - NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}], - ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write), - {ok, serialize_authenticator(Authenticator)}; - {error, Reason} -> - {error, Reason} - end; + case lists:keymember(NewName, 2, Authenticators) of + true -> + {error, name_has_be_used}; + false -> + case do_create_authenticator(ChainID, AuthenticatorID, Config) of + {ok, Authenticator} -> + NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}], + ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write), + {ok, serialize_authenticator(Authenticator)}; + {error, Reason} -> + {error, Reason} + end + end; false -> {error, {not_found, {authenticator, AuthenticatorID}}} end; {value, {_, _, #authenticator{provider = Provider, - state = #{version := Version} = State}}, + state = #{version := Version} = State} = Authenticator}, Others} -> case lists:keymember(NewName, 2, Others) of true -> @@ -215,12 +220,12 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C false -> case (NewProvider = authenticator_provider(Config)) =:= Provider of true -> - Unique = {ChainID, AuthenticatorID, Version}, + Unique = <>, case Provider:update(Config#{'_unique' => Unique}, State) of {ok, NewState} -> - NewAuthenticator = #authenticator{name = NewName, - config = Config, - state = switch_version(NewState)}, + NewAuthenticator = Authenticator#authenticator{name = NewName, + config = Config, + state = switch_version(NewState)}, NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators), ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write), {ok, serialize_authenticator(NewAuthenticator)}; @@ -228,12 +233,13 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C {error, Reason} end; false -> - case NewProvider:create(Config#{'_unique' => {ChainID, AuthenticatorID, Version}}) of + Unique = <>, + case NewProvider:create(Config#{'_unique' => Unique}) of {ok, NewState} -> - NewAuthenticator = #authenticator{name = NewName, - provider = NewProvider, - config = Config, - state = switch_version(NewState)}, + NewAuthenticator = Authenticator#authenticator{name = NewName, + provider = NewProvider, + config = Config, + state = switch_version(NewState)}, NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators), ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write), _ = Provider:destroy(State), diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index c87df2dc5..1c4d0575d 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -29,6 +29,56 @@ , users2/2 ]). +-define(EXAMPLE_1, #{name => <<"example 1">>, + mechanism => <<"password-based">>, + server_type => <<"built-in-example">>, + user_id_type => <<"username">>, + password_hash_algorithm => #{ + name => <<"sha256">> + }}). + +-define(EXAMPLE_2, #{name => <<"example 2">>, + mechanism => <<"password-based">>, + server_type => <<"http-server">>, + method => <<"post">>, + url => <<"http://localhost:80/login">>, + headers => #{ + <<"content-type">> => <<"application/json">> + }, + form_data => #{ + <<"username">> => <<"${mqtt-username}">>, + <<"password">> => <<"${mqtt-password}">> + }}). + +-define(EXAMPLE_3, #{name => <<"example 3">>, + mechanism => <<"jwt">>, + use_jwks => false, + algorithm => <<"hmac-based">>, + secret => <<"mysecret">>, + secret_base64_encoded => false, + verify_claims => #{ + <<"username">> => <<"${mqtt-username}">> + }}). + +-define(ERR_RESPONSE(Desc), #{description => Desc, + content => #{ + 'application/json' => #{ + schema => minirest:ref(<<"error">>), + examples => #{ + example1 => #{ + summary => <<"Not Found">>, + value => #{code => <<"NOT_FOUND">>, message => <<"Authenticator '67e4c9d3' does not exist">>} + }, + example2 => #{ + summary => <<"Conflict">>, + value => #{code => <<"ALREADY_EXISTS">>, message => <<"Name has be used">>} + }, + example3 => #{ + summary => <<"Bad Request 1">>, + value => #{code => <<"OUT_OF_RANGE">>, message => <<"Out of range">>} + } + }}}}). + api_spec() -> {[ authenticators_api() , authenticators_api2() @@ -39,40 +89,6 @@ api_spec() -> ], definitions()}. authenticators_api() -> - Example1 = #{name => <<"example">>, - mechanism => <<"password-based">>, - config => #{ - server_type => <<"built-in-example">>, - user_id_type => <<"username">>, - password_hash_algorithm => #{ - name => <<"sha256">> - } - }}, - Example2 = #{name => <<"example">>, - mechanism => <<"password-based">>, - config => #{ - server_type => <<"http-server">>, - method => <<"post">>, - url => <<"http://localhost:80/login">>, - headers => #{ - <<"content-type">> => <<"application/json">> - }, - form_data => #{ - <<"username">> => <<"${mqtt-username}">>, - <<"password">> => <<"${mqtt-password}">> - } - }}, - Example3 = #{name => <<"example">>, - mechanism => <<"jwt">>, - config => #{ - use_jwks => false, - algorithm => <<"hmac-based">>, - secret => <<"mysecret">>, - secret_base64_encoded => false, - verify_claims => #{ - <<"username">> => <<"${mqtt-username}">> - } - }}, Metadata = #{ post => #{ description => "Create authenticator", @@ -83,15 +99,15 @@ authenticators_api() -> examples => #{ default => #{ summary => <<"Default">>, - value => emqx_json:encode(Example1) + value => emqx_json:encode(?EXAMPLE_1) }, http => #{ summary => <<"Authentication provided by HTTP Server">>, - value => emqx_json:encode(Example2) + value => emqx_json:encode(?EXAMPLE_2) }, jwt => #{ summary => <<"JWT Authentication">>, - value => emqx_json:encode(Example3) + value => emqx_json:encode(?EXAMPLE_3) } } } @@ -102,10 +118,26 @@ authenticators_api() -> description => <<"Created">>, content => #{ 'application/json' => #{ - schema => minirest:ref(<<"returned_authenticator">>) + schema => minirest:ref(<<"returned_authenticator">>), + examples => #{ + example1 => #{ + summary => <<"Example 1">>, + value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1)) + }, + example2 => #{ + summary => <<"Example 2">>, + value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2)) + }, + example3 => #{ + summary => <<"Example 3">>, + value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3)) + } + } } } - } + }, + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"409">> => ?ERR_RESPONSE(<<"Conflict">>) } }, get => #{ @@ -118,6 +150,15 @@ authenticators_api() -> schema => #{ type => array, items => minirest:ref(<<"returned_authenticator">>) + }, + examples => #{ + example1 => #{ + summary => <<"Example 1">>, + value => emqx_json:encode([ maps:put(id, <<"example 1">>, ?EXAMPLE_1) + , maps:put(id, <<"example 2">>, ?EXAMPLE_2) + , maps:put(id, <<"example 3">>, ?EXAMPLE_3) + ]) + } } } } @@ -146,18 +187,25 @@ authenticators_api2() -> description => <<"OK">>, content => #{ 'application/json' => #{ - schema => minirest:ref(<<"returned_authenticator">>) + schema => minirest:ref(<<"returned_authenticator">>), + examples => #{ + example1 => #{ + summary => <<"Example 1">>, + value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1)) + }, + example2 => #{ + summary => <<"Example 2">>, + value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2)) + }, + example3 => #{ + summary => <<"Example 3">>, + value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3)) + } + } } } }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } }, put => #{ @@ -180,6 +228,16 @@ authenticators_api2() -> , minirest:ref(<<"jwt">>) , minirest:ref(<<"scram">>) ] + }, + examples => #{ + example1 => #{ + summary => <<"Example 1">>, + value => emqx_json:encode(?EXAMPLE_1) + }, + example2 => #{ + summary => <<"Example 2">>, + value => emqx_json:encode(?EXAMPLE_2) + } } } } @@ -189,18 +247,27 @@ authenticators_api2() -> description => <<"OK">>, content => #{ 'application/json' => #{ - schema => minirest:ref(<<"returned_authenticator">>) + schema => minirest:ref(<<"returned_authenticator">>), + examples => #{ + example1 => #{ + summary => <<"Example 1">>, + value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1)) + }, + example2 => #{ + summary => <<"Example 2">>, + value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2)) + }, + example3 => #{ + summary => <<"Example 3">>, + value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3)) + } + } } } }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>), + <<"409">> => ?ERR_RESPONSE(<<"Conflict">>) } }, delete => #{ @@ -219,14 +286,7 @@ authenticators_api2() -> <<"204">> => #{ description => <<"No Content">> }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } } }, @@ -266,14 +326,8 @@ position_api() -> <<"204">> => #{ description => <<"No Content">> }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } } }, @@ -312,22 +366,8 @@ import_users_api() -> <<"204">> => #{ description => <<"No Content">> }, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } } }, @@ -382,14 +422,8 @@ users_api() -> } } }, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } }, get => #{ @@ -423,7 +457,8 @@ users_api() -> } } } - } + }, + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } } }, @@ -486,14 +521,8 @@ users2_api() -> } } }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"400">> => ?ERR_RESPONSE(<<"Bad Request">>), + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } }, get => #{ @@ -536,14 +565,7 @@ users2_api() -> } } }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } }, delete => #{ @@ -570,14 +592,7 @@ users2_api() -> <<"204">> => #{ description => <<"No Content">> }, - <<"404">> => #{ - description => <<"Not Found">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>) - } - } - } + <<"404">> => ?ERR_RESPONSE(<<"Not Found">>) } } }, @@ -1007,47 +1022,43 @@ authenticators2(delete, Request) -> position(post, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), - case emqx_json:decode(Body, [return_maps]) of - #{<<"position">> := Position} when is_integer(Position) -> - case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of - ok -> - {204}; - {error, Reason} -> - serialize_error(Reason) - end; - _ -> - serialize_error({missing_parameter, position}) + NBody = emqx_json:decode(Body, [return_maps]), + Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"position">> => NBody}, + #{nullable => true}, ["position"]), + #{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config), + case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) end. import_users(post, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), - case emqx_json:decode(Body, [return_maps]) of - #{<<"filename">> := Filename} when is_binary(Filename) -> - case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of - ok -> - {204}; - {error, Reason} -> - serialize_error(Reason) - end; - _ -> - serialize_error({missing_parameter, filename}) + NBody = emqx_json:decode(Body, [return_maps]), + Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"filename">> => NBody}, + #{nullable => true}, ["filename"]), + #{filename := #{filename := Filename}} = emqx_map_lib:unsafe_atom_key_map(Config), + case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of + ok -> + {204}; + {error, Reason} -> + serialize_error(Reason) end. users(post, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), {ok, Body, _} = cowboy_req:read_body(Request), - case emqx_json:decode(Body, [return_maps]) of - #{<<"user_id">> := _, - <<"password">> := _} = UserInfo -> - case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of - {ok, User} -> - {201, User}; - {error, Reason} -> - serialize_error(Reason) - end; - _ -> - serialize_error({missing_parameter, user_id}) + NBody = emqx_json:decode(Body, [return_maps]), + Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"user_info">> => NBody}, + #{nullable => true}, ["user_info"]), + #{user_info := UserInfo} = emqx_map_lib:unsafe_atom_key_map(Config), + case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of + {ok, User} -> + {201, User}; + {error, Reason} -> + serialize_error(Reason) end; users(get, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), @@ -1062,16 +1073,15 @@ users2(patch, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), UserID = cowboy_req:binding(user_id, Request), {ok, Body, _} = cowboy_req:read_body(Request), - case emqx_json:decode(Body, [return_maps]) of - #{<<"password">> := _} = UserInfo -> - case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserID, UserInfo) of - {ok, User} -> - {200, User}; - {error, Reason} -> - serialize_error(Reason) - end; - _ -> - serialize_error({missing_parameter, password}) + NBody = emqx_json:decode(Body, [return_maps]), + Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"new_user_info">> => NBody}, + #{nullable => true}, ["new_user_info"]), + #{new_user_info := NewUserInfo} = emqx_map_lib:unsafe_atom_key_map(Config), + case emqx_authn:update_user(?CHAIN, AuthenticatorID, UserID, NewUserInfo) of + {ok, User} -> + {200, User}; + {error, Reason} -> + serialize_error(Reason) end; users2(get, Request) -> AuthenticatorID = cowboy_req:binding(id, Request), @@ -1106,6 +1116,6 @@ serialize_error({missing_parameter, Name}) -> message => list_to_binary( io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name]) )}}; -serialize_error(_) -> +serialize_error(Reason) -> {400, #{code => <<"BAD_REQUEST">>, - message => <<"Todo">>}}. \ No newline at end of file + message => list_to_binary(io_lib:format("Todo: ~p", [Reason]))}}. \ No newline at end of file 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 9b147dceb..52c301946 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 @@ -133,8 +133,8 @@ destroy(#{user_group := UserGroup}) -> end). %% TODO: binary to atom -add_user(#{<<"user_id">> := UserID, - <<"password">> := Password}, #{user_group := UserGroup} = State) -> +add_user(#{user_id := UserID, + password := Password}, #{user_group := UserGroup} = State) -> trans( fun() -> case mnesia:read(?TAB, {UserGroup, UserID}, write) of @@ -157,7 +157,7 @@ delete_user(UserID, #{user_group := UserGroup}) -> end end). -update_user(UserID, #{<<"password">> := Password}, +update_user(UserID, #{password := Password}, #{user_group := UserGroup} = State) -> trans( fun() -> 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 3d5ebe7a0..ce845d4e3 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl @@ -178,8 +178,8 @@ import_users(Filename0, State) -> {error, {unsupported_file_format, Extension}} end. -add_user(#{<<"user_id">> := UserID, - <<"password">> := Password}, +add_user(#{user_id := UserID, + password := Password}, #{user_group := UserGroup} = State) -> trans( fun() -> @@ -203,7 +203,7 @@ delete_user(UserID, #{user_group := UserGroup}) -> end end). -update_user(UserID, #{<<"password">> := Password}, +update_user(UserID, #{password := Password}, #{user_group := UserGroup} = State) -> trans( fun() -> diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl new file mode 100644 index 000000000..0f5c8abb8 --- /dev/null +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_other_schema.erl @@ -0,0 +1,58 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authn_other_schema). + +-include("emqx_authn.hrl"). +-include_lib("typerefl/include/types.hrl"). + +-behaviour(hocon_schema). + +-export([ structs/0 + , fields/1 + ]). + +structs() -> [ "filename", "position", "user_info", "new_user_info"]. + +fields("filename") -> + [ {filename, fun filename/1} ]; +fields("position") -> + [ {position, fun position/1} ]; +fields("user_info") -> + [ {user_id, fun user_id/1} + , {password, fun password/1} + ]; +fields("new_user_info") -> + [ {password, fun password/1} + ]. + +filename(type) -> string(); +filename(nullable) -> false; +filename(_) -> undefined. + +position(type) -> integer(); +position(validate) -> [fun (Position) -> Position > 0 end]; +position(nullable) -> false; +position(_) -> undefined. + +user_id(type) -> binary(); +user_id(nullable) -> false; +user_id(_) -> undefined. + +password(type) -> binary(); +password(nullable) -> false; +password(_) -> undefined. + diff --git a/apps/emqx_authn/test/emqx_authn_SUITE.erl b/apps/emqx_authn/test/emqx_authn_SUITE.erl index 59103662c..36b0eabf3 100644 --- a/apps/emqx_authn/test/emqx_authn_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_SUITE.erl @@ -54,34 +54,44 @@ t_authenticator(_) -> AuthenticatorName1 = <<"myauthenticator1">>, AuthenticatorConfig1 = #{name => AuthenticatorName1, mechanism => 'password-based', - config => #{ - server_type => 'built-in-database', - user_id_type => username, - password_hash_algorithm => #{ - name => sha256 - }}}, - ?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)), - ?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName1)), - ?assertEqual({ok, [AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)), - ?assertEqual({error, {already_exists, {authenticator, AuthenticatorName1}}}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)), + server_type => 'built-in-database', + user_id_type => username, + password_hash_algorithm => #{ + name => sha256 + }}, + {ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1), + ?assertMatch({ok, #{name := AuthenticatorName1}}, ?AUTH:lookup_authenticator(?CHAIN, ID1)), + ?assertMatch({ok, [#{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)), + ?assertEqual({error, name_has_be_used}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)), + + AuthenticatorConfig2 = #{name => AuthenticatorName1, + mechanism => jwt, + use_jwks => false, + algorithm => 'hmac-based', + secret => <<"abcdef">>, + secret_base64_encoded => false, + verify_claims => []}, + {ok, #{name := AuthenticatorName1, id := ID1, mechanism := jwt}} = ?AUTH:update_authenticator(?CHAIN, ID1, AuthenticatorConfig2), + + ID2 = <<"random">>, + ?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)), + ?assertEqual({error, name_has_be_used}, ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig2)), AuthenticatorName2 = <<"myauthenticator2">>, - AuthenticatorConfig2 = AuthenticatorConfig1#{name => AuthenticatorName2}, - ?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)), - ?assertMatch({ok, #{id := ?CHAIN, authenticators := [AuthenticatorConfig1, AuthenticatorConfig2]}}, ?AUTH:lookup_chain(?CHAIN)), - ?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName2)), - ?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)), + AuthenticatorConfig3 = AuthenticatorConfig2#{name => AuthenticatorName2}, + {ok, #{name := AuthenticatorName2, id := ID2, secret := <<"abcdef">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3), + ?assertMatch({ok, #{name := AuthenticatorName2}}, ?AUTH:lookup_authenticator(?CHAIN, ID2)), + {ok, #{name := AuthenticatorName2, id := ID2, secret := <<"fedcba">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3#{secret := <<"fedcba">>}), - ?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)), - ?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)), - ?assertEqual(ok, ?AUTH:move_authenticator_to_the_end(?CHAIN, AuthenticatorName2)), - ?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)), - ?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 1)), - ?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)), - ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 3)), - ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 0)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)), + ?assertMatch({ok, #{id := ?CHAIN, authenticators := [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}}, ?AUTH:lookup_chain(?CHAIN)), + ?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)), + + ?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)), + ?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)), + ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 3)), + ?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 0)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)), ?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)), ok. diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index b22e72a95..7435deaa0 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -39,15 +39,14 @@ end_per_suite(_) -> t_jwt_authenticator(_) -> AuthenticatorName = <<"myauthenticator">>, - Config = #{use_jwks => false, + Config = #{name => AuthenticatorName, + mechanism => jwt, + use_jwks => false, algorithm => 'hmac-based', secret => <<"abcdef">>, secret_base64_encoded => false, verify_claims => []}, - AuthenticatorConfig = #{name => AuthenticatorName, - mechanism => jwt, - config => Config}, - ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)), + {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config), Payload = #{<<"username">> => <<"myuser">>}, JWS = generate_jws('hmac-based', Payload, <<"abcdef">>), @@ -62,11 +61,11 @@ t_jwt_authenticator(_) -> %% secret_base64_encoded Config2 = Config#{secret => base64:encode(<<"abcdef">>), secret_base64_encoded => true}, - ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config2)), + ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config2)), ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)), Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]}, - ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config3)), + ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config3)), ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)), ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)), @@ -109,7 +108,7 @@ t_jwt_authenticator(_) -> ClientInfo8 = ClientInfo#{password => JWS8}, ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo8, ok)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), ok. t_jwt_authenticator2(_) -> @@ -117,14 +116,13 @@ t_jwt_authenticator2(_) -> PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])), PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])), AuthenticatorName = <<"myauthenticator">>, - Config = #{use_jwks => false, + Config = #{name => AuthenticatorName, + mechanism => jwt, + use_jwks => false, algorithm => 'public-key', certificate => PublicKey, verify_claims => []}, - AuthenticatorConfig = #{name => AuthenticatorName, - mechanism => jwt, - config => Config}, - ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)), + {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config), Payload = #{<<"username">> => <<"myuser">>}, JWS = generate_jws('public-key', Payload, PrivateKey), @@ -133,7 +131,7 @@ t_jwt_authenticator2(_) -> ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)), ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ok)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), ok. generate_jws('hmac-based', Payload, Secret) -> diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index c8c787cf1..511d00e79 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -41,18 +41,17 @@ t_mnesia_authenticator(_) -> AuthenticatorName = <<"myauthenticator">>, AuthenticatorConfig = #{name => AuthenticatorName, mechanism => 'password-based', - config => #{ - server_type => 'built-in-database', - user_id_type => username, - password_hash_algorithm => #{ - name => sha256 - }}}, - ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)), + server_type => 'built-in-database', + user_id_type => username, + password_hash_algorithm => #{ + name => sha256 + }}, + {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig), UserInfo = #{<<"user_id">> => <<"myuser">>, <<"password">> => <<"mypass">>}, - ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)), - ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)), + ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)), + ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)), ClientInfo = #{zone => external, username => <<"myuser">>, @@ -70,39 +69,38 @@ t_mnesia_authenticator(_) -> ?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo3)), UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>}, - ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, AuthenticatorName, <<"myuser">>, UserInfo2)), + ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, UserInfo2)), ClientInfo4 = ClientInfo#{password => <<"mypass2">>}, ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo4, ok)), - ?assertEqual(ok, ?AUTH:delete_user(?CHAIN, AuthenticatorName, <<"myuser">>)), - ?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)), + ?assertEqual(ok, ?AUTH:delete_user(?CHAIN, ID, <<"myuser">>)), + ?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)), - ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)), - ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)), + ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)), + ?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), - ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)), - ?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)), + {ok, #{name := AuthenticatorName, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig), + ?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID1, <<"myuser">>)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)), ok. t_import(_) -> AuthenticatorName = <<"myauthenticator">>, AuthenticatorConfig = #{name => AuthenticatorName, mechanism => 'password-based', - config => #{ - server_type => 'built-in-database', - user_id_type => username, - password_hash_algorithm => #{ - name => sha256 - }}}, - ?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)), + server_type => 'built-in-database', + user_id_type => username, + password_hash_algorithm => #{ + name => sha256 + }}, + {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig), Dir = code:lib_dir(emqx_authn, test), - ?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.json"]))), - ?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.csv"]))), - ?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser1">>)), - ?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser3">>)), + ?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.json"]))), + ?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.csv"]))), + ?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser1">>)), + ?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser3">>)), ClientInfo1 = #{username => <<"myuser1">>, password => <<"mypassword1">>}, @@ -110,37 +108,35 @@ t_import(_) -> ClientInfo2 = ClientInfo1#{username => <<"myuser3">>, password => <<"mypassword3">>}, ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), ok. t_multi_mnesia_authenticator(_) -> AuthenticatorName1 = <<"myauthenticator1">>, AuthenticatorConfig1 = #{name => AuthenticatorName1, mechanism => 'password-based', - config => #{ - server_type => 'built-in-database', - user_id_type => username, - password_hash_algorithm => #{ - name => sha256 - }}}, + server_type => 'built-in-database', + user_id_type => username, + password_hash_algorithm => #{ + name => sha256 + }}, AuthenticatorName2 = <<"myauthenticator2">>, AuthenticatorConfig2 = #{name => AuthenticatorName2, mechanism => 'password-based', - config => #{ - server_type => 'built-in-database', - user_id_type => clientid, - password_hash_algorithm => #{ - name => sha256 - }}}, - ?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)), - ?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)), + server_type => 'built-in-database', + user_id_type => clientid, + password_hash_algorithm => #{ + name => sha256 + }}, + {ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1), + {ok, #{name := AuthenticatorName2, id := ID2}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2), ?assertEqual({ok, #{user_id => <<"myuser">>}}, - ?AUTH:add_user(?CHAIN, AuthenticatorName1, + ?AUTH:add_user(?CHAIN, ID1, #{<<"user_id">> => <<"myuser">>, <<"password">> => <<"mypass1">>})), ?assertEqual({ok, #{user_id => <<"myclient">>}}, - ?AUTH:add_user(?CHAIN, AuthenticatorName2, + ?AUTH:add_user(?CHAIN, ID2, #{<<"user_id">> => <<"myclient">>, <<"password">> => <<"mypass2">>})), @@ -148,12 +144,12 @@ t_multi_mnesia_authenticator(_) -> clientid => <<"myclient">>, password => <<"mypass1">>}, ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)), - ?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)), + ?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)), ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)), ClientInfo2 = ClientInfo1#{password => <<"mypass2">>}, ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)), - ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)), + ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)), ok. From b97c46e3cfb0a2cfbae3637ea26bead03c61644e Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 27 Jul 2021 10:53:33 +0800 Subject: [PATCH 7/7] test(authn): fix test cases --- apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index 511d00e79..4a5a24844 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -48,8 +48,8 @@ t_mnesia_authenticator(_) -> }}, {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig), - UserInfo = #{<<"user_id">> => <<"myuser">>, - <<"password">> => <<"mypass">>}, + UserInfo = #{user_id => <<"myuser">>, + password => <<"mypass">>}, ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)), ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)), @@ -68,7 +68,7 @@ t_mnesia_authenticator(_) -> ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo3, ok)), ?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo3)), - UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>}, + UserInfo2 = UserInfo#{password => <<"mypass2">>}, ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, UserInfo2)), ClientInfo4 = ClientInfo#{password => <<"mypass2">>}, ?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo4, ok)), @@ -133,12 +133,12 @@ t_multi_mnesia_authenticator(_) -> ?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID1, - #{<<"user_id">> => <<"myuser">>, - <<"password">> => <<"mypass1">>})), + #{user_id => <<"myuser">>, + password => <<"mypass1">>})), ?assertEqual({ok, #{user_id => <<"myclient">>}}, ?AUTH:add_user(?CHAIN, ID2, - #{<<"user_id">> => <<"myclient">>, - <<"password">> => <<"mypass2">>})), + #{user_id => <<"myclient">>, + password => <<"mypass2">>})), ClientInfo1 = #{username => <<"myuser">>, clientid => <<"myclient">>,