emqx/apps/emqx_authentication/src/emqx_authentication_api.erl
2021-06-08 11:40:46 +08:00

408 lines
14 KiB
Erlang

%%--------------------------------------------------------------------
%% 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_authentication_api).
-export([ create_chain/2
, delete_chain/2
, lookup_chain/2
, list_chains/2
, add_service/2
, delete_service/2
, update_service/2
, lookup_service/2
, list_services/2
, move_service/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 => create_chain,
method => 'POST',
path => "/authentication/chains",
func => create_chain,
descr => "Create a chain"
}).
-rest_api(#{name => delete_chain,
method => 'DELETE',
path => "/authentication/chains/:bin:id",
func => delete_chain,
descr => "Delete chain"
}).
-rest_api(#{name => lookup_chain,
method => 'GET',
path => "/authentication/chains/:bin: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:id/services",
func => add_service,
descr => "Add service to chain"
}).
-rest_api(#{name => delete_service,
method => 'DELETE',
path => "/authentication/chains/:bin:id/services/:bin:service_name",
func => delete_service,
descr => "Delete service from chain"
}).
-rest_api(#{name => update_service,
method => 'PUT',
path => "/authentication/chains/:bin:id/services/:bin:service_name",
func => update_service,
descr => "Update service in chain"
}).
-rest_api(#{name => lookup_service,
method => 'GET',
path => "/authentication/chains/:bin:id/services/:bin:service_name",
func => lookup_service,
descr => "Lookup service in chain"
}).
-rest_api(#{name => list_services,
method => 'GET',
path => "/authentication/chains/:bin:id/services",
func => list_services,
descr => "List services in chain"
}).
-rest_api(#{name => move_service,
method => 'POST',
path => "/authentication/chains/:bin: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:id/services/:bin:service_name/import-users",
func => import_users,
descr => "Import users"
}).
-rest_api(#{name => add_user,
method => 'POST',
path => "/authentication/chains/:bin:id/services/:bin:service_name/users",
func => add_user,
descr => "Add user"
}).
-rest_api(#{name => delete_user,
method => 'DELETE',
path => "/authentication/chains/:bin: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: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: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: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, #{<<"id">> := ChainID}) ->
case emqx_authentication:create_chain(#{id => ChainID}) of
{ok, Chain} ->
return({ok, Chain});
{error, Reason} ->
return(serialize_error(Reason))
end;
do_create_chain(_Binding, _Params) ->
return(serialize_error({missing_parameter, id})).
delete_chain(Binding, Params) ->
do_delete_chain(uri_decode(Binding), maps:from_list(Params)).
do_delete_chain(#{id := ChainID}, _Params) ->
case emqx_authentication:delete_chain(ChainID) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end.
lookup_chain(Binding, Params) ->
do_lookup_chain(uri_decode(Binding), maps:from_list(Params)).
do_lookup_chain(#{id := ChainID}, _Params) ->
case emqx_authentication:lookup_chain(ChainID) of
{ok, Chain} ->
return({ok, Chain});
{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(#{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;
%% 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})).
delete_service(Binding, Params) ->
do_delete_service(uri_decode(Binding), maps:from_list(Params)).
do_delete_service(#{id := ChainID,
service_name := ServiceName}, _Params) ->
case emqx_authentication:delete_services(ChainID, [ServiceName]) of
ok ->
return(ok);
{error, Reason} ->
return(serialize_error(Reason))
end.
update_service(Binding, Params) ->
do_update_service(uri_decode(Binding), maps:from_list(Params)).
%% TOOD: PUT method supports creation and update
do_update_service(#{id := ChainID,
service_name := ServiceName}, Params) ->
case emqx_authentication:update_service(ChainID, ServiceName, Params) of
{ok, Service} ->
return({ok, Service});
{error, Reason} ->
return(serialize_error(Reason))
end.
lookup_service(Binding, Params) ->
do_lookup_service(uri_decode(Binding), maps:from_list(Params)).
do_lookup_service(#{id := ChainID,
service_name := ServiceName}, _Params) ->
case emqx_authentication:lookup_service(ChainID, ServiceName) of
{ok, Service} ->
return({ok, Service});
{error, Reason} ->
return(serialize_error(Reason))
end.
list_services(Binding, Params) ->
do_list_services(uri_decode(Binding), maps:from_list(Params)).
do_list_services(#{id := ChainID}, _Params) ->
case emqx_authentication:list_services(ChainID) of
{ok, Services} ->
return({ok, Services});
{error, Reason} ->
return(serialize_error(Reason))
end.
move_service(Binding, Params) ->
do_move_service(uri_decode(Binding), maps:from_list(Params)).
do_move_service(#{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(serialize_error(Reason))
end;
do_move_service(#{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(#{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">>})).
import_users(Binding, Params) ->
do_import_users(uri_decode(Binding), maps:from_list(Params)).
do_import_users(#{id := ChainID, service_name := ServiceName},
#{<<"filename">> := Filename}) ->
case emqx_authentication:import_users(ChainID, ServiceName, 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})).
add_user(Binding, Params) ->
do_add_user(uri_decode(Binding), maps:from_list(Params)).
do_add_user(#{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(#{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(#{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(#{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(#{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
%%------------------------------------------------------------------------------
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}}) ->
{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("Service 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(service) ->
"Service";
serialize_type(chain) ->
"Chain";
serialize_type(service_type) ->
"Service type".
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).