228 lines
7.8 KiB
Erlang
228 lines
7.8 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020-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_sasl_api).
|
|
|
|
-include("emqx_sasl.hrl").
|
|
|
|
-import(minirest, [ return/0
|
|
, return/1
|
|
]).
|
|
|
|
-rest_api(#{name => add,
|
|
method => 'POST',
|
|
path => "/sasl",
|
|
func => add,
|
|
descr => "Add authentication information"}).
|
|
|
|
-rest_api(#{name => delete,
|
|
method => 'DELETE',
|
|
path => "/sasl",
|
|
func => delete,
|
|
descr => "Delete authentication information"}).
|
|
|
|
-rest_api(#{name => update,
|
|
method => 'PUT',
|
|
path => "/sasl",
|
|
func => update,
|
|
descr => "Update authentication information"}).
|
|
|
|
-rest_api(#{name => get,
|
|
method => 'GET',
|
|
path => "/sasl",
|
|
func => get,
|
|
descr => "Get authentication information"}).
|
|
|
|
-export([ add/2
|
|
, delete/2
|
|
, update/2
|
|
, get/2
|
|
]).
|
|
|
|
add(_Bindings, Params) ->
|
|
case pipeline([fun ensure_required_add_params/1,
|
|
fun validate_params/1,
|
|
fun do_add/1], Params) of
|
|
ok ->
|
|
return();
|
|
{error, Reason} ->
|
|
return({error, Reason})
|
|
end.
|
|
|
|
delete(_Bindings, Params) ->
|
|
case pipeline([fun ensure_required_delete_params/1,
|
|
fun validate_params/1,
|
|
fun do_delete/1], Params) of
|
|
ok ->
|
|
return();
|
|
{error, Reason} ->
|
|
return({error, Reason})
|
|
end.
|
|
|
|
update(_Bindings, Params) ->
|
|
case pipeline([fun ensure_required_add_params/1,
|
|
fun validate_params/1,
|
|
fun do_update/1], Params) of
|
|
ok ->
|
|
return();
|
|
{error, Reason} ->
|
|
return({error, Reason})
|
|
end.
|
|
|
|
get(Bindings, Params) when is_list(Params) ->
|
|
get(Bindings, maps:from_list(Params));
|
|
|
|
get(_Bindings, #{<<"mechanism">> := Mechanism0,
|
|
<<"username">> := Username0}) ->
|
|
Mechanism = urldecode(Mechanism0),
|
|
Username = urldecode(Username0),
|
|
case Mechanism of
|
|
<<"SCRAM-SHA-1">> ->
|
|
case emqx_sasl_scram:lookup(Username) of
|
|
{ok, AuthInfo = #{salt := Salt}} ->
|
|
return({ok, AuthInfo#{salt => base64:decode(Salt)}});
|
|
{error, Reason} ->
|
|
return({error, Reason})
|
|
end;
|
|
_ ->
|
|
return({error, unsupported_mechanism})
|
|
end;
|
|
get(_Bindings, #{<<"mechanism">> := Mechanism}) ->
|
|
case urldecode(Mechanism) of
|
|
<<"SCRAM-SHA-1">> ->
|
|
Data = #{Mechanism => mnesia:dirty_all_keys(?SCRAM_AUTH_TAB)},
|
|
return({ok, Data});
|
|
_ ->
|
|
return({error, <<"Unsupported mechanism">>})
|
|
end;
|
|
|
|
get(_Bindings, _Params) ->
|
|
Data = lists:foldl(fun(Mechanism, Acc) ->
|
|
case Mechanism of
|
|
<<"SCRAM-SHA-1">> ->
|
|
[#{Mechanism => mnesia:dirty_all_keys(?SCRAM_AUTH_TAB)} | Acc]
|
|
end
|
|
end, [], emqx_sasl:supported()),
|
|
return({ok, Data}).
|
|
|
|
ensure_required_add_params(Params) when is_list(Params) ->
|
|
case proplists:get_value(<<"mechanism">>, Params) of
|
|
undefined ->
|
|
{missing, missing_required_param};
|
|
Mechaism ->
|
|
ensure_required_add_params(Mechaism, Params)
|
|
end.
|
|
|
|
ensure_required_add_params(<<"SCRAM-SHA-1">>, Params) ->
|
|
Required = [<<"username">>, <<"password">>, <<"salt">>],
|
|
case erlang:map_size(maps:with(Required, maps:from_list(Params))) =:= erlang:length(Required) of
|
|
true -> ok;
|
|
false -> {missing, missing_required_param}
|
|
end;
|
|
ensure_required_add_params(_, _) ->
|
|
{error, unsupported_mechanism}.
|
|
|
|
ensure_required_delete_params(Params) when is_list(Params) ->
|
|
case proplists:get_value(<<"mechanism">>, Params) of
|
|
undefined ->
|
|
{missing, missing_required_param};
|
|
Mechaism ->
|
|
ensure_required_delete_params(Mechaism, Params)
|
|
end.
|
|
|
|
ensure_required_delete_params(<<"SCRAM-SHA-1">>, Params) ->
|
|
Required = [<<"username">>],
|
|
case erlang:map_size(maps:with(Required, maps:from_list(Params))) =:= erlang:length(Required) of
|
|
true -> ok;
|
|
false -> {missing, missing_required_param}
|
|
end;
|
|
ensure_required_delete_params(_, _) ->
|
|
{error, unsupported_mechanism}.
|
|
|
|
validate_params(Params) ->
|
|
Mechaism = proplists:get_value(<<"mechanism">>, Params),
|
|
validate_params(Mechaism, Params).
|
|
|
|
validate_params(<<"SCRAM-SHA-1">>, []) ->
|
|
ok;
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"username">>, Username} | More]) when is_binary(Username) ->
|
|
validate_params(<<"SCRAM-SHA-1">>, More);
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"username">>, _} | _]) ->
|
|
{error, invalid_username};
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"password">>, Password} | More]) when is_binary(Password) ->
|
|
validate_params(<<"SCRAM-SHA-1">>, More);
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"password">>, _} | _]) ->
|
|
{error, invalid_password};
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"salt">>, Salt} | More]) when is_binary(Salt) ->
|
|
validate_params(<<"SCRAM-SHA-1">>, More);
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"salt">>, _} | _]) ->
|
|
{error, invalid_salt};
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"iteration_count">>, IterationCount} | More]) when is_integer(IterationCount) ->
|
|
validate_params(<<"SCRAM-SHA-1">>, More);
|
|
validate_params(<<"SCRAM-SHA-1">>, [{<<"iteration_count">>, _} | _]) ->
|
|
{error, invalid_iteration_count};
|
|
validate_params(<<"SCRAM-SHA-1">>, [_ | More]) ->
|
|
validate_params(<<"SCRAM-SHA-1">>, More).
|
|
|
|
do_add(Params) ->
|
|
Mechaism = proplists:get_value(<<"mechanism">>, Params),
|
|
do_add(Mechaism, Params).
|
|
|
|
do_add(<<"SCRAM-SHA-1">>, Params) ->
|
|
Username = proplists:get_value(<<"username">>, Params),
|
|
Password = proplists:get_value(<<"password">>, Params),
|
|
Salt = proplists:get_value(<<"salt">>, Params),
|
|
IterationCount = proplists:get_value(<<"iteration_count">>, Params, 4096),
|
|
emqx_sasl_scram:add(Username, Password, Salt, IterationCount);
|
|
do_add(_, _) ->
|
|
{error, unsupported_mechanism}.
|
|
|
|
do_delete(Params) ->
|
|
Mechaism = proplists:get_value(<<"mechanism">>, Params),
|
|
do_delete(Mechaism, Params).
|
|
|
|
do_delete(<<"SCRAM-SHA-1">>, Params) ->
|
|
Username = proplists:get_value(<<"username">>, Params),
|
|
emqx_sasl_scram:delete(Username);
|
|
do_delete(_, _) ->
|
|
{error, unsupported_mechanism}.
|
|
|
|
do_update(Params) ->
|
|
Mechaism = proplists:get_value(<<"mechanism">>, Params),
|
|
do_update(Mechaism, Params).
|
|
|
|
do_update(<<"SCRAM-SHA-1">>, Params) ->
|
|
Username = proplists:get_value(<<"username">>, Params),
|
|
Password = proplists:get_value(<<"password">>, Params),
|
|
Salt = proplists:get_value(<<"salt">>, Params),
|
|
IterationCount = proplists:get_value(<<"iteration_count">>, Params, 4096),
|
|
emqx_sasl_scram:update(Username, Password, Salt, IterationCount);
|
|
do_update(_, _) ->
|
|
{error, unsupported_mechanism}.
|
|
|
|
pipeline([], _) ->
|
|
ok;
|
|
pipeline([Fun | More], Params) ->
|
|
case Fun(Params) of
|
|
ok ->
|
|
pipeline(More, Params);
|
|
{error, Reason} ->
|
|
{error, Reason}
|
|
end.
|
|
|
|
urldecode(S) ->
|
|
emqx_http_lib:uri_decode(S).
|