feat(dashboard): add SSO feature and integrate with LDAP
This commit is contained in:
parent
6fe846bf0e
commit
2cddce5479
|
@ -22,18 +22,23 @@
|
||||||
%% a predefined configuration would replace these macros.
|
%% a predefined configuration would replace these macros.
|
||||||
-define(ROLE_VIEWER, <<"viewer">>).
|
-define(ROLE_VIEWER, <<"viewer">>).
|
||||||
-define(ROLE_SUPERUSER, <<"superuser">>).
|
-define(ROLE_SUPERUSER, <<"superuser">>).
|
||||||
|
|
||||||
-define(ROLE_DEFAULT, ?ROLE_SUPERUSER).
|
-define(ROLE_DEFAULT, ?ROLE_SUPERUSER).
|
||||||
|
|
||||||
|
-define(SSO_USERNAME(Backend, Name), {Backend, Name}).
|
||||||
|
|
||||||
|
-type dashboard_sso_backend() :: atom().
|
||||||
|
-type dashboard_sso_username() :: {dashboard_sso_backend(), binary()}.
|
||||||
|
-type dashboard_username() :: binary() | dashboard_sso_username().
|
||||||
|
-type dashboard_user_role() :: binary().
|
||||||
|
|
||||||
-record(?ADMIN, {
|
-record(?ADMIN, {
|
||||||
username :: binary(),
|
username :: dashboard_username(),
|
||||||
pwdhash :: binary(),
|
pwdhash :: binary(),
|
||||||
description :: binary(),
|
description :: binary(),
|
||||||
role = ?ROLE_DEFAULT :: binary(),
|
role = ?ROLE_DEFAULT :: dashboard_user_role(),
|
||||||
extra = #{} :: map()
|
extra = #{} :: map()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type dashboard_user_role() :: binary().
|
|
||||||
-type dashboard_user() :: #?ADMIN{}.
|
-type dashboard_user() :: #?ADMIN{}.
|
||||||
|
|
||||||
-define(ADMIN_JWT, emqx_admin_jwt).
|
-define(ADMIN_JWT, emqx_admin_jwt).
|
||||||
|
|
|
@ -60,6 +60,10 @@
|
||||||
|
|
||||||
-export([backup_tables/0]).
|
-export([backup_tables/0]).
|
||||||
|
|
||||||
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
-export([add_sso_user/4, lookup_user/2]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-type emqx_admin() :: #?ADMIN{}.
|
-type emqx_admin() :: #?ADMIN{}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -99,10 +103,11 @@ add_default_user() ->
|
||||||
%% API
|
%% API
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec add_user(binary(), binary(), dashboard_user_role(), binary()) -> {ok, map()} | {error, any()}.
|
|
||||||
add_user(Username, Password, Role, Desc) when
|
|
||||||
is_binary(Username), is_binary(Password)
|
-spec add_user(dashboard_username(), binary(), dashboard_user_role(), binary()) ->
|
||||||
->
|
{ok, map()} | {error, any()}.
|
||||||
|
add_user(Username, Password, Role, Desc) when is_binary(Password) ->
|
||||||
case {legal_username(Username), legal_password(Password), legal_role(Role)} of
|
case {legal_username(Username), legal_password(Password), legal_role(Role)} of
|
||||||
{ok, ok, ok} -> do_add_user(Username, Password, Role, Desc);
|
{ok, ok, ok} -> do_add_user(Username, Password, Role, Desc);
|
||||||
{{error, Reason}, _, _} -> {error, Reason};
|
{{error, Reason}, _, _} -> {error, Reason};
|
||||||
|
@ -115,6 +120,8 @@ do_add_user(Username, Password, Role, Desc) ->
|
||||||
return(Res).
|
return(Res).
|
||||||
|
|
||||||
%% 0-9 or A-Z or a-z or $_
|
%% 0-9 or A-Z or a-z or $_
|
||||||
|
legal_username(?SSO_USERNAME(_, _)) ->
|
||||||
|
ok;
|
||||||
legal_username(<<>>) ->
|
legal_username(<<>>) ->
|
||||||
{error, <<"Username cannot be empty">>};
|
{error, <<"Username cannot be empty">>};
|
||||||
legal_username(UserName) ->
|
legal_username(UserName) ->
|
||||||
|
@ -312,8 +319,8 @@ update_pwd(Username, Fun) ->
|
||||||
end,
|
end,
|
||||||
return(mria:transaction(?DASHBOARD_SHARD, Trans)).
|
return(mria:transaction(?DASHBOARD_SHARD, Trans)).
|
||||||
|
|
||||||
-spec lookup_user(binary()) -> [emqx_admin()].
|
-spec lookup_user(dashboard_username()) -> [emqx_admin()].
|
||||||
lookup_user(Username) when is_binary(Username) ->
|
lookup_user(Username) ->
|
||||||
Fun = fun() -> mnesia:read(?ADMIN, Username) end,
|
Fun = fun() -> mnesia:read(?ADMIN, Username) end,
|
||||||
{atomic, User} = mria:ro_transaction(?DASHBOARD_SHARD, Fun),
|
{atomic, User} = mria:ro_transaction(?DASHBOARD_SHARD, Fun),
|
||||||
User.
|
User.
|
||||||
|
@ -410,6 +417,14 @@ legal_role(Role) ->
|
||||||
role(Data) ->
|
role(Data) ->
|
||||||
emqx_dashboard_rbac:role(Data).
|
emqx_dashboard_rbac:role(Data).
|
||||||
|
|
||||||
|
-spec add_sso_user(atom(), binary(), dashboard_user_role(), binary()) ->
|
||||||
|
{ok, map()} | {error, any()}.
|
||||||
|
add_sso_user(Backend, Username, Role, Desc) ->
|
||||||
|
add_user(?SSO_USERNAME(Backend, Username), <<>>, Role, Desc).
|
||||||
|
|
||||||
|
-spec lookup_user(dashboard_sso_backend(), binary()) -> [emqx_admin()].
|
||||||
|
lookup_user(Backend, Username) when is_atom(Backend) ->
|
||||||
|
lookup_user(?SSO_USERNAME(Backend, Username)).
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
-dialyzer({no_match, [add_user/4, update_user/3]}).
|
-dialyzer({no_match, [add_user/4, update_user/3]}).
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
Business Source License 1.1
|
||||||
|
|
||||||
|
Licensor: Hangzhou EMQ Technologies Co., Ltd.
|
||||||
|
Licensed Work: EMQX Enterprise Edition
|
||||||
|
The Licensed Work is (c) 2023
|
||||||
|
Hangzhou EMQ Technologies Co., Ltd.
|
||||||
|
Additional Use Grant: Students and educators are granted right to copy,
|
||||||
|
modify, and create derivative work for research
|
||||||
|
or education.
|
||||||
|
Change Date: 2027-02-01
|
||||||
|
Change License: Apache License, Version 2.0
|
||||||
|
|
||||||
|
For information about alternative licensing arrangements for the Software,
|
||||||
|
please contact Licensor: https://www.emqx.com/en/contact
|
||||||
|
|
||||||
|
Notice
|
||||||
|
|
||||||
|
The Business Source License (this document, or the “License”) is not an Open
|
||||||
|
Source license. However, the Licensed Work will eventually be made available
|
||||||
|
under an Open Source License, as stated in this License.
|
||||||
|
|
||||||
|
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
||||||
|
“Business Source License” is a trademark of MariaDB Corporation Ab.
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Business Source License 1.1
|
||||||
|
|
||||||
|
Terms
|
||||||
|
|
||||||
|
The Licensor hereby grants you the right to copy, modify, create derivative
|
||||||
|
works, redistribute, and make non-production use of the Licensed Work. The
|
||||||
|
Licensor may make an Additional Use Grant, above, permitting limited
|
||||||
|
production use.
|
||||||
|
|
||||||
|
Effective on the Change Date, or the fourth anniversary of the first publicly
|
||||||
|
available distribution of a specific version of the Licensed Work under this
|
||||||
|
License, whichever comes first, the Licensor hereby grants you rights under
|
||||||
|
the terms of the Change License, and the rights granted in the paragraph
|
||||||
|
above terminate.
|
||||||
|
|
||||||
|
If your use of the Licensed Work does not comply with the requirements
|
||||||
|
currently in effect as described in this License, you must purchase a
|
||||||
|
commercial license from the Licensor, its affiliated entities, or authorized
|
||||||
|
resellers, or you must refrain from using the Licensed Work.
|
||||||
|
|
||||||
|
All copies of the original and modified Licensed Work, and derivative works
|
||||||
|
of the Licensed Work, are subject to this License. This License applies
|
||||||
|
separately for each version of the Licensed Work and the Change Date may vary
|
||||||
|
for each version of the Licensed Work released by Licensor.
|
||||||
|
|
||||||
|
You must conspicuously display this License on each original or modified copy
|
||||||
|
of the Licensed Work. If you receive the Licensed Work in original or
|
||||||
|
modified form from a third party, the terms and conditions set forth in this
|
||||||
|
License apply to your use of that work.
|
||||||
|
|
||||||
|
Any use of the Licensed Work in violation of this License will automatically
|
||||||
|
terminate your rights under this License for the current and all other
|
||||||
|
versions of the Licensed Work.
|
||||||
|
|
||||||
|
This License does not grant you any right in any trademark or logo of
|
||||||
|
Licensor or its affiliates (provided that you may use a trademark or logo of
|
||||||
|
Licensor as expressly required by this License).
|
||||||
|
|
||||||
|
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
||||||
|
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
||||||
|
TITLE.
|
||||||
|
|
||||||
|
MariaDB hereby grants you permission to use this License’s text to license
|
||||||
|
your works, and to refer to it using the trademark “Business Source License”,
|
||||||
|
as long as you comply with the Covenants of Licensor below.
|
||||||
|
|
||||||
|
Covenants of Licensor
|
||||||
|
|
||||||
|
In consideration of the right to use this License’s text and the “Business
|
||||||
|
Source License” name and trademark, Licensor covenants to MariaDB, and to all
|
||||||
|
other recipients of the licensed work to be provided by Licensor:
|
||||||
|
|
||||||
|
1. To specify as the Change License the GPL Version 2.0 or any later version,
|
||||||
|
or a license that is compatible with GPL Version 2.0 or a later version,
|
||||||
|
where “compatible” means that software provided under the Change License can
|
||||||
|
be included in a program with software provided under GPL Version 2.0 or a
|
||||||
|
later version. Licensor may specify additional Change Licenses without
|
||||||
|
limitation.
|
||||||
|
|
||||||
|
2. To either: (a) specify an additional grant of rights to use that does not
|
||||||
|
impose any additional restriction on the right granted in this License, as
|
||||||
|
the Additional Use Grant; or (b) insert the text “None”.
|
||||||
|
|
||||||
|
3. To specify a Change Date.
|
||||||
|
|
||||||
|
4. Not to modify this License in any other way.
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Dashboard Single sign-on
|
||||||
|
|
||||||
|
Single Sign-On is a mechanism that allows a user to automatically sign in to multiple applications after signing in to one. This improves convenience and security.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please see our [contributing.md](../../CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [APL](../../APL.txt).
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
%% -*- mode: erlang; -*-
|
||||||
|
|
||||||
|
{erl_opts, [debug_info]}.
|
||||||
|
{deps, [
|
||||||
|
{emqx_ldap, {path, "../../apps/emqx_ldap"}},
|
||||||
|
{emqx_dashboard, {path, "../../apps/emqx_dashboard"}}
|
||||||
|
]}.
|
|
@ -0,0 +1,19 @@
|
||||||
|
{application, emqx_dashboard_sso, [
|
||||||
|
{description, "EMQX Dashboard Single Sign-On"},
|
||||||
|
{vsn, "0.1.0"},
|
||||||
|
{registered, [emqx_dashboard_sso_sup]},
|
||||||
|
{applications, [
|
||||||
|
kernel,
|
||||||
|
stdlib,
|
||||||
|
emqx_dashboard,
|
||||||
|
emqx_ldap
|
||||||
|
]},
|
||||||
|
{mod, {emqx_dashboard_sso_app, []}},
|
||||||
|
{env, []},
|
||||||
|
{modules, []},
|
||||||
|
{maintainers, ["EMQX Team <contact@emqx.io>"]},
|
||||||
|
{links, [
|
||||||
|
{"Homepage", "https://emqx.io/"},
|
||||||
|
{"Github", "https://github.com/emqx/emqx-dashboard5"}
|
||||||
|
]}
|
||||||
|
]}.
|
|
@ -0,0 +1,45 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso).
|
||||||
|
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
|
||||||
|
-export([types/0, modules/0, provider/1, backends/0]).
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Callbacks
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-type request() :: map().
|
||||||
|
-type parsed_config() :: #{
|
||||||
|
backend => atom(),
|
||||||
|
atom() => term()
|
||||||
|
}.
|
||||||
|
-type state() :: #{atom() => term()}.
|
||||||
|
-type raw_config() :: #{binary() => term()}.
|
||||||
|
-type config() :: parsed_config() | raw_config().
|
||||||
|
|
||||||
|
-callback hocon_ref() -> ?R_REF(Module :: atom(), Name :: atom() | binary()).
|
||||||
|
-callback login_ref() -> ?R_REF(Module :: atom(), Name :: atom() | binary()).
|
||||||
|
-callback create(Config :: config()) ->
|
||||||
|
{ok, State :: state()} | {error, Reason :: term()}.
|
||||||
|
-callback update(Config :: config(), State :: state()) ->
|
||||||
|
{ok, NewState :: state()} | {error, Reason :: term()}.
|
||||||
|
-callback destroy(State :: state()) -> ok.
|
||||||
|
-callback sign(request(), State :: state()) ->
|
||||||
|
{ok, Token :: binary()} | {error, Reason :: term()}.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
types() ->
|
||||||
|
maps:keys(backends()).
|
||||||
|
|
||||||
|
modules() ->
|
||||||
|
maps:values(backends()).
|
||||||
|
|
||||||
|
provider(Backend) ->
|
||||||
|
maps:get(Backend, backends()).
|
||||||
|
|
||||||
|
backends() ->
|
||||||
|
#{ldap => emqx_dashboard_sso_ldap}.
|
|
@ -0,0 +1,235 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_api).
|
||||||
|
|
||||||
|
-behaviour(minirest_api).
|
||||||
|
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
|
||||||
|
-import(hoconsc, [
|
||||||
|
mk/2,
|
||||||
|
array/1,
|
||||||
|
enum/1,
|
||||||
|
ref/1,
|
||||||
|
union/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
api_spec/0,
|
||||||
|
fields/1,
|
||||||
|
paths/0,
|
||||||
|
schema/1,
|
||||||
|
namespace/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
running/2,
|
||||||
|
login/2,
|
||||||
|
sso/2,
|
||||||
|
backend/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD').
|
||||||
|
-define(BAD_REQUEST, 'BAD_REQUEST').
|
||||||
|
|
||||||
|
-define(BACKEND_NOT_FOUND, 'BACKEND_NOT_FOUND').
|
||||||
|
-define(TAGS, <<"Dashboard Single Sign-on">>).
|
||||||
|
|
||||||
|
namespace() -> "dashboard_sso".
|
||||||
|
|
||||||
|
api_spec() ->
|
||||||
|
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
|
||||||
|
|
||||||
|
paths() ->
|
||||||
|
[
|
||||||
|
"/sso",
|
||||||
|
"/sso/login",
|
||||||
|
"/sso/running",
|
||||||
|
"/sso/:backend"
|
||||||
|
].
|
||||||
|
|
||||||
|
schema("/sso") ->
|
||||||
|
#{
|
||||||
|
'operationId' => sso,
|
||||||
|
get => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(get_sso),
|
||||||
|
responses => #{
|
||||||
|
200 => array(ref(backend_status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sso/login") ->
|
||||||
|
#{
|
||||||
|
'operationId' => login,
|
||||||
|
post => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(login),
|
||||||
|
'requestBody' => login_union(),
|
||||||
|
responses => #{
|
||||||
|
200 => emqx_dashboard_api:fields([token, version, license]),
|
||||||
|
401 => response_schema(401),
|
||||||
|
404 => response_schema(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sso/running") ->
|
||||||
|
#{
|
||||||
|
'operationId' => running,
|
||||||
|
get => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(get_running),
|
||||||
|
responses => #{
|
||||||
|
200 => array(enum(emqx_dashboard_sso:types()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sso/:backend") ->
|
||||||
|
#{
|
||||||
|
'operationId' => backend,
|
||||||
|
get => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(get_backend),
|
||||||
|
parameters => backend_name_in_path(),
|
||||||
|
responses => #{
|
||||||
|
200 => backend_union(),
|
||||||
|
404 => response_schema(404)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
post => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(create_backend),
|
||||||
|
parameters => backend_name_in_path(),
|
||||||
|
'requestBody' => backend_union(),
|
||||||
|
responses => #{
|
||||||
|
200 => backend_union()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
put => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(update_backend),
|
||||||
|
parameters => backend_name_in_path(),
|
||||||
|
'requestBody' => backend_union(),
|
||||||
|
responses => #{
|
||||||
|
200 => backend_union(),
|
||||||
|
404 => response_schema(404)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(delete_backend),
|
||||||
|
parameters => backend_name_in_path(),
|
||||||
|
responses => #{
|
||||||
|
204 => <<"Delete successfully">>,
|
||||||
|
404 => response_schema(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
|
fields(backend_status) ->
|
||||||
|
emqx_dashboard_sso_schema:common_backend_schema(enum(emqx_dashboard_sso:types())).
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
running(get, _Request) ->
|
||||||
|
{200, emqx_dashboard_sso_manager:running()}.
|
||||||
|
|
||||||
|
login(post, #{backend := Backend} = Request) ->
|
||||||
|
case emqx_dashboard_sso_manager:lookup_state(Backend) of
|
||||||
|
undefined ->
|
||||||
|
{404, ?BACKEND_NOT_FOUND};
|
||||||
|
State ->
|
||||||
|
Provider = emqx_dashboard_sso:provider(Backend),
|
||||||
|
case Provider:login(Request, State) of
|
||||||
|
{ok, Token} ->
|
||||||
|
?SLOG(info, #{msg => "Dashboard SSO login successfully", request => Request}),
|
||||||
|
Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
|
||||||
|
{200, #{
|
||||||
|
token => Token,
|
||||||
|
version => Version,
|
||||||
|
license => #{edition => emqx_release:edition()}
|
||||||
|
}};
|
||||||
|
{error, Reason} ->
|
||||||
|
?SLOG(info, #{
|
||||||
|
msg => "Dashboard SSO login failed", request => Request, reason => Reason
|
||||||
|
}),
|
||||||
|
{401, ?BAD_USERNAME_OR_PWD, <<"Auth failed">>}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
sso(get, _Request) ->
|
||||||
|
SSO = emqx:get_config([dashboard_sso], #{}),
|
||||||
|
{200,
|
||||||
|
lists:map(
|
||||||
|
fun(Backend) ->
|
||||||
|
maps:with([backend, enabled], Backend)
|
||||||
|
end,
|
||||||
|
maps:values(SSO)
|
||||||
|
)}.
|
||||||
|
|
||||||
|
backend(get, #{bindings := #{backend := Type}}) ->
|
||||||
|
case emqx:get_config([dashboard_sso, Type], undefined) of
|
||||||
|
undefined ->
|
||||||
|
{404, ?BACKEND_NOT_FOUND};
|
||||||
|
Backend ->
|
||||||
|
{200, Backend}
|
||||||
|
end;
|
||||||
|
backend(create, #{bindings := #{backend := Backend}, body := Config}) ->
|
||||||
|
on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:create/2);
|
||||||
|
backend(put, #{bindings := #{backend := Backend}, body := Config}) ->
|
||||||
|
on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:update/2);
|
||||||
|
backend(delete, #{bindings := #{backend := Backend}, body := Config}) ->
|
||||||
|
on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:delete/2).
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------------------------------------
|
||||||
|
%% internal
|
||||||
|
response_schema(401) ->
|
||||||
|
emqx_dashboard_swagger:error_codes([?BAD_USERNAME_OR_PWD], ?DESC(login_failed401));
|
||||||
|
response_schema(404) ->
|
||||||
|
emqx_dashboard_swagger:error_codes([?BACKEND_NOT_FOUND], ?DESC(backend_not_found)).
|
||||||
|
|
||||||
|
backend_union() ->
|
||||||
|
hoconsc:union([Mod:hocon_ref() || Mod <- emqx_dashboard_sso:modules()]).
|
||||||
|
|
||||||
|
login_union() ->
|
||||||
|
hoconsc:union([Mod:login_ref() || Mod <- emqx_dashboard_sso:modules()]).
|
||||||
|
|
||||||
|
backend_name_in_path() ->
|
||||||
|
[
|
||||||
|
{name,
|
||||||
|
mk(
|
||||||
|
binary(),
|
||||||
|
#{
|
||||||
|
in => path,
|
||||||
|
desc => ?DESC(backend_name_in_qs),
|
||||||
|
example => <<"ldap">>
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
].
|
||||||
|
|
||||||
|
on_backend_update(Backend, Config, Fun) ->
|
||||||
|
Result = valid_config(Backend, Config, Fun),
|
||||||
|
handle_backend_update_result(Result, Config).
|
||||||
|
|
||||||
|
valid_config(Backend, Config, Fun) ->
|
||||||
|
case maps:get(backend, Config, undefined) of
|
||||||
|
Backend ->
|
||||||
|
Fun(Backend, Config);
|
||||||
|
_ ->
|
||||||
|
{error, invalid_config}
|
||||||
|
end.
|
||||||
|
|
||||||
|
handle_backend_update_result({ok, _}, Config) ->
|
||||||
|
{200, Config};
|
||||||
|
handle_backend_update_result(ok, _) ->
|
||||||
|
204;
|
||||||
|
handle_backend_update_result({error, not_exists}, _) ->
|
||||||
|
{404, ?BACKEND_NOT_FOUND};
|
||||||
|
handle_backend_update_result({error, already_exists}, _) ->
|
||||||
|
{400, ?BAD_REQUEST, <<"Backend already exists.">>};
|
||||||
|
handle_backend_update_result({error, Reason}, _) ->
|
||||||
|
{400, ?BAD_REQUEST, Reason}.
|
|
@ -0,0 +1,18 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_app).
|
||||||
|
|
||||||
|
-behaviour(application).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
start/2,
|
||||||
|
stop/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
start(_StartType, _StartArgs) ->
|
||||||
|
emqx_dashboard_sso_sup:start_link().
|
||||||
|
|
||||||
|
stop(_State) ->
|
||||||
|
ok.
|
|
@ -0,0 +1,134 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_ldap).
|
||||||
|
|
||||||
|
-include_lib("emqx_dashboard/include/emqx_dashboard.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("eldap/include/eldap.hrl").
|
||||||
|
|
||||||
|
-behaviour(emqx_dashboard_sso).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
fields/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
hocon_ref/0,
|
||||||
|
login_ref/0,
|
||||||
|
sign/2,
|
||||||
|
create/1,
|
||||||
|
update/2,
|
||||||
|
destroy/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Hocon Schema
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
hocon_ref() ->
|
||||||
|
hoconsc:ref(?MODULE, ldap).
|
||||||
|
|
||||||
|
login_ref() ->
|
||||||
|
hoconsc:ref(?MODULE, login).
|
||||||
|
|
||||||
|
fields(ldap) ->
|
||||||
|
emqx_dashboard_sso_schema:common_backend_schema(ldap) ++
|
||||||
|
[
|
||||||
|
{query_timeout, fun query_timeout/1}
|
||||||
|
] ++
|
||||||
|
emqx_ldap:fields(config) ++ emqx_ldap:fields(bind_opts);
|
||||||
|
fields(login) ->
|
||||||
|
[
|
||||||
|
emqx_dashboard_sso_schema:backend_schema(ldap)
|
||||||
|
| emqx_dashboard_sso_schema:username_password_schema()
|
||||||
|
].
|
||||||
|
|
||||||
|
query_timeout(type) -> emqx_schema:timeout_duration_ms();
|
||||||
|
query_timeout(desc) -> ?DESC(?FUNCTION_NAME);
|
||||||
|
query_timeout(default) -> <<"5s">>;
|
||||||
|
query_timeout(_) -> undefined.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% APIs
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
create(Config0) ->
|
||||||
|
ResourceId = emqx_dashboard_sso_manager:make_resource_id(ldap),
|
||||||
|
{Config, State} = parse_config(Config0),
|
||||||
|
case emqx_dashboard_sso_manager:create_resource(ResourceId, emqx_ldap, Config) of
|
||||||
|
{ok, _} ->
|
||||||
|
{ok, State#{resource_id => ResourceId}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
update(Config0, #{resource_id := ResourceId} = _State) ->
|
||||||
|
{Config, NState} = parse_config(Config0),
|
||||||
|
case emqx_dashboard_sso_manager:update_resource(ResourceId, emqx_ldap, Config) of
|
||||||
|
{ok, _} ->
|
||||||
|
{ok, NState#{resource_id => ResourceId}};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
destroy(#{resource_id := ResourceId}) ->
|
||||||
|
_ = emqx_resource:remove_local(ResourceId),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
sign(
|
||||||
|
#{username := Username} = Req,
|
||||||
|
#{
|
||||||
|
query_timeout := Timeout,
|
||||||
|
resource_id := ResourceId
|
||||||
|
} = _State
|
||||||
|
) ->
|
||||||
|
case
|
||||||
|
emqx_resource:simple_sync_query(
|
||||||
|
ResourceId,
|
||||||
|
{query, Req, [], Timeout}
|
||||||
|
)
|
||||||
|
of
|
||||||
|
{ok, []} ->
|
||||||
|
{error, user_not_found};
|
||||||
|
{ok, [_Entry | _]} ->
|
||||||
|
case
|
||||||
|
emqx_resource:simple_sync_query(
|
||||||
|
ResourceId,
|
||||||
|
{bind, Req}
|
||||||
|
)
|
||||||
|
of
|
||||||
|
ok ->
|
||||||
|
User = ensure_user_exists(Username),
|
||||||
|
{ok, emqx_dashboard_token:sign(User, <<>>)};
|
||||||
|
{error, _} = Error ->
|
||||||
|
Error
|
||||||
|
end;
|
||||||
|
{error, _} = Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_config(Config) ->
|
||||||
|
State = lists:foldl(
|
||||||
|
fun(Key, Acc) ->
|
||||||
|
case maps:find(Key, Config) of
|
||||||
|
{ok, Value} when is_binary(Value) ->
|
||||||
|
Acc#{Key := erlang:binary_to_list(Value)};
|
||||||
|
_ ->
|
||||||
|
Acc
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Config,
|
||||||
|
[query_timeout]
|
||||||
|
),
|
||||||
|
{Config, State}.
|
||||||
|
|
||||||
|
ensure_user_exists(Username) ->
|
||||||
|
case emqx_dashboard_admin:lookup_user(ldap, Username) of
|
||||||
|
[User] ->
|
||||||
|
User;
|
||||||
|
[] ->
|
||||||
|
emqx_dashboard_admin:add_sso_user(ldap, Username, ?ROLE_VIEWER, <<>>)
|
||||||
|
end.
|
|
@ -0,0 +1,269 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_manager).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([
|
||||||
|
init/1,
|
||||||
|
handle_call/3,
|
||||||
|
handle_cast/2,
|
||||||
|
handle_info/2,
|
||||||
|
terminate/2,
|
||||||
|
code_change/3,
|
||||||
|
format_status/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
running/0,
|
||||||
|
lookup_state/1,
|
||||||
|
make_resource_id/1,
|
||||||
|
create_resource/3,
|
||||||
|
update_resource/3,
|
||||||
|
call/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
create/2,
|
||||||
|
update/2,
|
||||||
|
delete/1,
|
||||||
|
pre_config_update/3,
|
||||||
|
post_config_update/5
|
||||||
|
]).
|
||||||
|
|
||||||
|
-import(emqx_dashboard_sso, [provider/1]).
|
||||||
|
|
||||||
|
-define(MOD_KEY_PATH, [dashboard_sso]).
|
||||||
|
-define(RESOURCE_GROUP, <<"emqx_dashboard_sso">>).
|
||||||
|
-define(DEFAULT_RESOURCE_OPTS, #{
|
||||||
|
start_after_created => false
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(dashboard_sso, {
|
||||||
|
backend :: atom(),
|
||||||
|
state :: map()
|
||||||
|
}).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
running() ->
|
||||||
|
maps:fold(
|
||||||
|
fun
|
||||||
|
(Type, #{enable := true}, Acc) ->
|
||||||
|
[Type | Acc];
|
||||||
|
(_Type, _Cfg, Acc) ->
|
||||||
|
Acc
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
emqx:get_config([emqx_dashboard_sso])
|
||||||
|
).
|
||||||
|
|
||||||
|
create(Backend, Config) ->
|
||||||
|
update_config(Backend, {?FUNCTION_NAME, Backend, Config}).
|
||||||
|
update(Backend, Config) ->
|
||||||
|
update_config(Backend, {?FUNCTION_NAME, Backend, Config}).
|
||||||
|
delete(Backend) ->
|
||||||
|
update_config(Backend, {?FUNCTION_NAME, Backend}).
|
||||||
|
|
||||||
|
lookup_state(Backend) ->
|
||||||
|
case ets:lookup(dashboard_sso, Backend) of
|
||||||
|
[Data] ->
|
||||||
|
Data#dashboard_sso.state;
|
||||||
|
[] ->
|
||||||
|
undefined
|
||||||
|
end.
|
||||||
|
|
||||||
|
make_resource_id(Backend) ->
|
||||||
|
BackendBin = bin(Backend),
|
||||||
|
emqx_resource:generate_id(BackendBin).
|
||||||
|
|
||||||
|
create_resource(ResourceId, Module, Config) ->
|
||||||
|
Result = emqx_resource:create_local(
|
||||||
|
ResourceId,
|
||||||
|
?RESOURCE_GROUP,
|
||||||
|
Module,
|
||||||
|
Config,
|
||||||
|
?DEFAULT_RESOURCE_OPTS
|
||||||
|
),
|
||||||
|
start_resource_if_enabled(Result, ResourceId, Config).
|
||||||
|
|
||||||
|
update_resource(ResourceId, Module, Config) ->
|
||||||
|
Result = emqx_resource:recreate_local(
|
||||||
|
ResourceId, Module, Config, ?DEFAULT_RESOURCE_OPTS
|
||||||
|
),
|
||||||
|
start_resource_if_enabled(ResourceId, Result, Config).
|
||||||
|
|
||||||
|
call(Req) ->
|
||||||
|
gen_server:call(?MODULE, Req).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% gen_server callbacks
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
init([]) ->
|
||||||
|
process_flag(trap_exit, true),
|
||||||
|
emqx_conf:add_handler(?MOD_KEY_PATH, ?MODULE),
|
||||||
|
emqx_utils_ets:new(
|
||||||
|
dashboard_sso,
|
||||||
|
[
|
||||||
|
set,
|
||||||
|
public,
|
||||||
|
named_table,
|
||||||
|
{keypos, #dashboard_sso.backend},
|
||||||
|
{read_concurrency, true}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
start_backend_services(),
|
||||||
|
{ok, #{}}.
|
||||||
|
|
||||||
|
handle_call({update_config, Req, NewConf, OldConf}, _From, State) ->
|
||||||
|
Result = on_config_update(Req, NewConf, OldConf),
|
||||||
|
{reply, Result, State};
|
||||||
|
handle_call(_Request, _From, State) ->
|
||||||
|
Reply = ok,
|
||||||
|
{reply, Reply, State}.
|
||||||
|
|
||||||
|
handle_cast(_Request, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
emqx_conf:remove_handler(?MOD_KEY_PATH),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
format_status(_Opt, Status) ->
|
||||||
|
Status.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
start_backend_services() ->
|
||||||
|
Backends = emqx_conf:get([dashboard_sso], #{}),
|
||||||
|
lists:foreach(
|
||||||
|
fun({Backend, Config}) ->
|
||||||
|
Provider = provider(Backend),
|
||||||
|
on_backend_updated(
|
||||||
|
Provider:create(Config),
|
||||||
|
fun(State) ->
|
||||||
|
ets:insert(dashboard_sso, #dashboard_sso{backend = Backend, state = State})
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
maps:to_list(Backends)
|
||||||
|
).
|
||||||
|
|
||||||
|
update_config(Backend, UpdateReq) ->
|
||||||
|
case emqx_conf:update([dashboard_sso, Backend], UpdateReq, #{override_to => cluster}) of
|
||||||
|
{ok, UpdateResult} ->
|
||||||
|
#{post_config_update := #{?MODULE := Result}} = UpdateResult,
|
||||||
|
{ok, Result};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
pre_config_update(_Path, {create, Backend, Config}, OldConf) ->
|
||||||
|
case maps:find(Backend, OldConf) of
|
||||||
|
{ok, _} ->
|
||||||
|
throw(already_exists);
|
||||||
|
error ->
|
||||||
|
{ok, OldConf#{Backend => Config}}
|
||||||
|
end;
|
||||||
|
pre_config_update(_Path, {update, Backend, Config}, OldConf) ->
|
||||||
|
case maps:find(Backend, OldConf) of
|
||||||
|
error ->
|
||||||
|
throw(not_exists);
|
||||||
|
{ok, _} ->
|
||||||
|
{ok, OldConf#{Backend => Config}}
|
||||||
|
end;
|
||||||
|
pre_config_update(_Path, {delete, Backend}, OldConf) ->
|
||||||
|
case maps:find(Backend, OldConf) of
|
||||||
|
error ->
|
||||||
|
throw(not_exists);
|
||||||
|
{ok, _} ->
|
||||||
|
{ok, maps:remove(Backend, OldConf)}
|
||||||
|
end.
|
||||||
|
|
||||||
|
post_config_update(_Path, UpdateReq, NewConf, OldConf, _AppEnvs) ->
|
||||||
|
Result = call({update_config, UpdateReq, NewConf, OldConf}),
|
||||||
|
{ok, Result}.
|
||||||
|
|
||||||
|
on_config_update({create, Backend, Config}, _NewConf, _OldConf) ->
|
||||||
|
case lookup(Backend) of
|
||||||
|
undefined ->
|
||||||
|
Provider = provider(Backend),
|
||||||
|
on_backend_updated(
|
||||||
|
Provider:create(Config),
|
||||||
|
fun(State) ->
|
||||||
|
ets:insert(dashboard_sso, #dashboard_sso{backend = Backend, state = State})
|
||||||
|
end
|
||||||
|
);
|
||||||
|
_Data ->
|
||||||
|
{error, already_exists}
|
||||||
|
end;
|
||||||
|
on_config_update({update, Backend, Config}, _NewConf, _OldConf) ->
|
||||||
|
case lookup(Backend) of
|
||||||
|
undefined ->
|
||||||
|
{error, not_exists};
|
||||||
|
Data ->
|
||||||
|
Provider = provider(Backend),
|
||||||
|
on_backend_updated(
|
||||||
|
Provider:update(Config, Data#dashboard_sso.state),
|
||||||
|
fun(State) ->
|
||||||
|
ets:insert(dashboard_sso, Data#dashboard_sso{state = State})
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end;
|
||||||
|
on_config_update({delete, Backend}, _NewConf, _OldConf) ->
|
||||||
|
case lookup(Backend) of
|
||||||
|
undefined ->
|
||||||
|
{error, not_exists};
|
||||||
|
Data ->
|
||||||
|
Provider = provider(Backend),
|
||||||
|
on_backend_updated(
|
||||||
|
Provider:destroy(Data#dashboard_sso.state),
|
||||||
|
fun() ->
|
||||||
|
ets:delete(dashboard_sso, Backend)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end.
|
||||||
|
|
||||||
|
lookup(Backend) ->
|
||||||
|
case ets:lookup(dashboard_sso, Backend) of
|
||||||
|
[Data] ->
|
||||||
|
Data;
|
||||||
|
[] ->
|
||||||
|
undefined
|
||||||
|
end.
|
||||||
|
|
||||||
|
start_resource_if_enabled(ResourceId, {ok, _} = Result, #{enable := true}) ->
|
||||||
|
_ = emqx_resource:start(ResourceId),
|
||||||
|
Result;
|
||||||
|
start_resource_if_enabled(_ResourceId, Result, _Config) ->
|
||||||
|
Result.
|
||||||
|
|
||||||
|
on_backend_updated({ok, State} = Ok, Fun) ->
|
||||||
|
Fun(State),
|
||||||
|
Ok;
|
||||||
|
on_backend_updated(ok, Fun) ->
|
||||||
|
Fun(),
|
||||||
|
ok;
|
||||||
|
on_backend_updated(Error, _) ->
|
||||||
|
Error.
|
||||||
|
|
||||||
|
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
|
||||||
|
bin(L) when is_list(L) -> list_to_binary(L);
|
||||||
|
bin(X) -> X.
|
|
@ -0,0 +1,82 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_schema).
|
||||||
|
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
|
||||||
|
%% Hocon
|
||||||
|
-export([namespace/0, roots/0, fields/1, tags/0, desc/1]).
|
||||||
|
-export([
|
||||||
|
common_backend_schema/1,
|
||||||
|
backend_schema/1,
|
||||||
|
username_password_schema/0
|
||||||
|
]).
|
||||||
|
-import(hoconsc, [ref/2, mk/2]).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Hocon Schema
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
namespace() -> dashboard_sso.
|
||||||
|
|
||||||
|
tags() ->
|
||||||
|
[<<"Dashboard Single Sign-On">>].
|
||||||
|
|
||||||
|
roots() -> [dashboard_sso].
|
||||||
|
|
||||||
|
fields(dashboard_sso) ->
|
||||||
|
lists:map(
|
||||||
|
fun({Type, Module}) ->
|
||||||
|
{Type, mk(Module:hocon_ref(), #{required => {false, recursively}})}
|
||||||
|
end,
|
||||||
|
maps:to_list(emqx_dashboard_sso:backends())
|
||||||
|
).
|
||||||
|
|
||||||
|
desc(dashboard_sso) ->
|
||||||
|
"Dashboard Single Sign-On";
|
||||||
|
desc(_) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
common_backend_schema(Backend) ->
|
||||||
|
[
|
||||||
|
{enable,
|
||||||
|
mk(
|
||||||
|
boolean(), #{
|
||||||
|
desc => ?DESC(backend_enable),
|
||||||
|
required => false,
|
||||||
|
default => false
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
backend_schema(Backend)
|
||||||
|
].
|
||||||
|
|
||||||
|
backend_schema(Backend) ->
|
||||||
|
{backend,
|
||||||
|
mk(Backend, #{
|
||||||
|
required => true,
|
||||||
|
desc => ?DESC(backend)
|
||||||
|
})}.
|
||||||
|
|
||||||
|
username_password_schema() ->
|
||||||
|
[
|
||||||
|
{username,
|
||||||
|
mk(
|
||||||
|
binary(),
|
||||||
|
#{
|
||||||
|
desc => ?DESC(username),
|
||||||
|
'maxLength' => 100,
|
||||||
|
example => <<"admin">>
|
||||||
|
}
|
||||||
|
)},
|
||||||
|
{password,
|
||||||
|
mk(
|
||||||
|
binary(),
|
||||||
|
#{
|
||||||
|
desc => ?DESC(password),
|
||||||
|
'maxLength' => 100,
|
||||||
|
example => <<"public">>
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
].
|
|
@ -0,0 +1,22 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_sup).
|
||||||
|
|
||||||
|
-behaviour(supervisor).
|
||||||
|
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
-export([init/1]).
|
||||||
|
|
||||||
|
-define(CHILD(I, ShutDown), {I, {I, start_link, []}, permanent, ShutDown, worker, [I]}).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
{ok,
|
||||||
|
{{one_for_one, 5, 100}, [
|
||||||
|
?CHILD(emqx_dashboard_sso_manager, 5000)
|
||||||
|
]}}.
|
|
@ -11,7 +11,8 @@
|
||||||
-define(EE_SCHEMA_MODULES, [
|
-define(EE_SCHEMA_MODULES, [
|
||||||
emqx_license_schema,
|
emqx_license_schema,
|
||||||
emqx_schema_registry_schema,
|
emqx_schema_registry_schema,
|
||||||
emqx_ft_schema
|
emqx_ft_schema,
|
||||||
|
emqx_dashboard_sso_schema
|
||||||
]).
|
]).
|
||||||
|
|
||||||
namespace() ->
|
namespace() ->
|
||||||
|
|
|
@ -115,7 +115,8 @@
|
||||||
emqx_ft,
|
emqx_ft,
|
||||||
emqx_ldap,
|
emqx_ldap,
|
||||||
emqx_gcp_device,
|
emqx_gcp_device,
|
||||||
emqx_dashboard_rbac
|
emqx_dashboard_rbac,
|
||||||
|
emqx_dashboard_sso
|
||||||
],
|
],
|
||||||
%% must always be of type `load'
|
%% must always be of type `load'
|
||||||
ce_business_apps =>
|
ce_business_apps =>
|
||||||
|
|
3
mix.exs
3
mix.exs
|
@ -227,7 +227,8 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
:emqx_bridge_azure_event_hub,
|
:emqx_bridge_azure_event_hub,
|
||||||
:emqx_ldap,
|
:emqx_ldap,
|
||||||
:emqx_gcp_device,
|
:emqx_gcp_device,
|
||||||
:emqx_dashboard_rbac
|
:emqx_dashboard_rbac,
|
||||||
|
:emqx_dashboard_sso
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,7 @@ is_community_umbrella_app("apps/emqx_bridge_azure_event_hub") -> false;
|
||||||
is_community_umbrella_app("apps/emqx_ldap") -> false;
|
is_community_umbrella_app("apps/emqx_ldap") -> false;
|
||||||
is_community_umbrella_app("apps/emqx_gcp_device") -> false;
|
is_community_umbrella_app("apps/emqx_gcp_device") -> false;
|
||||||
is_community_umbrella_app("apps/emqx_dashboard_rbac") -> false;
|
is_community_umbrella_app("apps/emqx_dashboard_rbac") -> false;
|
||||||
|
is_community_umbrella_app("apps/emqx_dashboard_sso") -> false;
|
||||||
is_community_umbrella_app(_) -> true.
|
is_community_umbrella_app(_) -> true.
|
||||||
|
|
||||||
is_jq_supported() ->
|
is_jq_supported() ->
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
emqx_dashboard_api {
|
||||||
|
|
||||||
|
get_sso.desc:
|
||||||
|
"""List all SSO backends"""
|
||||||
|
get_sso.label:
|
||||||
|
"""SSO Backends"""
|
||||||
|
|
||||||
|
login.desc:
|
||||||
|
"""Get Dashboard Auth Token."""
|
||||||
|
login.label:
|
||||||
|
"""Get Dashboard Auth Token."""
|
||||||
|
|
||||||
|
get_running.desc:
|
||||||
|
"""Get Running SSO backends"""
|
||||||
|
get_running.label:
|
||||||
|
"""Running SSO"""
|
||||||
|
|
||||||
|
get_backend.desc:
|
||||||
|
"""Get details of a backend"""
|
||||||
|
get_backend.label:
|
||||||
|
"""Backend Details"""
|
||||||
|
|
||||||
|
create_backend.desc:
|
||||||
|
"""Create a backend"""
|
||||||
|
create_backend.label:
|
||||||
|
"""Create Backend"""
|
||||||
|
|
||||||
|
update_backend.desc:
|
||||||
|
"""Update a backend"""
|
||||||
|
update_backend.label:
|
||||||
|
"""Update Backend"""
|
||||||
|
|
||||||
|
delete_backend.desc:
|
||||||
|
"""Delete a backend"""
|
||||||
|
delete_backend.label:
|
||||||
|
"""Delete Backend"""
|
||||||
|
|
||||||
|
login_failed401.desc:
|
||||||
|
"""Login failed. Bad username or password"""
|
||||||
|
|
||||||
|
backend_not_found.desc:
|
||||||
|
"""Operate failed. Backend not exists"""
|
||||||
|
|
||||||
|
backend_name.desc:
|
||||||
|
"""Backend name"""
|
||||||
|
|
||||||
|
backend_name.label:
|
||||||
|
"""Backend Name"""
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
emqx_dashboard_sso_ldap {
|
||||||
|
|
||||||
|
ldap_bind.desc:
|
||||||
|
"""Configuration of authenticator using the LDAP bind operation as the authentication method."""
|
||||||
|
|
||||||
|
query_timeout.desc:
|
||||||
|
"""Timeout for the LDAP query."""
|
||||||
|
|
||||||
|
query_timeout.label:
|
||||||
|
"""Query Timeout"""
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
emqx_dashboard_sso_schema {
|
||||||
|
|
||||||
|
backend_enable.desc:
|
||||||
|
"""Whether to enable this backend."""
|
||||||
|
|
||||||
|
backend.desc:
|
||||||
|
"""Backend type."""
|
||||||
|
|
||||||
|
backend.label:
|
||||||
|
"""Backend Type"""
|
||||||
|
}
|
Loading…
Reference in New Issue