1486 lines
49 KiB
Erlang
1486 lines
49 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2021-2023 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_api).
|
|
|
|
-behaviour(minirest_api).
|
|
|
|
-include("emqx_authn.hrl").
|
|
-include_lib("emqx/include/logger.hrl").
|
|
-include_lib("emqx/include/emqx_placeholder.hrl").
|
|
-include_lib("emqx/include/emqx_authentication.hrl").
|
|
-include_lib("hocon/include/hoconsc.hrl").
|
|
|
|
-import(hoconsc, [mk/2, ref/1, ref/2]).
|
|
-import(emqx_dashboard_swagger, [error_codes/2]).
|
|
|
|
-define(BAD_REQUEST, 'BAD_REQUEST').
|
|
-define(NOT_FOUND, 'NOT_FOUND').
|
|
-define(ALREADY_EXISTS, 'ALREADY_EXISTS').
|
|
-define(INTERNAL_ERROR, 'INTERNAL_ERROR').
|
|
|
|
% Swagger
|
|
|
|
-define(API_TAGS_GLOBAL, [<<"Authentication">>]).
|
|
-define(API_TAGS_SINGLE, [<<"Listener Authentication">>]).
|
|
|
|
-export([
|
|
api_spec/0,
|
|
paths/0,
|
|
schema/1
|
|
]).
|
|
|
|
-export([
|
|
roots/0,
|
|
fields/1
|
|
]).
|
|
|
|
-export([
|
|
authenticators/2,
|
|
authenticator/2,
|
|
authenticator_status/2,
|
|
listener_authenticators/2,
|
|
listener_authenticator/2,
|
|
listener_authenticator_status/2,
|
|
authenticator_position/2,
|
|
listener_authenticator_position/2,
|
|
authenticator_users/2,
|
|
authenticator_user/2,
|
|
listener_authenticator_users/2,
|
|
listener_authenticator_user/2,
|
|
lookup_from_local_node/2,
|
|
lookup_from_all_nodes/2
|
|
]).
|
|
|
|
-export([
|
|
authenticator_examples/0,
|
|
request_user_create_examples/0,
|
|
request_user_update_examples/0,
|
|
response_user_examples/0,
|
|
response_users_example/0
|
|
]).
|
|
|
|
%% export these funcs for gateway
|
|
-export([
|
|
list_users/3,
|
|
add_user/3,
|
|
delete_user/3,
|
|
find_user/3,
|
|
update_user/4,
|
|
serialize_error/1,
|
|
aggregate_metrics/1,
|
|
|
|
with_chain/2,
|
|
param_auth_id/0,
|
|
param_listener_id/0
|
|
]).
|
|
|
|
-elvis([{elvis_style, god_modules, disable}]).
|
|
|
|
api_spec() ->
|
|
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
|
|
|
|
paths() ->
|
|
[
|
|
"/authentication",
|
|
"/authentication/:id",
|
|
"/authentication/:id/status",
|
|
"/authentication/:id/position/:position",
|
|
"/authentication/:id/users",
|
|
"/authentication/:id/users/:user_id",
|
|
|
|
"/listeners/:listener_id/authentication",
|
|
"/listeners/:listener_id/authentication/:id",
|
|
"/listeners/:listener_id/authentication/:id/status",
|
|
"/listeners/:listener_id/authentication/:id/position/:position",
|
|
"/listeners/:listener_id/authentication/:id/users",
|
|
"/listeners/:listener_id/authentication/:id/users/:user_id"
|
|
].
|
|
|
|
roots() ->
|
|
[
|
|
request_user_create,
|
|
request_user_update,
|
|
response_user,
|
|
response_users
|
|
].
|
|
|
|
fields(request_user_create) ->
|
|
[
|
|
{user_id, mk(binary(), #{required => true})}
|
|
| fields(request_user_update)
|
|
];
|
|
fields(request_user_update) ->
|
|
[
|
|
{password, mk(binary(), #{required => true})},
|
|
{is_superuser, mk(boolean(), #{default => false, required => false})}
|
|
];
|
|
fields(response_user) ->
|
|
[
|
|
{user_id, mk(binary(), #{required => true})},
|
|
{is_superuser, mk(boolean(), #{default => false, required => false})}
|
|
];
|
|
fields(response_users) ->
|
|
paginated_list_type(ref(response_user)).
|
|
|
|
schema("/authentication") ->
|
|
#{
|
|
'operationId' => authenticators,
|
|
get => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_get),
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
hoconsc:array(emqx_authn_schema:authenticator_type()),
|
|
authenticator_array_example()
|
|
)
|
|
}
|
|
},
|
|
post => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_post),
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/authentication/:id") ->
|
|
#{
|
|
'operationId' => authenticator,
|
|
get => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_get),
|
|
parameters => [param_auth_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
put => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_put),
|
|
parameters => [param_auth_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
responses => #{
|
|
204 => <<"Authenticator updated">>,
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>),
|
|
409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>)
|
|
}
|
|
},
|
|
delete => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_delete),
|
|
parameters => [param_auth_id()],
|
|
responses => #{
|
|
204 => <<"Authenticator deleted">>,
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/authentication/:id/status") ->
|
|
#{
|
|
'operationId' => authenticator_status,
|
|
get => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_status_get),
|
|
parameters => [param_auth_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
|
|
status_metrics_example()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>),
|
|
500 => error_codes([?INTERNAL_ERROR], <<"Internal Service Error">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/listeners/:listener_id/authentication") ->
|
|
#{
|
|
'operationId' => listener_authenticators,
|
|
get => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_get),
|
|
parameters => [param_listener_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
hoconsc:array(emqx_authn_schema:authenticator_type()),
|
|
authenticator_array_example()
|
|
)
|
|
}
|
|
},
|
|
post => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_post),
|
|
parameters => [param_listener_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/listeners/:listener_id/authentication/:id") ->
|
|
#{
|
|
'operationId' => listener_authenticator,
|
|
get => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_get),
|
|
parameters => [param_listener_id(), param_auth_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
put => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_put),
|
|
parameters => [param_listener_id(), param_auth_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
emqx_authn_schema:authenticator_type(),
|
|
authenticator_examples()
|
|
),
|
|
responses => #{
|
|
204 => <<"Authenticator updated">>,
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>),
|
|
409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>)
|
|
}
|
|
},
|
|
delete => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_delete),
|
|
parameters => [param_listener_id(), param_auth_id()],
|
|
responses => #{
|
|
204 => <<"Authenticator deleted">>,
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/listeners/:listener_id/authentication/:id/status") ->
|
|
#{
|
|
'operationId' => listener_authenticator_status,
|
|
get => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_status_get),
|
|
parameters => [param_listener_id(), param_auth_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
hoconsc:ref(emqx_authn_schema, "metrics_status_fields"),
|
|
status_metrics_example()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/authentication/:id/position/:position") ->
|
|
#{
|
|
'operationId' => authenticator_position,
|
|
put => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_position_put),
|
|
parameters => [param_auth_id(), param_position()],
|
|
responses => #{
|
|
204 => <<"Authenticator moved">>,
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/listeners/:listener_id/authentication/:id/position/:position") ->
|
|
#{
|
|
'operationId' => listener_authenticator_position,
|
|
put => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_position_put),
|
|
parameters => [param_listener_id(), param_auth_id(), param_position()],
|
|
responses => #{
|
|
204 => <<"Authenticator moved">>,
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/authentication/:id/users") ->
|
|
#{
|
|
'operationId' => authenticator_users,
|
|
post => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_users_post),
|
|
parameters => [param_auth_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
ref(request_user_create),
|
|
request_user_create_examples()
|
|
),
|
|
responses => #{
|
|
201 => emqx_dashboard_swagger:schema_with_examples(
|
|
ref(response_user),
|
|
response_user_examples()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
get => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_users_get),
|
|
parameters => [
|
|
param_auth_id(),
|
|
ref(emqx_dashboard_swagger, page),
|
|
ref(emqx_dashboard_swagger, limit),
|
|
{like_user_id,
|
|
mk(binary(), #{
|
|
in => query,
|
|
desc => ?DESC(like_user_id),
|
|
required => false
|
|
})},
|
|
{is_superuser,
|
|
mk(boolean(), #{
|
|
in => query,
|
|
desc => ?DESC(is_superuser),
|
|
required => false
|
|
})}
|
|
],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
ref(response_users),
|
|
response_users_example()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/listeners/:listener_id/authentication/:id/users") ->
|
|
#{
|
|
'operationId' => listener_authenticator_users,
|
|
post => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_users_post),
|
|
parameters => [param_auth_id(), param_listener_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
ref(request_user_create),
|
|
request_user_create_examples()
|
|
),
|
|
responses => #{
|
|
201 => emqx_dashboard_swagger:schema_with_examples(
|
|
ref(response_user),
|
|
response_user_examples()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
get => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_users_get),
|
|
parameters => [
|
|
param_listener_id(),
|
|
param_auth_id(),
|
|
ref(emqx_dashboard_swagger, page),
|
|
ref(emqx_dashboard_swagger, limit),
|
|
{is_superuser,
|
|
mk(boolean(), #{
|
|
in => query,
|
|
desc => ?DESC(is_superuser),
|
|
required => false
|
|
})}
|
|
],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
ref(response_users),
|
|
response_users_example()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/authentication/:id/users/:user_id") ->
|
|
#{
|
|
'operationId' => authenticator_user,
|
|
get => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_users_user_id_get),
|
|
parameters => [param_auth_id(), param_user_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
ref(response_user),
|
|
response_user_examples()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
put => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_users_user_id_put),
|
|
parameters => [param_auth_id(), param_user_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
|
ref(request_user_update),
|
|
request_user_update_examples()
|
|
),
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
ref(response_user),
|
|
response_user_examples()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
delete => #{
|
|
tags => ?API_TAGS_GLOBAL,
|
|
description => ?DESC(authentication_id_users_user_id_delete),
|
|
parameters => [param_auth_id(), param_user_id()],
|
|
responses => #{
|
|
204 => <<"User deleted">>,
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
};
|
|
schema("/listeners/:listener_id/authentication/:id/users/:user_id") ->
|
|
#{
|
|
'operationId' => listener_authenticator_user,
|
|
get => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_users_user_id_get),
|
|
parameters => [param_listener_id(), param_auth_id(), param_user_id()],
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
ref(response_user),
|
|
response_user_examples()
|
|
),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
put => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_users_user_id_put),
|
|
parameters => [param_listener_id(), param_auth_id(), param_user_id()],
|
|
'requestBody' => emqx_dashboard_swagger:schema_with_example(
|
|
ref(request_user_update),
|
|
request_user_update_examples()
|
|
),
|
|
responses => #{
|
|
200 => emqx_dashboard_swagger:schema_with_example(
|
|
ref(response_user),
|
|
response_user_examples()
|
|
),
|
|
400 => error_codes([?BAD_REQUEST], <<"Bad Request">>),
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
},
|
|
delete => #{
|
|
tags => ?API_TAGS_SINGLE,
|
|
description => ?DESC(listeners_listener_id_authentication_id_users_user_id_delete),
|
|
parameters => [param_listener_id(), param_auth_id(), param_user_id()],
|
|
responses => #{
|
|
204 => <<"User deleted">>,
|
|
404 => error_codes([?NOT_FOUND], <<"Not Found">>)
|
|
}
|
|
}
|
|
}.
|
|
|
|
param_auth_id() ->
|
|
{
|
|
id,
|
|
mk(binary(), #{
|
|
in => path,
|
|
desc => ?DESC(param_auth_id),
|
|
required => true
|
|
})
|
|
}.
|
|
|
|
param_listener_id() ->
|
|
{
|
|
listener_id,
|
|
mk(binary(), #{
|
|
in => path,
|
|
desc => ?DESC(param_listener_id),
|
|
required => true,
|
|
example => emqx_listeners:id_example()
|
|
})
|
|
}.
|
|
|
|
param_position() ->
|
|
{
|
|
position,
|
|
mk(binary(), #{
|
|
in => path,
|
|
desc => ?DESC(param_position),
|
|
required => true,
|
|
example => "before:password_based:built_in_database"
|
|
})
|
|
}.
|
|
|
|
param_user_id() ->
|
|
{
|
|
user_id,
|
|
mk(binary(), #{
|
|
in => path,
|
|
desc => ?DESC(param_user_id)
|
|
})
|
|
}.
|
|
|
|
authenticators(post, #{body := Config}) ->
|
|
create_authenticator([authentication], ?GLOBAL, Config);
|
|
authenticators(get, _Params) ->
|
|
list_authenticators([authentication]).
|
|
|
|
authenticator(get, #{bindings := #{id := AuthenticatorID}}) ->
|
|
list_authenticator(?GLOBAL, [authentication], AuthenticatorID);
|
|
authenticator(put, #{bindings := #{id := AuthenticatorID}, body := Config}) ->
|
|
update_authenticator([authentication], ?GLOBAL, AuthenticatorID, Config);
|
|
authenticator(delete, #{bindings := #{id := AuthenticatorID}}) ->
|
|
delete_authenticator([authentication], ?GLOBAL, AuthenticatorID).
|
|
|
|
authenticator_status(get, #{bindings := #{id := AuthenticatorID}}) ->
|
|
with_authenticator(
|
|
AuthenticatorID,
|
|
[authentication],
|
|
fun(_) -> lookup_from_all_nodes(?GLOBAL, AuthenticatorID) end
|
|
).
|
|
|
|
listener_authenticators(post, #{bindings := #{listener_id := ListenerID}, body := Config}) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, ChainName) ->
|
|
create_authenticator(
|
|
[listeners, Type, Name, authentication],
|
|
ChainName,
|
|
Config
|
|
)
|
|
end
|
|
);
|
|
listener_authenticators(get, #{bindings := #{listener_id := ListenerID}}) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, _) ->
|
|
list_authenticators([listeners, Type, Name, authentication])
|
|
end
|
|
).
|
|
|
|
listener_authenticator(get, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, ChainName) ->
|
|
list_authenticator(
|
|
ChainName,
|
|
[listeners, Type, Name, authentication],
|
|
AuthenticatorID
|
|
)
|
|
end
|
|
);
|
|
listener_authenticator(
|
|
put,
|
|
#{
|
|
bindings := #{listener_id := ListenerID, id := AuthenticatorID},
|
|
body := Config
|
|
}
|
|
) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, ChainName) ->
|
|
update_authenticator(
|
|
[listeners, Type, Name, authentication],
|
|
ChainName,
|
|
AuthenticatorID,
|
|
Config
|
|
)
|
|
end
|
|
);
|
|
listener_authenticator(
|
|
delete,
|
|
#{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}
|
|
) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, ChainName) ->
|
|
delete_authenticator(
|
|
[listeners, Type, Name, authentication],
|
|
ChainName,
|
|
AuthenticatorID
|
|
)
|
|
end
|
|
).
|
|
|
|
listener_authenticator_status(
|
|
get,
|
|
#{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}
|
|
) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, ChainName) ->
|
|
with_authenticator(
|
|
AuthenticatorID,
|
|
[listeners, Type, Name, authentication],
|
|
fun(_) -> lookup_from_all_nodes(ChainName, AuthenticatorID) end
|
|
)
|
|
end
|
|
).
|
|
|
|
authenticator_position(
|
|
put,
|
|
#{bindings := #{id := AuthenticatorID, position := Position}}
|
|
) ->
|
|
move_authenticator([authentication], ?GLOBAL, AuthenticatorID, Position).
|
|
|
|
listener_authenticator_position(
|
|
put,
|
|
#{bindings := #{listener_id := ListenerID, id := AuthenticatorID, position := Position}}
|
|
) ->
|
|
with_listener(
|
|
ListenerID,
|
|
fun(Type, Name, ChainName) ->
|
|
move_authenticator(
|
|
[listeners, Type, Name, authentication],
|
|
ChainName,
|
|
AuthenticatorID,
|
|
Position
|
|
)
|
|
end
|
|
).
|
|
|
|
authenticator_users(post, #{bindings := #{id := AuthenticatorID}, body := UserInfo}) ->
|
|
add_user(?GLOBAL, AuthenticatorID, UserInfo);
|
|
authenticator_users(get, #{bindings := #{id := AuthenticatorID}, query_string := QueryString}) ->
|
|
list_users(?GLOBAL, AuthenticatorID, QueryString).
|
|
|
|
authenticator_user(put, #{
|
|
bindings := #{
|
|
id := AuthenticatorID,
|
|
user_id := UserID
|
|
},
|
|
body := UserInfo
|
|
}) ->
|
|
update_user(?GLOBAL, AuthenticatorID, UserID, UserInfo);
|
|
authenticator_user(get, #{bindings := #{id := AuthenticatorID, user_id := UserID}}) ->
|
|
find_user(?GLOBAL, AuthenticatorID, UserID);
|
|
authenticator_user(delete, #{bindings := #{id := AuthenticatorID, user_id := UserID}}) ->
|
|
delete_user(?GLOBAL, AuthenticatorID, UserID).
|
|
|
|
listener_authenticator_users(post, #{
|
|
bindings := #{
|
|
listener_id := ListenerID,
|
|
id := AuthenticatorID
|
|
},
|
|
body := UserInfo
|
|
}) ->
|
|
with_chain(
|
|
ListenerID,
|
|
fun(ChainName) ->
|
|
add_user(ChainName, AuthenticatorID, UserInfo)
|
|
end
|
|
);
|
|
listener_authenticator_users(get, #{
|
|
bindings := #{
|
|
listener_id := ListenerID,
|
|
id := AuthenticatorID
|
|
},
|
|
query_string := PageParams
|
|
}) ->
|
|
with_chain(
|
|
ListenerID,
|
|
fun(ChainName) ->
|
|
list_users(ChainName, AuthenticatorID, PageParams)
|
|
end
|
|
).
|
|
|
|
listener_authenticator_user(put, #{
|
|
bindings := #{
|
|
listener_id := ListenerID,
|
|
id := AuthenticatorID,
|
|
user_id := UserID
|
|
},
|
|
body := UserInfo
|
|
}) ->
|
|
with_chain(
|
|
ListenerID,
|
|
fun(ChainName) ->
|
|
update_user(ChainName, AuthenticatorID, UserID, UserInfo)
|
|
end
|
|
);
|
|
listener_authenticator_user(get, #{
|
|
bindings := #{
|
|
listener_id := ListenerID,
|
|
id := AuthenticatorID,
|
|
user_id := UserID
|
|
}
|
|
}) ->
|
|
with_chain(
|
|
ListenerID,
|
|
fun(ChainName) ->
|
|
find_user(ChainName, AuthenticatorID, UserID)
|
|
end
|
|
);
|
|
listener_authenticator_user(delete, #{
|
|
bindings := #{
|
|
listener_id := ListenerID,
|
|
id := AuthenticatorID,
|
|
user_id := UserID
|
|
}
|
|
}) ->
|
|
with_chain(
|
|
ListenerID,
|
|
fun(ChainName) ->
|
|
delete_user(ChainName, AuthenticatorID, UserID)
|
|
end
|
|
).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Internal functions
|
|
%%------------------------------------------------------------------------------
|
|
|
|
with_authenticator(AuthenticatorID, ConfKeyPath, Fun) ->
|
|
case find_authenticator_config(AuthenticatorID, ConfKeyPath) of
|
|
{ok, AuthenticatorConfig} ->
|
|
Fun(AuthenticatorConfig);
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end.
|
|
|
|
find_authenticator_config(AuthenticatorID, ConfKeyPath) ->
|
|
AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath),
|
|
find_config(AuthenticatorID, AuthenticatorsConfig).
|
|
|
|
with_listener(ListenerID, Fun) ->
|
|
case find_listener(ListenerID) of
|
|
{ok, {BType, BName}} ->
|
|
Type = binary_to_existing_atom(BType),
|
|
Name = binary_to_existing_atom(BName),
|
|
ChainName = binary_to_atom(ListenerID),
|
|
Fun(Type, Name, ChainName);
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end.
|
|
|
|
find_listener(ListenerID) ->
|
|
case binary:split(ListenerID, <<":">>) of
|
|
[BType, BName] ->
|
|
case emqx_config:find([listeners, BType, BName]) of
|
|
{ok, _} ->
|
|
{ok, {BType, BName}};
|
|
{not_found, _, _} ->
|
|
{error, {not_found, {listener, ListenerID}}}
|
|
end;
|
|
_ ->
|
|
{error, {not_found, {listener, ListenerID}}}
|
|
end.
|
|
|
|
with_chain(ListenerID, Fun) ->
|
|
{ok, ChainNames} = emqx_authentication:list_chain_names(),
|
|
ListenerChainName =
|
|
[Name || Name <- ChainNames, atom_to_binary(Name) =:= ListenerID],
|
|
case ListenerChainName of
|
|
[ChainName] ->
|
|
Fun(ChainName);
|
|
_ ->
|
|
serialize_error({not_found, {chain, ListenerID}})
|
|
end.
|
|
|
|
create_authenticator(ConfKeyPath, ChainName, Config) ->
|
|
case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of
|
|
{ok, #{
|
|
post_config_update := #{emqx_authentication := #{id := ID}},
|
|
raw_config := AuthenticatorsConfig
|
|
}} ->
|
|
{ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig),
|
|
{200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))};
|
|
{error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
|
|
serialize_error(Reason);
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end.
|
|
|
|
list_authenticators(ConfKeyPath) ->
|
|
AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath),
|
|
NAuthenticators = [
|
|
maps:put(
|
|
id,
|
|
emqx_authentication:authenticator_id(AuthenticatorConfig),
|
|
convert_certs(AuthenticatorConfig)
|
|
)
|
|
|| AuthenticatorConfig <- AuthenticatorsConfig
|
|
],
|
|
{200, NAuthenticators}.
|
|
|
|
list_authenticator(_, ConfKeyPath, AuthenticatorID) ->
|
|
with_authenticator(
|
|
AuthenticatorID,
|
|
ConfKeyPath,
|
|
fun(AuthenticatorConfig) ->
|
|
{200, maps:put(id, AuthenticatorID, convert_certs(AuthenticatorConfig))}
|
|
end
|
|
).
|
|
|
|
resource_provider() ->
|
|
[
|
|
emqx_authn_mysql,
|
|
emqx_authn_pgsql,
|
|
emqx_authn_mongodb,
|
|
emqx_authn_redis,
|
|
emqx_authn_http
|
|
].
|
|
|
|
lookup_from_local_node(ChainName, AuthenticatorID) ->
|
|
NodeId = node(self()),
|
|
case emqx_authentication:lookup_authenticator(ChainName, AuthenticatorID) of
|
|
{ok, #{provider := Provider, state := State}} ->
|
|
MetricsId = emqx_authentication:metrics_id(ChainName, AuthenticatorID),
|
|
Metrics = emqx_metrics_worker:get_metrics(authn_metrics, MetricsId),
|
|
case lists:member(Provider, resource_provider()) of
|
|
false ->
|
|
{ok, {NodeId, connected, Metrics, #{}}};
|
|
true ->
|
|
#{resource_id := ResourceId} = State,
|
|
case emqx_resource:get_instance(ResourceId) of
|
|
{error, not_found} ->
|
|
{error, {NodeId, not_found_resource}};
|
|
{ok, _, #{status := Status, metrics := ResourceMetrics}} ->
|
|
{ok, {NodeId, Status, Metrics, ResourceMetrics}}
|
|
end
|
|
end;
|
|
{error, Reason} ->
|
|
{error, {NodeId, list_to_binary(io_lib:format("~p", [Reason]))}}
|
|
end.
|
|
|
|
lookup_from_all_nodes(ChainName, AuthenticatorID) ->
|
|
Nodes = mria_mnesia:running_nodes(),
|
|
LookupResult = emqx_authn_proto_v1:lookup_from_all_nodes(Nodes, ChainName, AuthenticatorID),
|
|
case is_ok(LookupResult) of
|
|
{ok, ResList} ->
|
|
{StatusMap, MetricsMap, ResourceMetricsMap, ErrorMap} = make_result_map(ResList),
|
|
AggregateStatus = aggregate_status(maps:values(StatusMap)),
|
|
AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)),
|
|
AggregateResourceMetrics = aggregate_metrics(maps:values(ResourceMetricsMap)),
|
|
Fun = fun(_, V1) -> restructure_map(V1) end,
|
|
MKMap = fun(Name) -> fun({Key, Val}) -> #{node => Key, Name => Val} end end,
|
|
HelpFun = fun(M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end,
|
|
{200, #{
|
|
node_resource_metrics => HelpFun(maps:map(Fun, ResourceMetricsMap), metrics),
|
|
resource_metrics =>
|
|
case maps:size(AggregateResourceMetrics) of
|
|
0 -> #{};
|
|
_ -> restructure_map(AggregateResourceMetrics)
|
|
end,
|
|
node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics),
|
|
metrics => restructure_map(AggregateMetrics),
|
|
|
|
node_status => HelpFun(StatusMap, status),
|
|
status => AggregateStatus,
|
|
node_error => HelpFun(maps:map(Fun, ErrorMap), reason)
|
|
}};
|
|
{error, ErrL} ->
|
|
{500, #{
|
|
code => <<"INTERNAL_ERROR">>,
|
|
message => list_to_binary(io_lib:format("~p", [ErrL]))
|
|
}}
|
|
end.
|
|
|
|
aggregate_status([]) ->
|
|
empty_metrics_and_status;
|
|
aggregate_status(AllStatus) ->
|
|
Head = fun([A | _]) -> A end,
|
|
HeadVal = Head(AllStatus),
|
|
AllRes = lists:all(fun(Val) -> Val == HeadVal end, AllStatus),
|
|
case AllRes of
|
|
true -> HeadVal;
|
|
false -> inconsistent
|
|
end.
|
|
|
|
aggregate_metrics([]) ->
|
|
#{};
|
|
aggregate_metrics([HeadMetrics | AllMetrics]) ->
|
|
ErrorLogger = fun(Reason) -> ?SLOG(info, #{msg => "bad_metrics_value", error => Reason}) end,
|
|
Fun = fun(ElemMap, AccMap) ->
|
|
emqx_map_lib:best_effort_recursive_sum(AccMap, ElemMap, ErrorLogger)
|
|
end,
|
|
lists:foldl(Fun, HeadMetrics, AllMetrics).
|
|
|
|
make_result_map(ResList) ->
|
|
Fun =
|
|
fun(Elem, {StatusMap, MetricsMap, ResourceMetricsMap, ErrorMap}) ->
|
|
case Elem of
|
|
{ok, {NodeId, Status, Metrics, ResourceMetrics}} ->
|
|
{
|
|
maps:put(NodeId, Status, StatusMap),
|
|
maps:put(NodeId, Metrics, MetricsMap),
|
|
maps:put(NodeId, ResourceMetrics, ResourceMetricsMap),
|
|
ErrorMap
|
|
};
|
|
{error, {NodeId, Reason}} ->
|
|
{StatusMap, MetricsMap, ResourceMetricsMap, maps:put(NodeId, Reason, ErrorMap)}
|
|
end
|
|
end,
|
|
lists:foldl(Fun, {maps:new(), maps:new(), maps:new(), maps:new()}, ResList).
|
|
|
|
restructure_map(#{
|
|
counters := #{failed := Failed, total := Total, success := Succ, nomatch := Nomatch},
|
|
rate := #{total := #{current := Rate, last5m := Rate5m, max := RateMax}}
|
|
}) ->
|
|
#{
|
|
total => Total,
|
|
success => Succ,
|
|
failed => Failed,
|
|
nomatch => Nomatch,
|
|
rate => Rate,
|
|
rate_last5m => Rate5m,
|
|
rate_max => RateMax
|
|
};
|
|
restructure_map(#{
|
|
counters := #{failed := Failed, matched := Match, success := Succ},
|
|
rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax}}
|
|
}) ->
|
|
#{
|
|
matched => Match,
|
|
success => Succ,
|
|
failed => Failed,
|
|
rate => Rate,
|
|
rate_last5m => Rate5m,
|
|
rate_max => RateMax
|
|
};
|
|
restructure_map(Error) ->
|
|
Error.
|
|
|
|
is_ok(ResL) ->
|
|
case
|
|
lists:filter(
|
|
fun
|
|
({ok, _}) -> false;
|
|
(_) -> true
|
|
end,
|
|
ResL
|
|
)
|
|
of
|
|
[] -> {ok, [Res || {ok, Res} <- ResL]};
|
|
ErrL -> {error, ErrL}
|
|
end.
|
|
|
|
update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) ->
|
|
case
|
|
update_config(
|
|
ConfKeyPath,
|
|
{update_authenticator, ChainName, AuthenticatorID, Config}
|
|
)
|
|
of
|
|
{ok, _} ->
|
|
{204};
|
|
{error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
|
|
serialize_error(Reason);
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end.
|
|
|
|
delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) ->
|
|
case update_config(ConfKeyPath, {delete_authenticator, ChainName, AuthenticatorID}) of
|
|
{ok, _} ->
|
|
{204};
|
|
{error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
|
|
serialize_error(Reason);
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end.
|
|
|
|
move_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Position) ->
|
|
case parse_position(Position) of
|
|
{ok, NPosition} ->
|
|
case
|
|
update_config(
|
|
ConfKeyPath,
|
|
{move_authenticator, ChainName, AuthenticatorID, NPosition}
|
|
)
|
|
of
|
|
{ok, _} ->
|
|
{204};
|
|
{error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
|
|
serialize_error(Reason);
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end;
|
|
{error, Reason} ->
|
|
serialize_error(Reason)
|
|
end.
|
|
|
|
add_user(
|
|
ChainName,
|
|
AuthenticatorID,
|
|
#{<<"user_id">> := UserID, <<"password">> := Password} = UserInfo
|
|
) ->
|
|
IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false),
|
|
case
|
|
emqx_authentication:add_user(
|
|
ChainName,
|
|
AuthenticatorID,
|
|
#{
|
|
user_id => UserID,
|
|
password => Password,
|
|
is_superuser => IsSuperuser
|
|
}
|
|
)
|
|
of
|
|
{ok, User} ->
|
|
{201, User};
|
|
{error, Reason} ->
|
|
serialize_error({user_error, Reason})
|
|
end;
|
|
add_user(_, _, #{<<"user_id">> := _}) ->
|
|
serialize_error({missing_parameter, password});
|
|
add_user(_, _, _) ->
|
|
serialize_error({missing_parameter, user_id}).
|
|
|
|
update_user(ChainName, AuthenticatorID, UserID, UserInfo0) ->
|
|
case maps:with([<<"password">>, <<"is_superuser">>], UserInfo0) =:= #{} of
|
|
true ->
|
|
serialize_error({missing_parameter, password});
|
|
false ->
|
|
UserInfo = emqx_map_lib:safe_atom_key_map(UserInfo0),
|
|
case emqx_authentication:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of
|
|
{ok, User} ->
|
|
{200, User};
|
|
{error, Reason} ->
|
|
serialize_error({user_error, Reason})
|
|
end
|
|
end.
|
|
|
|
find_user(ChainName, AuthenticatorID, UserID) ->
|
|
case emqx_authentication:lookup_user(ChainName, AuthenticatorID, UserID) of
|
|
{ok, User} ->
|
|
{200, User};
|
|
{error, Reason} ->
|
|
serialize_error({user_error, Reason})
|
|
end.
|
|
|
|
delete_user(ChainName, AuthenticatorID, UserID) ->
|
|
case emqx_authentication:delete_user(ChainName, AuthenticatorID, UserID) of
|
|
ok ->
|
|
{204};
|
|
{error, Reason} ->
|
|
serialize_error({user_error, Reason})
|
|
end.
|
|
|
|
list_users(ChainName, AuthenticatorID, QueryString) ->
|
|
case emqx_authentication:list_users(ChainName, AuthenticatorID, QueryString) of
|
|
{error, page_limit_invalid} ->
|
|
{400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}};
|
|
{error, Reason} ->
|
|
{400, #{
|
|
code => <<"INVALID_PARAMETER">>,
|
|
message => list_to_binary(io_lib:format("Reason ~p", [Reason]))
|
|
}};
|
|
Result ->
|
|
{200, Result}
|
|
end.
|
|
|
|
update_config(Path, ConfigRequest) ->
|
|
emqx_conf:update(Path, ConfigRequest, #{
|
|
rawconf_with_defaults => true,
|
|
override_to => cluster
|
|
}).
|
|
|
|
get_raw_config_with_defaults(ConfKeyPath) ->
|
|
NConfKeyPath = [atom_to_binary(Key, utf8) || Key <- ConfKeyPath],
|
|
RawConfig = emqx:get_raw_config(NConfKeyPath, []),
|
|
ensure_list(fill_defaults(RawConfig)).
|
|
|
|
find_config(AuthenticatorID, AuthenticatorsConfig) ->
|
|
MatchingACs =
|
|
[
|
|
AC
|
|
|| AC <- ensure_list(AuthenticatorsConfig),
|
|
AuthenticatorID =:= emqx_authentication:authenticator_id(AC)
|
|
],
|
|
case MatchingACs of
|
|
[] -> {error, {not_found, {authenticator, AuthenticatorID}}};
|
|
[AuthenticatorConfig] -> {ok, AuthenticatorConfig}
|
|
end.
|
|
|
|
fill_defaults(Configs) when is_list(Configs) ->
|
|
lists:map(fun fill_defaults/1, Configs);
|
|
fill_defaults(Config) ->
|
|
emqx_authn:check_config(merge_default_headers(Config), #{make_serializable => true}).
|
|
|
|
merge_default_headers(Config) ->
|
|
case maps:find(<<"headers">>, Config) of
|
|
{ok, Headers} ->
|
|
NewHeaders =
|
|
case Config of
|
|
#{<<"method">> := <<"get">>} ->
|
|
(emqx_authn_http:headers_no_content_type(converter))(Headers);
|
|
#{<<"method">> := <<"post">>} ->
|
|
(emqx_authn_http:headers(converter))(Headers);
|
|
_ ->
|
|
Headers
|
|
end,
|
|
Config#{<<"headers">> => NewHeaders};
|
|
error ->
|
|
Config
|
|
end.
|
|
|
|
convert_certs(#{ssl := SSL} = Config) when SSL =/= undefined ->
|
|
Config#{ssl := emqx_tls_lib:drop_invalid_certs(SSL)};
|
|
convert_certs(Config) ->
|
|
Config.
|
|
|
|
serialize_error({user_error, not_found}) ->
|
|
{404, #{
|
|
code => <<"NOT_FOUND">>,
|
|
message => binfmt("User not found", [])
|
|
}};
|
|
serialize_error({user_error, already_exist}) ->
|
|
{409, #{
|
|
code => <<"ALREADY_EXISTS">>,
|
|
message => binfmt("User already exists", [])
|
|
}};
|
|
serialize_error({user_error, Reason}) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("User error: ~p", [Reason])
|
|
}};
|
|
serialize_error({not_found, {authenticator, ID}}) ->
|
|
{404, #{
|
|
code => <<"NOT_FOUND">>,
|
|
message => binfmt("Authenticator '~ts' does not exist", [ID])
|
|
}};
|
|
serialize_error({not_found, {listener, ID}}) ->
|
|
{404, #{
|
|
code => <<"NOT_FOUND">>,
|
|
message => binfmt("Listener '~ts' does not exist", [ID])
|
|
}};
|
|
serialize_error({not_found, {chain, ?GLOBAL}}) ->
|
|
{404, #{
|
|
code => <<"NOT_FOUND">>,
|
|
message => <<"Authenticator not found in the 'global' scope">>
|
|
}};
|
|
serialize_error({not_found, {chain, Name}}) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("No authentication has been created for listener ~p", [Name])
|
|
}};
|
|
serialize_error({already_exists, {authenticator, ID}}) ->
|
|
{409, #{
|
|
code => <<"ALREADY_EXISTS">>,
|
|
message => binfmt("Authenticator '~ts' already exist", [ID])
|
|
}};
|
|
serialize_error(no_available_provider) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => <<"Unsupported authentication type">>
|
|
}};
|
|
serialize_error(change_of_authentication_type_is_not_allowed) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => <<"Change of authentication type is not allowed">>
|
|
}};
|
|
serialize_error(unsupported_operation) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => <<"Operation not supported in this authentication type">>
|
|
}};
|
|
serialize_error({bad_ssl_config, Details}) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("bad_ssl_config ~p", [Details])
|
|
}};
|
|
serialize_error({missing_parameter, Detail}) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("Missing required parameter: ~p", [Detail])
|
|
}};
|
|
serialize_error({invalid_parameter, Name}) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("Invalid value for '~p'", [Name])
|
|
}};
|
|
serialize_error({unknown_authn_type, Type}) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("Unknown type '~p'", [Type])
|
|
}};
|
|
serialize_error(Reason) ->
|
|
{400, #{
|
|
code => <<"BAD_REQUEST">>,
|
|
message => binfmt("~0p", [Reason])
|
|
}}.
|
|
|
|
parse_position(<<"front">>) ->
|
|
{ok, ?CMD_MOVE_FRONT};
|
|
parse_position(<<"rear">>) ->
|
|
{ok, ?CMD_MOVE_REAR};
|
|
parse_position(<<"before:">>) ->
|
|
{error, {invalid_parameter, position}};
|
|
parse_position(<<"after:">>) ->
|
|
{error, {invalid_parameter, position}};
|
|
parse_position(<<"before:", Before/binary>>) ->
|
|
{ok, ?CMD_MOVE_BEFORE(Before)};
|
|
parse_position(<<"after:", After/binary>>) ->
|
|
{ok, ?CMD_MOVE_AFTER(After)};
|
|
parse_position(_) ->
|
|
{error, {invalid_parameter, position}}.
|
|
|
|
ensure_list(M) when is_map(M) -> [M];
|
|
ensure_list(L) when is_list(L) -> L.
|
|
|
|
binfmt(Fmt, Args) -> iolist_to_binary(io_lib:format(Fmt, Args)).
|
|
|
|
paginated_list_type(Type) ->
|
|
[
|
|
{data, hoconsc:array(Type)},
|
|
{meta, ref(emqx_dashboard_swagger, meta)}
|
|
].
|
|
|
|
authenticator_array_example() ->
|
|
[Config || #{value := Config} <- maps:values(authenticator_examples())].
|
|
|
|
authenticator_examples() ->
|
|
#{
|
|
'password_based:built_in_database' => #{
|
|
summary => <<"Built-in password_based authentication">>,
|
|
value => #{
|
|
mechanism => <<"password_based">>,
|
|
backend => <<"built_in_database">>,
|
|
user_id_type => <<"username">>,
|
|
password_hash_algorithm => #{
|
|
name => <<"sha256">>,
|
|
salt_position => <<"suffix">>
|
|
}
|
|
}
|
|
},
|
|
'password_based:http' => #{
|
|
summary => <<"password_based authentication through external HTTP API">>,
|
|
value => #{
|
|
mechanism => <<"password_based">>,
|
|
backend => <<"http">>,
|
|
method => <<"post">>,
|
|
url => <<"http://127.0.0.1:18083">>,
|
|
headers => #{
|
|
<<"content-type">> => <<"application/json">>
|
|
},
|
|
body => #{
|
|
<<"username">> => ?PH_USERNAME,
|
|
<<"password">> => ?PH_PASSWORD
|
|
},
|
|
pool_size => 8,
|
|
connect_timeout => 5000,
|
|
request_timeout => 5000,
|
|
enable_pipelining => 100,
|
|
ssl => #{enable => false}
|
|
}
|
|
},
|
|
'jwt' => #{
|
|
summary => <<"JWT authentication">>,
|
|
value => #{
|
|
mechanism => <<"jwt">>,
|
|
use_jwks => false,
|
|
algorithm => <<"hmac-based">>,
|
|
secret => <<"mysecret">>,
|
|
secret_base64_encoded => false,
|
|
verify_claims => #{
|
|
<<"username">> => ?PH_USERNAME
|
|
}
|
|
}
|
|
},
|
|
'password_based:mongodb' => #{
|
|
summary => <<"password_based authentication with MongoDB backend">>,
|
|
value => #{
|
|
mechanism => <<"password_based">>,
|
|
backend => <<"mongodb">>,
|
|
server => <<"127.0.0.1:27017">>,
|
|
database => example,
|
|
collection => users,
|
|
filter => #{
|
|
username => ?PH_USERNAME
|
|
},
|
|
password_hash_field => <<"password_hash">>,
|
|
salt_field => <<"salt">>,
|
|
is_superuser_field => <<"is_superuser">>,
|
|
password_hash_algorithm => #{
|
|
name => <<"sha256">>,
|
|
salt_position => <<"suffix">>
|
|
}
|
|
}
|
|
},
|
|
'password_based:redis' => #{
|
|
summary => <<"password_based authentication with Redis backend">>,
|
|
value => #{
|
|
mechanism => <<"password_based">>,
|
|
backend => <<"redis">>,
|
|
server => <<"127.0.0.1:6379">>,
|
|
redis_type => <<"single">>,
|
|
database => 0,
|
|
cmd => <<"HMGET ${username} password_hash salt">>,
|
|
password_hash_algorithm => #{
|
|
name => <<"sha256">>,
|
|
salt_position => <<"suffix">>
|
|
}
|
|
}
|
|
}
|
|
}.
|
|
|
|
status_metrics_example() ->
|
|
#{
|
|
status_metrics => #{
|
|
summary => <<"Authn status metrics">>,
|
|
value => #{
|
|
resource_metrics => #{
|
|
matched => 0,
|
|
success => 0,
|
|
failed => 0,
|
|
rate => 0.0,
|
|
rate_last5m => 0.0,
|
|
rate_max => 0.0
|
|
},
|
|
node_resource_metrics => [
|
|
#{
|
|
node => node(),
|
|
metrics => #{
|
|
matched => 0,
|
|
success => 0,
|
|
failed => 0,
|
|
rate => 0.0,
|
|
rate_last5m => 0.0,
|
|
rate_max => 0.0
|
|
}
|
|
}
|
|
],
|
|
metrics => #{
|
|
total => 0,
|
|
success => 0,
|
|
failed => 0,
|
|
nomatch => 0,
|
|
rate => 0.0,
|
|
rate_last5m => 0.0,
|
|
rate_max => 0.0
|
|
},
|
|
node_error => [],
|
|
node_metrics => [
|
|
#{
|
|
node => node(),
|
|
metrics => #{
|
|
matched => 0,
|
|
total => 0,
|
|
failed => 0,
|
|
nomatch => 0,
|
|
rate => 0.0,
|
|
rate_last5m => 0.0,
|
|
rate_max => 0.0
|
|
}
|
|
}
|
|
],
|
|
status => connected,
|
|
node_status => [
|
|
#{
|
|
node => node(),
|
|
status => connected
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}.
|
|
|
|
request_user_create_examples() ->
|
|
#{
|
|
regular_user => #{
|
|
summary => <<"Regular user">>,
|
|
value => #{
|
|
user_id => <<"user1">>,
|
|
password => <<"secret">>
|
|
}
|
|
},
|
|
super_user => #{
|
|
summary => <<"Superuser">>,
|
|
value => #{
|
|
user_id => <<"user2">>,
|
|
password => <<"secret">>,
|
|
is_superuser => true
|
|
}
|
|
}
|
|
}.
|
|
|
|
request_user_update_examples() ->
|
|
#{
|
|
regular_user => #{
|
|
summary => <<"Update regular user">>,
|
|
value => #{
|
|
password => <<"newsecret">>
|
|
}
|
|
},
|
|
super_user => #{
|
|
summary => <<"Update user and promote to superuser">>,
|
|
value => #{
|
|
password => <<"newsecret">>,
|
|
is_superuser => true
|
|
}
|
|
}
|
|
}.
|
|
|
|
response_user_examples() ->
|
|
#{
|
|
regular_user => #{
|
|
summary => <<"Regular user">>,
|
|
value => #{
|
|
user_id => <<"user1">>
|
|
}
|
|
},
|
|
super_user => #{
|
|
summary => <<"Superuser">>,
|
|
value => #{
|
|
user_id => <<"user2">>,
|
|
is_superuser => true
|
|
}
|
|
}
|
|
}.
|
|
|
|
response_users_example() ->
|
|
#{
|
|
data => [
|
|
#{
|
|
user_id => <<"user1">>
|
|
},
|
|
#{
|
|
user_id => <<"user2">>,
|
|
is_superuser => true
|
|
}
|
|
],
|
|
meta => #{
|
|
page => 0,
|
|
limit => 20,
|
|
count => 300
|
|
}
|
|
}.
|