feat(authentication): implement http api and change external apis
This commit is contained in:
parent
54106790c7
commit
9d4af87eb7
|
@ -1,2 +1,2 @@
|
|||
myuser3,mypassword3
|
||||
myuser4,mypassword4
|
||||
myuser3,8d41233e39c95b5da13361e354e1c9e639f07b27d397463a8f91b71ee07ccfb2
|
||||
myuser4,5809df0154f3cb4ac5c3a5572eaca0c5f7f9d858e887fc675b2becab9feb19d1
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"myuser1": "mypassword1",
|
||||
"myuser2": "mypassword2"
|
||||
"myuser1": "09343625c6c123d3434932fe1ce08bae5ac00a8f95bd746e10491b0bafdd1817",
|
||||
"myuser2": "8767a7d316ad68cb607c7c805b859ffa78277dda13b7a3e2e8b53cad3cabbc6e"
|
||||
}
|
|
@ -40,11 +40,12 @@
|
|||
, move_service_to_the_nth/3
|
||||
]).
|
||||
|
||||
-export([ import_user_credentials/4
|
||||
, add_user_credential/3
|
||||
, delete_user_credential/3
|
||||
, update_user_credential/3
|
||||
, lookup_user_credential/3
|
||||
-export([ import_users/3
|
||||
, add_user/3
|
||||
, delete_user/3
|
||||
, update_user/4
|
||||
, lookup_user/3
|
||||
, list_users/2
|
||||
]).
|
||||
|
||||
-export([mnesia/1]).
|
||||
|
@ -101,7 +102,7 @@ disable() ->
|
|||
authenticate(#{chain_id := ChainID} = ClientInfo) ->
|
||||
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
||||
[#chain{services = []}] ->
|
||||
{error, todo};
|
||||
{error, no_services};
|
||||
[#chain{services = Services}] ->
|
||||
do_authenticate(Services, ClientInfo);
|
||||
[] ->
|
||||
|
@ -109,7 +110,7 @@ authenticate(#{chain_id := ChainID} = ClientInfo) ->
|
|||
end.
|
||||
|
||||
do_authenticate([], _) ->
|
||||
{error, user_credential_not_found};
|
||||
{error, user_not_found};
|
||||
do_authenticate([{_, #service{provider = Provider, state = State}} | More], ClientInfo) ->
|
||||
case Provider:authenticate(ClientInfo, State) of
|
||||
ignore -> do_authenticate(More, ClientInfo);
|
||||
|
@ -136,55 +137,44 @@ register_service_types([{_App, Mod, #{name := Name,
|
|||
params_spec = ParamsSpec},
|
||||
register_service_types(Types, [ServiceType | Acc]).
|
||||
|
||||
create_chain(Params = #{chain_id := ChainID}) ->
|
||||
ServiceParams = maps:get(services, Params, []),
|
||||
case validate_service_params(ServiceParams) of
|
||||
{ok, NServiceParams} ->
|
||||
create_chain(#{id := ID}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?CHAIN_TAB, ChainID, write) of
|
||||
case mnesia:read(?CHAIN_TAB, ID, write) of
|
||||
[] ->
|
||||
case create_services(ChainID, NServiceParams) of
|
||||
{ok, Services} ->
|
||||
Chain = #chain{id = ChainID,
|
||||
services = Services,
|
||||
Chain = #chain{id = ID,
|
||||
services = [],
|
||||
created_at = erlang:system_time(millisecond)},
|
||||
mnesia:write(?CHAIN_TAB, Chain, write),
|
||||
{ok, ChainID};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
{ok, serialize_chain(Chain)};
|
||||
[_ | _] ->
|
||||
{error, {already_exists, {chain, ChainID}}}
|
||||
end
|
||||
end);
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
delete_chain(ChainID) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?CHAIN_TAB, ChainID, write) of
|
||||
[] ->
|
||||
{error, {not_found, {chain, ChainID}}};
|
||||
[#chain{services = Services}] ->
|
||||
ok = delete_services_(Services),
|
||||
mnesia:delete(?CHAIN_TAB, ChainID, write)
|
||||
{error, {already_exists, {chain, ID}}}
|
||||
end
|
||||
end).
|
||||
|
||||
lookup_chain(ChainID) ->
|
||||
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
||||
delete_chain(ID) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?CHAIN_TAB, ID, write) of
|
||||
[] ->
|
||||
{error, {not_found, {chain, ChainID}}};
|
||||
{error, {not_found, {chain, ID}}};
|
||||
[#chain{services = Services}] ->
|
||||
ok = delete_services_(Services),
|
||||
mnesia:delete(?CHAIN_TAB, ID, write)
|
||||
end
|
||||
end).
|
||||
|
||||
lookup_chain(ID) ->
|
||||
case mnesia:dirty_read(?CHAIN_TAB, ID) of
|
||||
[] ->
|
||||
{error, {not_found, {chain, ID}}};
|
||||
[Chain] ->
|
||||
{ok, serialize_chain(Chain)}
|
||||
end.
|
||||
|
||||
list_chains() ->
|
||||
Chains = ets:tab2list(?CHAIN_TAB),
|
||||
[serialize_chain(Chain) || Chain <- Chains].
|
||||
{ok, [serialize_chain(Chain) || Chain <- Chains]}.
|
||||
|
||||
add_services(ChainID, ServiceParams) ->
|
||||
case validate_service_params(ServiceParams) of
|
||||
|
@ -195,8 +185,10 @@ add_services(ChainID, ServiceParams) ->
|
|||
ok ->
|
||||
case create_services(ChainID, NServiceParams) of
|
||||
{ok, NServices} ->
|
||||
io:format("~p~n", [NServices]),
|
||||
NChain = Chain#chain{services = Services ++ NServices},
|
||||
mnesia:write(?CHAIN_TAB, NChain, write);
|
||||
ok = mnesia:write(?CHAIN_TAB, NChain, write),
|
||||
{ok, serialize_services(NServices)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
|
@ -243,8 +235,9 @@ update_service(ChainID, ServiceName, NewParams) ->
|
|||
{ok, NState} ->
|
||||
NService = Service#service{params = Params,
|
||||
state = NState},
|
||||
NServices = lists:keyreplace(ServiceName, 1, Services, [{ServiceName, NService}]),
|
||||
mnesia:write(?CHAIN_TAB, Chain#chain{services = NServices}, write);
|
||||
NServices = lists:keyreplace(ServiceName, 1, Services, {ServiceName, NService}),
|
||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{services = NServices}, write),
|
||||
{ok, serialize_service({ServiceName, NService})};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end
|
||||
|
@ -270,7 +263,7 @@ list_services(ChainID) ->
|
|||
[] ->
|
||||
{error, {not_found, {chain, ChainID}}};
|
||||
[#chain{services = Services}] ->
|
||||
{ok, [serialize_service(Service) || Service <- Services]}
|
||||
{ok, serialize_services(Services)}
|
||||
end.
|
||||
|
||||
move_service_to_the_front(ChainID, ServiceName) ->
|
||||
|
@ -309,20 +302,23 @@ move_service_to_the_nth(ChainID, ServiceName, N) ->
|
|||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
|
||||
import_user_credentials(ChainID, ServiceName, Filename, FileFormat) ->
|
||||
call_service(ChainID, ServiceName, import_user_credentials, [Filename, FileFormat]).
|
||||
import_users(ChainID, ServiceName, Filename) ->
|
||||
call_service(ChainID, ServiceName, import_users, [Filename]).
|
||||
|
||||
add_user_credential(ChainID, ServiceName, Credential) ->
|
||||
call_service(ChainID, ServiceName, add_user_credential, [Credential]).
|
||||
add_user(ChainID, ServiceName, UserInfo) ->
|
||||
call_service(ChainID, ServiceName, add_user, [UserInfo]).
|
||||
|
||||
delete_user_credential(ChainID, ServiceName, UserIdentity) ->
|
||||
call_service(ChainID, ServiceName, delete_user_credential, [UserIdentity]).
|
||||
delete_user(ChainID, ServiceName, UserID) ->
|
||||
call_service(ChainID, ServiceName, delete_user, [UserID]).
|
||||
|
||||
update_user_credential(ChainID, ServiceName, Credential) ->
|
||||
call_service(ChainID, ServiceName, update_user_credential, [Credential]).
|
||||
update_user(ChainID, ServiceName, UserID, NewUserInfo) ->
|
||||
call_service(ChainID, ServiceName, update_user, [UserID, NewUserInfo]).
|
||||
|
||||
lookup_user_credential(ChainID, ServiceName, UserIdentity) ->
|
||||
call_service(ChainID, ServiceName, lookup_user_credential, [UserIdentity]).
|
||||
lookup_user(ChainID, ServiceName, UserID) ->
|
||||
call_service(ChainID, ServiceName, lookup_user, [UserID]).
|
||||
|
||||
list_users(ChainID, ServiceName) ->
|
||||
call_service(ChainID, ServiceName, list_users, []).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
|
@ -506,9 +502,12 @@ serialize_chain(#chain{id = ID,
|
|||
services = Services,
|
||||
created_at = CreatedAt}) ->
|
||||
#{id => ID,
|
||||
services => [serialize_service(Service) || Service <- Services],
|
||||
services => serialize_services(Services),
|
||||
created_at => CreatedAt}.
|
||||
|
||||
serialize_services(Services) ->
|
||||
[serialize_service(Service) || Service <- Services].
|
||||
|
||||
serialize_service({_, #service{name = Name,
|
||||
type = Type,
|
||||
params = Params}}) ->
|
||||
|
|
|
@ -20,25 +20,22 @@
|
|||
, delete_chain/2
|
||||
, lookup_chain/2
|
||||
, list_chains/2
|
||||
, add_services/2
|
||||
, delete_services/2
|
||||
, add_service/2
|
||||
, delete_service/2
|
||||
, update_service/2
|
||||
, lookup_service/2
|
||||
, list_services/2
|
||||
, move_service/2
|
||||
, import_user_credentials/2
|
||||
, add_user_creadential/2
|
||||
, import_users/2
|
||||
, add_user/2
|
||||
, delete_user/2
|
||||
, update_user/2
|
||||
, lookup_user/2
|
||||
, list_users/2
|
||||
]).
|
||||
|
||||
-import(minirest, [return/1]).
|
||||
|
||||
-rest_api(#{name => list_chains,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains",
|
||||
func => list_chains,
|
||||
descr => "List all chains"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => create_chain,
|
||||
method => 'POST',
|
||||
path => "/authentication/chains",
|
||||
|
@ -46,187 +43,352 @@
|
|||
descr => "Create a chain"
|
||||
}).
|
||||
|
||||
create_chain(_Binding, Params = #{chain_id := ChainID}) ->
|
||||
case emqx_authentication:create_chain(Params) of
|
||||
{ok, ChainID} ->
|
||||
return({ok, ChainID});
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
create_chain(_Binding, _Params) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
-rest_api(#{name => delete_chain,
|
||||
method => 'DELETE',
|
||||
path => "/authentication/chains/:bin:chain_id",
|
||||
func => delete_chain,
|
||||
descr => "Delete chain"
|
||||
}).
|
||||
|
||||
delete_chain(_Binding, #{chain_id := ChainID}) ->
|
||||
-rest_api(#{name => lookup_chain,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains/:bin:chain_id",
|
||||
func => lookup_chain,
|
||||
descr => "Lookup chain"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => list_chains,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains",
|
||||
func => list_chains,
|
||||
descr => "List all chains"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => add_service,
|
||||
method => 'POST',
|
||||
path => "/authentication/chains/:bin:chain_id/services",
|
||||
func => add_service,
|
||||
descr => "Add service to chain"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => delete_service,
|
||||
method => 'DELETE',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name",
|
||||
func => delete_service,
|
||||
descr => "Delete service from chain"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => update_service,
|
||||
method => 'PUT',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name",
|
||||
func => update_service,
|
||||
descr => "Update service in chain"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => lookup_service,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name",
|
||||
func => lookup_service,
|
||||
descr => "Lookup service in chain"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => list_services,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains/:bin:chain_id/services",
|
||||
func => list_services,
|
||||
descr => "List services in chain"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => move_service,
|
||||
method => 'POST',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/position",
|
||||
func => move_service,
|
||||
descr => "Change the order of services"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => import_users,
|
||||
method => 'POST',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/import-users",
|
||||
func => import_users,
|
||||
descr => "Import users"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => add_user,
|
||||
method => 'POST',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/users",
|
||||
func => add_user,
|
||||
descr => "Add user"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => delete_user,
|
||||
method => 'DELETE',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/users/:bin:user_id",
|
||||
func => delete_user,
|
||||
descr => "Delete user"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => update_user,
|
||||
method => 'PUT',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/users/:bin:user_id",
|
||||
func => update_user,
|
||||
descr => "Update user"
|
||||
}).
|
||||
|
||||
-rest_api(#{name => lookup_user,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/users/:bin:user_id",
|
||||
func => lookup_user,
|
||||
descr => "Lookup user"
|
||||
}).
|
||||
|
||||
%% TODO: Support pagination
|
||||
-rest_api(#{name => list_users,
|
||||
method => 'GET',
|
||||
path => "/authentication/chains/:bin:chain_id/services/:bin:service_name/users",
|
||||
func => list_users,
|
||||
descr => "List all users"
|
||||
}).
|
||||
|
||||
create_chain(Binding, Params) ->
|
||||
do_create_chain(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_create_chain(_Binding, #{<<"chain_id">> := ChainID}) ->
|
||||
case emqx_authentication:create_chain(ChainID) of
|
||||
{ok, Chain} ->
|
||||
return({ok, Chain});
|
||||
{error, Reason} ->
|
||||
return(serialize_error(Reason))
|
||||
end;
|
||||
do_create_chain(_Binding, _Params) ->
|
||||
return(serialize_error({missing_parameter, chain_id})).
|
||||
|
||||
delete_chain(Binding, Params) ->
|
||||
do_delete_chain(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_delete_chain(#{chain_id := ChainID}, _Params) ->
|
||||
case emqx_authentication:delete_chain(ChainID) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
lookup_chain(_Binding, #{chain_id := ChainID}) ->
|
||||
lookup_chain(Binding, Params) ->
|
||||
do_lookup_chain(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_lookup_chain(#{chain_id := ChainID}, _Params) ->
|
||||
case emqx_authentication:lookup_chain(ChainID) of
|
||||
{ok, Chain} ->
|
||||
return({ok, Chain});
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
list_chains(Binding, Params) ->
|
||||
do_list_chains(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_list_chains(_Binding, _Params) ->
|
||||
{ok, Chains} = emqx_authentication:list_chains(),
|
||||
return({ok, Chains}).
|
||||
|
||||
add_service(Binding, Params) ->
|
||||
do_add_service(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_add_service(#{chain_id := ChainID}, #{<<"name">> := Name,
|
||||
<<"type">> := Type,
|
||||
<<"params">> := Params}) ->
|
||||
case emqx_authentication:add_services(ChainID, [#{name => Name,
|
||||
type => binary_to_existing_atom(Type, utf8),
|
||||
params => maps:from_list(Params)}]) of
|
||||
{ok, Services} ->
|
||||
return({ok, Services});
|
||||
{error, Reason} ->
|
||||
return(serialize_error(Reason))
|
||||
end;
|
||||
lookup_chain(_Binding, _Params) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
%% TODO: Check missed field in params
|
||||
do_add_service(_Binding, Params) ->
|
||||
Missed = get_missed_params(Params, [<<"name">>, <<"type">>, <<"params">>]),
|
||||
return(serialize_error({missing_parameter, Missed})).
|
||||
|
||||
list_chains(_Binding, _Params) ->
|
||||
emqx_authentication:list_chains().
|
||||
delete_service(Binding, Params) ->
|
||||
do_delete_service(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
add_services(_Binding, Params = #{chain_id := ChainID}) ->
|
||||
case maps:get(services, Params, []) of
|
||||
[] -> return(ok);
|
||||
Services ->
|
||||
case emqx_authentication:add_services(ChainID, Services) of
|
||||
do_delete_service(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, _Params) ->
|
||||
case emqx_authentication:delete_services(ChainID, [ServiceName]) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end
|
||||
end;
|
||||
add_services(_Binding, _Params) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
delete_services(_Binding, #{chain_id := ChainID,
|
||||
service_names := ServiceNames}) ->
|
||||
case emqx_authentication:delete_services(ChainID, ServiceNames) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
delete_services(_Binding, #{chain_id := _}) ->
|
||||
return({error, serialize_error({missing_parameter, service_names})});
|
||||
delete_services(_Binding, #{service_names := _}) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
update_service(Binding, Params) ->
|
||||
do_update_service(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
%% TODO: better input parameters
|
||||
update_service(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
service_params := Params}) ->
|
||||
%% TOOD: PUT 方法支持创建和更新
|
||||
do_update_service(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, Params) ->
|
||||
case emqx_authentication:update_service(ChainID, ServiceName, Params) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{ok, Service} ->
|
||||
return({ok, Service});
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
update_service(_Binding, #{chain_id := _}) ->
|
||||
return({error, serialize_error({missing_parameter, service_name})});
|
||||
update_service(_Binding, #{service_name := _}) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
lookup_service(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName}) ->
|
||||
lookup_service(Binding, Params) ->
|
||||
do_lookup_service(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_lookup_service(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, _Params) ->
|
||||
case emqx_authentication:lookup_service(ChainID, ServiceName) of
|
||||
{ok, Service} ->
|
||||
return({ok, Service});
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
lookup_service(_Binding, #{chain_id := _}) ->
|
||||
return({error, serialize_error({missing_parameter, service_name})});
|
||||
lookup_service(_Binding, #{service_name := _}) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
list_services(_Binding, #{chain_id := ChainID}) ->
|
||||
list_services(Binding, Params) ->
|
||||
do_list_services(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_list_services(#{chain_id := ChainID}, _Params) ->
|
||||
case emqx_authentication:list_services(ChainID) of
|
||||
{ok, Service} ->
|
||||
return({ok, Service});
|
||||
{ok, Services} ->
|
||||
return({ok, Services});
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
list_services(_Binding, _Params) ->
|
||||
return({error, serialize_error({missing_parameter, chain_id})}).
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
move_service(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
to := <<"the front">>}) ->
|
||||
case emqx_authenticaiton:move_service_to_the_front(ChainID, ServiceName) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
move_service(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
to := <<"the end">>}) ->
|
||||
case emqx_authenticaiton:move_service_to_the_end(ChainID, ServiceName) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
move_service(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
to := N}) when is_number(N) ->
|
||||
case emqx_authenticaiton:move_service_to_the_nth(ChainID, ServiceName, N) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
end;
|
||||
move_service(_Binding, Params) ->
|
||||
Missed = get_missed_params(Params, [chain_id, service_name, to]),
|
||||
return({error, serialize_error({missing_parameter, Missed})}).
|
||||
move_service(Binding, Params) ->
|
||||
do_move_service(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
import_user_credentials(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
filename := Filename,
|
||||
file_format := FileFormat}) ->
|
||||
case emqx_authentication:import_user_credentials(ChainID, ServiceName, Filename, FileFormat) of
|
||||
do_move_service(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, #{<<"position">> := <<"the front">>}) ->
|
||||
case emqx_authentication:move_service_to_the_front(ChainID, ServiceName) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
return(serialize_error(Reason))
|
||||
end;
|
||||
import_user_credentials(_Binding, Params) ->
|
||||
Missed = get_missed_params(Params, [chain_id, service_name, filename, file_format]),
|
||||
return({error, serialize_error({missing_parameter, Missed})}).
|
||||
do_move_service(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, #{<<"position">> := <<"the end">>}) ->
|
||||
case emqx_authentication:move_service_to_the_end(ChainID, ServiceName) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return(serialize_error(Reason))
|
||||
end;
|
||||
do_move_service(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, #{<<"position">> := N}) when is_number(N) ->
|
||||
case emqx_authentication:move_service_to_the_nth(ChainID, ServiceName, N) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return(serialize_error(Reason))
|
||||
end;
|
||||
do_move_service(_Binding, _Params) ->
|
||||
return(serialize_error({missing_parameter, <<"position">>})).
|
||||
|
||||
add_user_creadential(_Binding, #{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
credential := Credential}) ->
|
||||
case emqx_authentication:add_user_creadentials(ChainID, ServiceName, Credential) of
|
||||
import_users(Binding, Params) ->
|
||||
do_import_users(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_import_users(#{chain_id := ChainID, service_name := ServiceName},
|
||||
#{<<"filename">> := Filename}) ->
|
||||
case emqx_authentication:import_users(ChainID, ServiceName, Filename) of
|
||||
ok ->
|
||||
return(ok);
|
||||
{error, Reason} ->
|
||||
return({error, serialize_error(Reason)})
|
||||
return(serialize_error(Reason))
|
||||
end;
|
||||
add_user_creadential(_Binding, Params) ->
|
||||
Missed = get_missed_params(Params, [chain_id, service_name, credential]),
|
||||
return({error, serialize_error({missing_parameter, Missed})}).
|
||||
do_import_users(_Binding, Params) ->
|
||||
Missed = get_missed_params(Params, [<<"filename">>, <<"file_format">>]),
|
||||
return(serialize_error({missing_parameter, Missed})).
|
||||
|
||||
add_user(Binding, Params) ->
|
||||
do_add_user(uri_decode(Binding), maps:from_list(Params)).
|
||||
|
||||
do_add_user(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, UserInfo) ->
|
||||
case emqx_authentication:add_user(ChainID, ServiceName, 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(#{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
user_id := UserID}, _Params) ->
|
||||
case emqx_authentication:delete_user(ChainID, ServiceName, 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(#{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
user_id := UserID}, NewUserInfo) ->
|
||||
case emqx_authentication:update_user(ChainID, ServiceName, 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(#{chain_id := ChainID,
|
||||
service_name := ServiceName,
|
||||
user_id := UserID}, _Params) ->
|
||||
case emqx_authentication:lookup_user(ChainID, ServiceName, 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(#{chain_id := ChainID,
|
||||
service_name := ServiceName}, _Params) ->
|
||||
case emqx_authentication:list_users(ChainID, ServiceName) of
|
||||
{ok, Users} ->
|
||||
return({ok, Users});
|
||||
{error, Reason} ->
|
||||
return(serialize_error(Reason))
|
||||
end.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
serialize_error(Reason) when not is_map(Reason) ->
|
||||
Error = serialize_error_(Reason),
|
||||
emqx_json:encode(Error).
|
||||
uri_decode(Params) ->
|
||||
maps:fold(fun(K, V, Acc) ->
|
||||
Acc#{K => emqx_http_lib:uri_decode(V)}
|
||||
end, #{}, Params).
|
||||
|
||||
serialize_error_({already_exists, {Type, ID}}) ->
|
||||
#{code => "ALREADY_EXISTS",
|
||||
message => io_lib:format("~p ~p already exists", [serialize_type(Type), ID])};
|
||||
serialize_error_({not_found, {Type, ID}}) ->
|
||||
#{code => "NOT_FOUND",
|
||||
message => io_lib:format("~p ~p not found", [serialize_type(Type), ID])};
|
||||
serialize_error_({duplicate, Name}) ->
|
||||
#{code => "INVALID_PARAMETER",
|
||||
message => io_lib:format("Service name ~p is duplicated", [Name])};
|
||||
serialize_error_({missing_parameter, Names = [_ | Rest]}) ->
|
||||
serialize_error({already_exists, {Type, ID}}) ->
|
||||
{error, <<"ALREADY_EXISTS">>, list_to_binary(io_lib:format("~p ~p already exists", [serialize_type(Type), ID]))};
|
||||
serialize_error({not_found, {Type, ID}}) ->
|
||||
{error, <<"NOT_FOUND">>, list_to_binary(io_lib:format("~p ~p not found", [serialize_type(Type), ID]))};
|
||||
serialize_error({duplicate, Name}) ->
|
||||
{error, <<"INVALID_PARAMETER">>, list_to_binary(io_lib:format("Service name ~p is duplicated", [Name]))};
|
||||
serialize_error({missing_parameter, Names = [_ | Rest]}) ->
|
||||
Format = ["~p," || _ <- Rest] ++ ["~p"],
|
||||
NFormat = binary_to_list(iolist_to_binary(Format)),
|
||||
#{code => "MISSING_PARAMETER",
|
||||
message => io_lib:format("The input parameters " ++ NFormat ++ " that are mandatory for processing this request are not supplied.", Names)};
|
||||
serialize_error_({missing_parameter, Name}) ->
|
||||
#{code => "MISSING_PARAMETER",
|
||||
message => io_lib:format("The input parameter ~p that is mandatory for processing this request is not supplied.", [Name])};
|
||||
serialize_error_(_) ->
|
||||
#{code => "UNKNOWN_ERROR"}.
|
||||
{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 ~p that is mandatory for processing this request is not supplied.", [Name]))};
|
||||
serialize_error(_) ->
|
||||
{error, <<"UNKNOWN_ERROR">>, <<"Unknown error">>}.
|
||||
|
||||
serialize_type(service) ->
|
||||
"Service";
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
-behaviour(application).
|
||||
|
||||
-emqx_plugin(?MODULE).
|
||||
|
||||
%% Application callbacks
|
||||
-export([ start/2
|
||||
, stop/1
|
||||
|
|
|
@ -24,17 +24,18 @@
|
|||
, destroy/1
|
||||
]).
|
||||
|
||||
-export([ import_user_credentials/3
|
||||
, add_user_credential/2
|
||||
, delete_user_credential/2
|
||||
, update_user_credential/2
|
||||
, lookup_user_credential/2
|
||||
-export([ import_users/2
|
||||
, add_user/2
|
||||
, delete_user/2
|
||||
, update_user/3
|
||||
, lookup_user/2
|
||||
, list_users/1
|
||||
]).
|
||||
|
||||
-service_type(#{
|
||||
name => mnesia,
|
||||
params_spec => #{
|
||||
user_identity_type => #{
|
||||
user_id_type => #{
|
||||
order => 1,
|
||||
type => string,
|
||||
required => true,
|
||||
|
@ -51,13 +52,13 @@
|
|||
}
|
||||
}).
|
||||
|
||||
-record(user_credential,
|
||||
{ user_identity :: {user_group(), user_identity()}
|
||||
-record(user_info,
|
||||
{ user_id :: {user_group(), user_id()}
|
||||
, password_hash :: binary()
|
||||
}).
|
||||
|
||||
-type(user_group() :: {chain_id(), service_name()}).
|
||||
-type(user_identity() :: binary()).
|
||||
-type(user_id() :: binary()).
|
||||
|
||||
-export([mnesia/1]).
|
||||
|
||||
|
@ -66,6 +67,8 @@
|
|||
|
||||
-define(TAB, mnesia_basic_auth).
|
||||
|
||||
%% TODO: Support salt
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Mnesia bootstrap
|
||||
%%------------------------------------------------------------------------------
|
||||
|
@ -75,17 +78,17 @@
|
|||
mnesia(boot) ->
|
||||
ok = ekka_mnesia:create_table(?TAB, [
|
||||
{disc_copies, [node()]},
|
||||
{record_name, user_credential},
|
||||
{attributes, record_info(fields, user_credential)},
|
||||
{record_name, user_info},
|
||||
{attributes, record_info(fields, user_info)},
|
||||
{storage_properties, [{ets, [{read_concurrency, true}]}]}]);
|
||||
|
||||
mnesia(copy) ->
|
||||
ok = ekka_mnesia:copy_table(?TAB, disc_copies).
|
||||
|
||||
create(ChainID, ServiceName, #{<<"user_identity_type">> := Type,
|
||||
create(ChainID, ServiceName, #{<<"user_id_type">> := Type,
|
||||
<<"password_hash_algorithm">> := Algorithm}) ->
|
||||
State = #{user_group => {ChainID, ServiceName},
|
||||
user_identity_type => binary_to_atom(Type, utf8),
|
||||
user_id_type => binary_to_atom(Type, utf8),
|
||||
password_hash_algorithm => binary_to_atom(Algorithm, utf8)},
|
||||
{ok, State}.
|
||||
|
||||
|
@ -94,13 +97,13 @@ update(ChainID, ServiceName, Params, _State) ->
|
|||
|
||||
authenticate(ClientInfo = #{password := Password},
|
||||
#{user_group := UserGroup,
|
||||
user_identity_type := Type,
|
||||
user_id_type := Type,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
UserIdentity = get_user_identity(ClientInfo, Type),
|
||||
case mnesia:dirty_read(?TAB, {UserGroup, UserIdentity}) of
|
||||
UserID = get_user_identity(ClientInfo, Type),
|
||||
case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of
|
||||
[] ->
|
||||
ignore;
|
||||
[#user_credential{password_hash = Hash}] ->
|
||||
[#user_info{password_hash = Hash}] ->
|
||||
case Hash =:= emqx_passwd:hash(Algorithm, Password) of
|
||||
true ->
|
||||
ok;
|
||||
|
@ -112,117 +115,133 @@ authenticate(ClientInfo = #{password := Password},
|
|||
destroy(#{user_group := UserGroup}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
MatchSpec = [{#user_credential{user_identity = {UserGroup, '_'}, _ = '_'}, [], ['$_']}],
|
||||
lists:foreach(fun delete_user_credential/1, mnesia:select(?TAB, MatchSpec, write))
|
||||
MatchSpec = [{#user_info{user_id = {UserGroup, '_'}, _ = '_'}, [], ['$_']}],
|
||||
lists:foreach(fun delete_user2/1, mnesia:select(?TAB, MatchSpec, write))
|
||||
end).
|
||||
|
||||
import_users(Filename0, State) ->
|
||||
Filename = to_binary(Filename0),
|
||||
case filename:extension(Filename) of
|
||||
<<".json">> ->
|
||||
import_users_from_json(Filename, State);
|
||||
<<".csv">> ->
|
||||
import_users_from_csv(Filename, State);
|
||||
<<>> ->
|
||||
{error, unknown_file_format};
|
||||
Extension ->
|
||||
{error, {unsupported_file_format, Extension}}
|
||||
end.
|
||||
|
||||
add_user(#{<<"user_id">> := UserID,
|
||||
<<"password">> := Password},
|
||||
#{user_group := UserGroup,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserID}, write) of
|
||||
[] ->
|
||||
add(UserGroup, UserID, Password, Algorithm),
|
||||
{ok, #{user_id => UserID}};
|
||||
[_] ->
|
||||
{error, already_exist}
|
||||
end
|
||||
end).
|
||||
|
||||
delete_user(UserID, #{user_group := UserGroup}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserID}, write) of
|
||||
[] ->
|
||||
{error, not_found};
|
||||
[_] ->
|
||||
mnesia:delete(?TAB, {UserGroup, UserID}, write)
|
||||
end
|
||||
end).
|
||||
|
||||
update_user(UserID, #{<<"password">> := Password},
|
||||
#{user_group := UserGroup,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserID}, write) of
|
||||
[] ->
|
||||
{error, not_found};
|
||||
[_] ->
|
||||
add(UserGroup, UserID, Password, Algorithm),
|
||||
{ok, #{user_id => UserID}}
|
||||
end
|
||||
end).
|
||||
|
||||
lookup_user(UserID, #{user_group := UserGroup}) ->
|
||||
case mnesia:dirty_read(?TAB, {UserGroup, UserID}) of
|
||||
[#user_info{user_id = {_, UserID}}] ->
|
||||
{ok, #{user_id => UserID}};
|
||||
[] ->
|
||||
{error, not_found}
|
||||
end.
|
||||
|
||||
list_users(#{user_group := UserGroup}) ->
|
||||
Users = [#{user_id => UserID} || #user_info{user_id = {UserGroup0, UserID}} <- ets:tab2list(?TAB), UserGroup0 =:= UserGroup],
|
||||
{ok, Users}.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
%% Example:
|
||||
%% {
|
||||
%% "myuser1":"mypassword1",
|
||||
%% "myuser2":"mypassword2"
|
||||
%% "myuser1":"password_hash1",
|
||||
%% "myuser2":"password_hash2"
|
||||
%% }
|
||||
import_user_credentials(Filename, json,
|
||||
#{user_group := UserGroup,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
import_users_from_json(Filename, #{user_group := UserGroup}) ->
|
||||
case file:read_file(Filename) of
|
||||
{ok, Bin} ->
|
||||
case emqx_json:safe_decode(Bin) of
|
||||
{ok, List} ->
|
||||
import(UserGroup, List, Algorithm);
|
||||
import(UserGroup, List);
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
end.
|
||||
|
||||
%% Example:
|
||||
%% myuser1,mypassword1
|
||||
%% myuser2,mypassword2
|
||||
import_user_credentials(Filename, csv,
|
||||
#{user_group := UserGroup,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
%% myuser1,password_hash1
|
||||
%% myuser2,password_hash2
|
||||
import_users_from_csv(Filename, #{user_group := UserGroup}) ->
|
||||
case file:open(Filename, [read, binary]) of
|
||||
{ok, File} ->
|
||||
Result = import(UserGroup, File, Algorithm),
|
||||
Result = import(UserGroup, File),
|
||||
file:close(File),
|
||||
Result;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
add_user_credential(#{user_identity := UserIdentity, password := Password},
|
||||
#{user_group := UserGroup,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserIdentity}, write) of
|
||||
[] ->
|
||||
add(UserGroup, UserIdentity, Password, Algorithm);
|
||||
[_] ->
|
||||
{error, already_exist}
|
||||
end
|
||||
end).
|
||||
import(UserGroup, ListOrFile) ->
|
||||
trans(fun do_import/2, [UserGroup, ListOrFile]).
|
||||
|
||||
delete_user_credential(UserIdentity, #{user_group := UserGroup}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserIdentity}, write) of
|
||||
[] ->
|
||||
{error, not_found};
|
||||
[_] ->
|
||||
mnesia:delete(?TAB, {UserGroup, UserIdentity}, write)
|
||||
end
|
||||
end).
|
||||
|
||||
update_user_credential(#{user_identity := UserIdentity, password := Password},
|
||||
#{user_group := UserGroup,
|
||||
password_hash_algorithm := Algorithm}) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserIdentity}, write) of
|
||||
[] ->
|
||||
{error, not_found};
|
||||
[_] ->
|
||||
add(UserGroup, UserIdentity, Password, Algorithm)
|
||||
end
|
||||
end).
|
||||
|
||||
lookup_user_credential(UserIdentity, #{user_group := UserGroup}) ->
|
||||
case mnesia:dirty_read(?TAB, {UserGroup, UserIdentity}) of
|
||||
[#user_credential{user_identity = {_, UserIdentity},
|
||||
password_hash = PassHash}] ->
|
||||
{ok, #{user_identity => UserIdentity,
|
||||
password_hash => PassHash}};
|
||||
[] -> {error, not_found}
|
||||
end.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
import(UserGroup, ListOrFile, Algorithm) ->
|
||||
trans(fun do_import/3, [UserGroup, ListOrFile, Algorithm]).
|
||||
|
||||
do_import(_UserGroup, [], _Algorithm) ->
|
||||
do_import(_UserGroup, []) ->
|
||||
ok;
|
||||
do_import(UserGroup, [{UserIdentity, Password} | More], Algorithm)
|
||||
when is_binary(UserIdentity) andalso is_binary(Password) ->
|
||||
add(UserGroup, UserIdentity, Password, Algorithm),
|
||||
do_import(UserGroup, More, Algorithm);
|
||||
do_import(_UserGroup, [_ | _More], _Algorithm) ->
|
||||
do_import(UserGroup, [{UserID, PasswordHash} | More])
|
||||
when is_binary(UserID) andalso is_binary(PasswordHash) ->
|
||||
import_user(UserGroup, UserID, PasswordHash),
|
||||
do_import(UserGroup, More);
|
||||
do_import(_UserGroup, [_ | _More]) ->
|
||||
{error, bad_format};
|
||||
|
||||
%% Importing 5w credentials needs 1.7 seconds
|
||||
do_import(UserGroup, File, Algorithm) ->
|
||||
%% Importing 5w users needs 1.7 seconds
|
||||
do_import(UserGroup, File) ->
|
||||
case file:read_line(File) of
|
||||
{ok, Line} ->
|
||||
case binary:split(Line, [<<",">>, <<"\n">>], [global]) of
|
||||
[UserIdentity, Password, <<>>] ->
|
||||
add(UserGroup, UserIdentity, Password, Algorithm),
|
||||
do_import(UserGroup, File, Algorithm);
|
||||
[UserIdentity, Password] ->
|
||||
add(UserGroup, UserIdentity, Password, Algorithm),
|
||||
do_import(UserGroup, File, Algorithm);
|
||||
[UserID, PasswordHash, <<>>] ->
|
||||
import_user(UserGroup, UserID, PasswordHash),
|
||||
do_import(UserGroup, File);
|
||||
[UserID, PasswordHash] ->
|
||||
import_user(UserGroup, UserID, PasswordHash),
|
||||
do_import(UserGroup, File);
|
||||
_ ->
|
||||
{error, bad_format}
|
||||
end;
|
||||
|
@ -233,13 +252,18 @@ do_import(UserGroup, File, Algorithm) ->
|
|||
end.
|
||||
|
||||
-compile({inline, [add/4]}).
|
||||
add(UserGroup, UserIdentity, Password, Algorithm) ->
|
||||
Credential = #user_credential{user_identity = {UserGroup, UserIdentity},
|
||||
add(UserGroup, UserID, Password, Algorithm) ->
|
||||
Credential = #user_info{user_id = {UserGroup, UserID},
|
||||
password_hash = emqx_passwd:hash(Algorithm, Password)},
|
||||
mnesia:write(?TAB, Credential, write).
|
||||
|
||||
delete_user_credential(UserCredential) ->
|
||||
mnesia:delete_object(?TAB, UserCredential, write).
|
||||
import_user(UserGroup, UserID, PasswordHash) ->
|
||||
Credential = #user_info{user_id = {UserGroup, UserID},
|
||||
password_hash = PasswordHash},
|
||||
mnesia:write(?TAB, Credential, write).
|
||||
|
||||
delete_user2(UserInfo) ->
|
||||
mnesia:delete_object(?TAB, UserInfo, write).
|
||||
|
||||
%% TODO: Support other type
|
||||
get_user_identity(#{username := Username}, username) ->
|
||||
|
@ -259,6 +283,10 @@ trans(Fun, Args) ->
|
|||
end.
|
||||
|
||||
|
||||
to_binary(B) when is_binary(B) ->
|
||||
B;
|
||||
to_binary(L) when is_list(L) ->
|
||||
iolist_to_binary(L).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
myuser3,mypassword3
|
||||
myuser4,mypassword4
|
||||
myuser3,8d41233e39c95b5da13361e354e1c9e639f07b27d397463a8f91b71ee07ccfb2
|
||||
myuser4,5809df0154f3cb4ac5c3a5572eaca0c5f7f9d858e887fc675b2becab9feb19d1
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"myuser1": "mypassword1",
|
||||
"myuser2": "mypassword2"
|
||||
"myuser1": "09343625c6c123d3434932fe1ce08bae5ac00a8f95bd746e10491b0bafdd1817",
|
||||
"myuser2": "8767a7d316ad68cb607c7c805b859ffa78277dda13b7a3e2e8b53cad3cabbc6e"
|
||||
}
|
|
@ -37,10 +37,8 @@ end_per_suite(_) ->
|
|||
|
||||
t_chain(_) ->
|
||||
ChainID = <<"mychain">>,
|
||||
ChainParams = #{chain_id => ChainID,
|
||||
services => []},
|
||||
?assertEqual({ok, ChainID}, ?AUTH:create_chain(ChainParams)),
|
||||
?assertEqual({error, {already_exists, {chain, ChainID}}}, ?AUTH:create_chain(ChainParams)),
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:create_chain(#{id => ChainID})),
|
||||
?assertEqual({error, {already_exists, {chain, ChainID}}}, ?AUTH:create_chain(#{id => ChainID})),
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:lookup_chain(ChainID)),
|
||||
?assertEqual(ok, ?AUTH:delete_chain(ChainID)),
|
||||
?assertMatch({error, {not_found, {chain, ChainID}}}, ?AUTH:lookup_chain(ChainID)),
|
||||
|
@ -48,34 +46,33 @@ t_chain(_) ->
|
|||
|
||||
t_service(_) ->
|
||||
ChainID = <<"mychain">>,
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:create_chain(#{id => ChainID})),
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:lookup_chain(ChainID)),
|
||||
|
||||
ServiceName1 = <<"myservice1">>,
|
||||
ServiceParams1 = #{name => ServiceName1,
|
||||
type => mnesia,
|
||||
params => #{
|
||||
user_identity_type => <<"username">>,
|
||||
user_id_type => <<"username">>,
|
||||
password_hash_algorithm => <<"sha256">>}},
|
||||
ChainParams = #{chain_id => ChainID,
|
||||
services => [ServiceParams1]},
|
||||
?assertEqual({ok, ChainID}, ?AUTH:create_chain(ChainParams)),
|
||||
Service1 = ServiceParams1,
|
||||
?assertMatch({ok, #{id := ChainID, services := [Service1]}}, ?AUTH:lookup_chain(ChainID)),
|
||||
?assertEqual({ok, Service1}, ?AUTH:lookup_service(ChainID, ServiceName1)),
|
||||
?assertEqual({ok, [Service1]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({ok, [ServiceParams1]}, ?AUTH:add_services(ChainID, [ServiceParams1])),
|
||||
?assertEqual({ok, ServiceParams1}, ?AUTH:lookup_service(ChainID, ServiceName1)),
|
||||
?assertEqual({ok, [ServiceParams1]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({error, {already_exists, {service, ServiceName1}}}, ?AUTH:add_services(ChainID, [ServiceParams1])),
|
||||
|
||||
ServiceName2 = <<"myservice2">>,
|
||||
ServiceParams2 = ServiceParams1#{name => ServiceName2},
|
||||
?assertEqual(ok, ?AUTH:add_services(ChainID, [ServiceParams2])),
|
||||
Service2 = ServiceParams2,
|
||||
?assertMatch({ok, #{id := ChainID, services := [Service1, Service2]}}, ?AUTH:lookup_chain(ChainID)),
|
||||
?assertEqual({ok, Service2}, ?AUTH:lookup_service(ChainID, ServiceName2)),
|
||||
?assertEqual({ok, [Service1, Service2]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({ok, [ServiceParams2]}, ?AUTH:add_services(ChainID, [ServiceParams2])),
|
||||
?assertMatch({ok, #{id := ChainID, services := [ServiceParams1, ServiceParams2]}}, ?AUTH:lookup_chain(ChainID)),
|
||||
?assertEqual({ok, ServiceParams2}, ?AUTH:lookup_service(ChainID, ServiceName2)),
|
||||
?assertEqual({ok, [ServiceParams1, ServiceParams2]}, ?AUTH:list_services(ChainID)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:move_service_to_the_front(ChainID, ServiceName2)),
|
||||
?assertEqual({ok, [Service2, Service1]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({ok, [ServiceParams2, ServiceParams1]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual(ok, ?AUTH:move_service_to_the_end(ChainID, ServiceName2)),
|
||||
?assertEqual({ok, [Service1, Service2]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({ok, [ServiceParams1, ServiceParams2]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual(ok, ?AUTH:move_service_to_the_nth(ChainID, ServiceName2, 1)),
|
||||
?assertEqual({ok, [Service2, Service1]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({ok, [ServiceParams2, ServiceParams1]}, ?AUTH:list_services(ChainID)),
|
||||
?assertEqual({error, out_of_range}, ?AUTH:move_service_to_the_nth(ChainID, ServiceName2, 3)),
|
||||
?assertEqual({error, out_of_range}, ?AUTH:move_service_to_the_nth(ChainID, ServiceName2, 0)),
|
||||
?assertEqual(ok, ?AUTH:delete_services(ChainID, [ServiceName1, ServiceName2])),
|
||||
|
@ -85,40 +82,40 @@ t_service(_) ->
|
|||
|
||||
t_mnesia_service(_) ->
|
||||
ChainID = <<"mychain">>,
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:create_chain(#{id => ChainID})),
|
||||
|
||||
ServiceName = <<"myservice">>,
|
||||
ServiceParams = #{name => ServiceName,
|
||||
type => mnesia,
|
||||
params => #{
|
||||
user_identity_type => <<"username">>,
|
||||
user_id_type => <<"username">>,
|
||||
password_hash_algorithm => <<"sha256">>}},
|
||||
ChainParams = #{chain_id => ChainID,
|
||||
services => [ServiceParams]},
|
||||
?assertEqual({ok, ChainID}, ?AUTH:create_chain(ChainParams)),
|
||||
UserCredential = #{user_identity => <<"myuser">>,
|
||||
password => <<"mypass">>},
|
||||
?assertEqual(ok, ?AUTH:add_user_credential(ChainID, ServiceName, UserCredential)),
|
||||
?assertMatch({ok, #{user_identity := <<"myuser">>, password_hash := _}},
|
||||
?AUTH:lookup_user_credential(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual({ok, [ServiceParams]}, ?AUTH:add_services(ChainID, [ServiceParams])),
|
||||
|
||||
UserInfo = #{<<"user_id">> => <<"myuser">>,
|
||||
<<"password">> => <<"mypass">>},
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(ChainID, ServiceName, UserInfo)),
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(ChainID, ServiceName, <<"myuser">>)),
|
||||
ClientInfo = #{chain_id => ChainID,
|
||||
username => <<"myuser">>,
|
||||
password => <<"mypass">>},
|
||||
?assertEqual(ok, ?AUTH:authenticate(ClientInfo)),
|
||||
ClientInfo2 = ClientInfo#{username => <<"baduser">>},
|
||||
?assertEqual({error, user_credential_not_found}, ?AUTH:authenticate(ClientInfo2)),
|
||||
?assertEqual({error, user_not_found}, ?AUTH:authenticate(ClientInfo2)),
|
||||
ClientInfo3 = ClientInfo#{password => <<"badpass">>},
|
||||
?assertEqual({error, bad_password}, ?AUTH:authenticate(ClientInfo3)),
|
||||
UserCredential2 = UserCredential#{password => <<"mypass2">>},
|
||||
?assertEqual(ok, ?AUTH:update_user_credential(ChainID, ServiceName, UserCredential2)),
|
||||
UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>},
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(ChainID, ServiceName, <<"myuser">>, UserInfo2)),
|
||||
ClientInfo4 = ClientInfo#{password => <<"mypass2">>},
|
||||
?assertEqual(ok, ?AUTH:authenticate(ClientInfo4)),
|
||||
?assertEqual(ok, ?AUTH:delete_user_credential(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual({error, not_found}, ?AUTH:lookup_user_credential(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_user(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual({error, not_found}, ?AUTH:lookup_user(ChainID, ServiceName, <<"myuser">>)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:add_user_credential(ChainID, ServiceName, UserCredential)),
|
||||
?assertMatch({ok, #{user_identity := <<"myuser">>}}, ?AUTH:lookup_user_credential(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(ChainID, ServiceName, UserInfo)),
|
||||
?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_services(ChainID, [ServiceName])),
|
||||
?assertEqual(ok, ?AUTH:add_services(ChainID, [ServiceParams])),
|
||||
?assertMatch({error, not_found}, ?AUTH:lookup_user_credential(ChainID, ServiceName, <<"myuser">>)),
|
||||
?assertEqual({ok, [ServiceParams]}, ?AUTH:add_services(ChainID, [ServiceParams])),
|
||||
?assertMatch({error, not_found}, ?AUTH:lookup_user(ChainID, ServiceName, <<"myuser">>)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:delete_chain(ChainID)),
|
||||
?assertEqual([], ets:tab2list(mnesia_basic_auth)),
|
||||
|
@ -126,20 +123,21 @@ t_mnesia_service(_) ->
|
|||
|
||||
t_import(_) ->
|
||||
ChainID = <<"mychain">>,
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:create_chain(#{id => ChainID})),
|
||||
|
||||
ServiceName = <<"myservice">>,
|
||||
ServiceParams = #{name => ServiceName,
|
||||
type => mnesia,
|
||||
params => #{
|
||||
user_identity_type => <<"username">>,
|
||||
user_id_type => <<"username">>,
|
||||
password_hash_algorithm => <<"sha256">>}},
|
||||
ChainParams = #{chain_id => ChainID,
|
||||
services => [ServiceParams]},
|
||||
?assertEqual({ok, ChainID}, ?AUTH:create_chain(ChainParams)),
|
||||
?assertEqual({ok, [ServiceParams]}, ?AUTH:add_services(ChainID, [ServiceParams])),
|
||||
|
||||
Dir = code:lib_dir(emqx_authentication, test),
|
||||
?assertEqual(ok, ?AUTH:import_user_credentials(ChainID, ServiceName, filename:join([Dir, "data/user-credentials.json"]), json)),
|
||||
?assertEqual(ok, ?AUTH:import_user_credentials(ChainID, ServiceName, filename:join([Dir, "data/user-credentials.csv"]), csv)),
|
||||
?assertMatch({ok, #{user_identity := <<"myuser1">>}}, ?AUTH:lookup_user_credential(ChainID, ServiceName, <<"myuser1">>)),
|
||||
?assertMatch({ok, #{user_identity := <<"myuser3">>}}, ?AUTH:lookup_user_credential(ChainID, ServiceName, <<"myuser3">>)),
|
||||
?assertEqual(ok, ?AUTH:import_users(ChainID, ServiceName, filename:join([Dir, "data/user-credentials.json"]))),
|
||||
?assertEqual(ok, ?AUTH:import_users(ChainID, ServiceName, filename:join([Dir, "data/user-credentials.csv"]))),
|
||||
?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(ChainID, ServiceName, <<"myuser1">>)),
|
||||
?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(ChainID, ServiceName, <<"myuser3">>)),
|
||||
ClientInfo1 = #{chain_id => ChainID,
|
||||
username => <<"myuser1">>,
|
||||
password => <<"mypassword1">>},
|
||||
|
@ -152,30 +150,31 @@ t_import(_) ->
|
|||
|
||||
t_multi_mnesia_service(_) ->
|
||||
ChainID = <<"mychain">>,
|
||||
?assertMatch({ok, #{id := ChainID, services := []}}, ?AUTH:create_chain(#{id => ChainID})),
|
||||
|
||||
ServiceName1 = <<"myservice1">>,
|
||||
ServiceParams1 = #{name => ServiceName1,
|
||||
type => mnesia,
|
||||
params => #{
|
||||
user_identity_type => <<"username">>,
|
||||
user_id_type => <<"username">>,
|
||||
password_hash_algorithm => <<"sha256">>}},
|
||||
ServiceName2 = <<"myservice2">>,
|
||||
ServiceParams2 = #{name => ServiceName2,
|
||||
type => mnesia,
|
||||
params => #{
|
||||
user_identity_type => <<"clientid">>,
|
||||
user_id_type => <<"clientid">>,
|
||||
password_hash_algorithm => <<"sha256">>}},
|
||||
ChainParams = #{chain_id => ChainID,
|
||||
services => [ServiceParams1, ServiceParams2]},
|
||||
?assertEqual({ok, ChainID}, ?AUTH:create_chain(ChainParams)),
|
||||
?assertEqual({ok, [ServiceParams1]}, ?AUTH:add_services(ChainID, [ServiceParams1])),
|
||||
?assertEqual({ok, [ServiceParams2]}, ?AUTH:add_services(ChainID, [ServiceParams2])),
|
||||
|
||||
?assertEqual(ok, ?AUTH:add_user_credential(ChainID,
|
||||
ServiceName1,
|
||||
#{user_identity => <<"myuser">>,
|
||||
password => <<"mypass1">>})),
|
||||
?assertEqual(ok, ?AUTH:add_user_credential(ChainID,
|
||||
ServiceName2,
|
||||
#{user_identity => <<"myclient">>,
|
||||
password => <<"mypass2">>})),
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}},
|
||||
?AUTH:add_user(ChainID, ServiceName1,
|
||||
#{<<"user_id">> => <<"myuser">>,
|
||||
<<"password">> => <<"mypass1">>})),
|
||||
?assertEqual({ok, #{user_id => <<"myclient">>}},
|
||||
?AUTH:add_user(ChainID, ServiceName2,
|
||||
#{<<"user_id">> => <<"myclient">>,
|
||||
<<"password">> => <<"mypass2">>})),
|
||||
ClientInfo1 = #{chain_id => ChainID,
|
||||
username => <<"myuser">>,
|
||||
clientid => <<"myclient">>,
|
||||
|
|
Loading…
Reference in New Issue