fix(sso): fix sso errors found when manual test

This commit is contained in:
firest 2023-09-19 19:15:52 +08:00
parent 2cddce5479
commit 285e529766
13 changed files with 180 additions and 140 deletions

View File

@ -103,11 +103,9 @@ add_default_user() ->
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec add_user(dashboard_username(), binary(), dashboard_user_role(), binary()) -> -spec add_user(dashboard_username(), binary(), dashboard_user_role(), binary()) ->
{ok, map()} | {error, any()}. {ok, map()} | {error, any()}.
add_user(Username, Password, Role, Desc) when is_binary(Password) -> add_user(Username, Password, Role, Desc) when is_binary(Username), 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};
@ -120,8 +118,6 @@ 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) ->
@ -211,7 +207,7 @@ add_user_(Username, Password, Role, Desc) ->
description = Desc description = Desc
}, },
mnesia:write(Admin), mnesia:write(Admin),
#{username => Username, role => Role, description => Desc}; flatten_username(#{username => Username, role => Role, description => Desc});
[_] -> [_] ->
mnesia:abort(<<"username_already_exist">>) mnesia:abort(<<"username_already_exist">>)
end. end.
@ -232,7 +228,8 @@ remove_user(Username) when is_binary(Username) ->
{error, Reason} {error, Reason}
end. end.
-spec update_user(binary(), dashboard_user_role(), binary()) -> {ok, map()} | {error, term()}. -spec update_user(dashboard_username(), dashboard_user_role(), binary()) ->
{ok, map()} | {error, term()}.
update_user(Username, Role, Desc) when is_binary(Username) -> update_user(Username, Role, Desc) when is_binary(Username) ->
case legal_role(Role) of case legal_role(Role) of
ok -> ok ->
@ -279,7 +276,10 @@ update_user_(Username, Role, Desc) ->
mnesia:abort(<<"username_not_found">>); mnesia:abort(<<"username_not_found">>);
[Admin] -> [Admin] ->
mnesia:write(Admin#?ADMIN{role = Role, description = Desc}), mnesia:write(Admin#?ADMIN{role = Role, description = Desc}),
{role(Admin) =:= Role, #{username => Username, role => Role, description => Desc}} {
role(Admin) =:= Role,
flatten_username(#{username => Username, role => Role, description => Desc})
}
end. end.
change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) -> change_password(Username, OldPasswd, NewPasswd) when is_binary(Username) ->
@ -335,11 +335,11 @@ all_users() ->
role = Role role = Role
} }
) -> ) ->
#{ flatten_username(#{
username => Username, username => Username,
description => Desc, description => Desc,
role => ensure_role(Role) role => ensure_role(Role)
} })
end, end,
ets:tab2list(?ADMIN) ets:tab2list(?ADMIN)
). ).
@ -417,10 +417,24 @@ 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()) -> flatten_username(#{username := ?SSO_USERNAME(Backend, Name)} = Data) ->
Data#{
username := Name,
backend => Backend
};
flatten_username(#{username := Username} = Data) when is_binary(Username) ->
Data#{backend => local}.
-spec add_sso_user(dashboard_sso_backend(), binary(), dashboard_user_role(), binary()) ->
{ok, map()} | {error, any()}. {ok, map()} | {error, any()}.
add_sso_user(Backend, Username, Role, Desc) -> add_sso_user(Backend, Username0, Role, Desc) when is_binary(Username0) ->
add_user(?SSO_USERNAME(Backend, Username), <<>>, Role, Desc). case legal_role(Role) of
ok ->
Username = ?SSO_USERNAME(Backend, Username0),
do_add_user(Username, <<>>, Role, Desc);
{error, _} = Error ->
Error
end.
-spec lookup_user(dashboard_sso_backend(), binary()) -> [emqx_admin()]. -spec lookup_user(dashboard_sso_backend(), binary()) -> [emqx_admin()].
lookup_user(Backend, Username) when is_atom(Backend) -> lookup_user(Backend, Username) when is_atom(Backend) ->
@ -434,6 +448,9 @@ legal_role(_) ->
role(_) -> role(_) ->
?ROLE_DEFAULT. ?ROLE_DEFAULT.
flatten_username(Data) ->
Data.
-endif. -endif.
-ifdef(TEST). -ifdef(TEST).

View File

@ -89,6 +89,7 @@ schema("/logout") ->
post => #{ post => #{
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
desc => ?DESC(logout_api), desc => ?DESC(logout_api),
parameters => sso_parameters(),
'requestBody' => fields([username]), 'requestBody' => fields([username]),
responses => #{ responses => #{
204 => <<"Dashboard logout successfully">>, 204 => <<"Dashboard logout successfully">>,
@ -114,7 +115,7 @@ schema("/users") ->
desc => ?DESC(create_user_api), desc => ?DESC(create_user_api),
'requestBody' => fields([username, password, role, description]), 'requestBody' => fields([username, password, role, description]),
responses => #{ responses => #{
200 => fields([username, role, description]) 200 => fields([username, role, description, backend])
} }
} }
}; };
@ -124,17 +125,17 @@ schema("/users/:username") ->
put => #{ put => #{
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
desc => ?DESC(update_user_api), desc => ?DESC(update_user_api),
parameters => fields([username_in_path]), parameters => sso_parameters(fields([username_in_path])),
'requestBody' => fields([role, description]), 'requestBody' => fields([role, description]),
responses => #{ responses => #{
200 => fields([username, role, description]), 200 => fields([username, role, description, backend]),
404 => response_schema(404) 404 => response_schema(404)
} }
}, },
delete => #{ delete => #{
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
desc => ?DESC(delete_user_api), desc => ?DESC(delete_user_api),
parameters => fields([username_in_path]), parameters => sso_parameters(fields([username_in_path])),
responses => #{ responses => #{
204 => <<"Delete User successfully">>, 204 => <<"Delete User successfully">>,
400 => emqx_dashboard_swagger:error_codes( 400 => emqx_dashboard_swagger:error_codes(
@ -169,7 +170,7 @@ response_schema(404) ->
emqx_dashboard_swagger:error_codes([?USER_NOT_FOUND], ?DESC(users_api404)). emqx_dashboard_swagger:error_codes([?USER_NOT_FOUND], ?DESC(users_api404)).
fields(user) -> fields(user) ->
fields([username, description]); fields([username, role, description, backend]);
fields(List) -> fields(List) ->
[field(Key) || Key <- List, field_filter(Key)]. [field(Key) || Key <- List, field_filter(Key)].
@ -206,7 +207,10 @@ field(old_pwd) ->
field(new_pwd) -> field(new_pwd) ->
{new_pwd, mk(binary(), #{desc => ?DESC(new_pwd)})}; {new_pwd, mk(binary(), #{desc => ?DESC(new_pwd)})};
field(role) -> field(role) ->
{role, mk(binary(), #{desc => ?DESC(role), example => ?ROLE_DEFAULT})}. {role,
mk(binary(), #{desc => ?DESC(role), default => ?ROLE_DEFAULT, example => ?ROLE_DEFAULT})};
field(backend) ->
{backend, mk(binary(), #{desc => ?DESC(backend), example => <<"local">>})}.
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% API %% API
@ -229,15 +233,16 @@ login(post, #{body := Params}) ->
end. end.
logout(_, #{ logout(_, #{
body := #{<<"username">> := Username}, body := #{<<"username">> := Username0} = Req,
headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>} headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}
}) -> }) ->
Username = username(Req, Username0),
case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of
ok -> ok ->
?SLOG(info, #{msg => "Dashboard logout successfully", username => Username}), ?SLOG(info, #{msg => "Dashboard logout successfully", username => Username0}),
204; 204;
_R -> _R ->
?SLOG(info, #{msg => "Dashboard logout failed.", username => Username}), ?SLOG(info, #{msg => "Dashboard logout failed.", username => Username0}),
{401, ?WRONG_TOKEN_OR_USERNAME, <<"Ensure your token & username">>} {401, ?WRONG_TOKEN_OR_USERNAME, <<"Ensure your token & username">>}
end. end.
@ -266,9 +271,10 @@ users(post, #{body := Params}) ->
end end
end. end.
user(put, #{bindings := #{username := Username}, body := Params}) -> user(put, #{bindings := #{username := Username0}, body := Params} = Req) ->
Role = maps:get(<<"role">>, Params, ?ROLE_DEFAULT), Role = maps:get(<<"role">>, Params, ?ROLE_DEFAULT),
Desc = maps:get(<<"description">>, Params), Desc = maps:get(<<"description">>, Params),
Username = username(Req, Username0),
case emqx_dashboard_admin:update_user(Username, Role, Desc) of case emqx_dashboard_admin:update_user(Username, Role, Desc) of
{ok, Result} -> {ok, Result} ->
{200, filter_result(Result)}; {200, filter_result(Result)};
@ -277,14 +283,15 @@ user(put, #{bindings := #{username := Username}, body := Params}) ->
{error, Reason} -> {error, Reason} ->
{400, ?BAD_REQUEST, Reason} {400, ?BAD_REQUEST, Reason}
end; end;
user(delete, #{bindings := #{username := Username}, headers := Headers}) -> user(delete, #{bindings := #{username := Username0}, headers := Headers} = Req) ->
case Username == emqx_dashboard_admin:default_username() of case Username0 == emqx_dashboard_admin:default_username() of
true -> true ->
?SLOG(info, #{msg => "Dashboard delete admin user failed", username => Username}), ?SLOG(info, #{msg => "Dashboard delete admin user failed", username => Username0}),
Message = list_to_binary(io_lib:format("Cannot delete user ~p", [Username])), Message = list_to_binary(io_lib:format("Cannot delete user ~p", [Username0])),
{400, ?NOT_ALLOWED, Message}; {400, ?NOT_ALLOWED, Message};
false -> false ->
case is_self_auth(Username, Headers) of Username = username(Req, Username0),
case is_self_auth(Username0, Headers) of
true -> true ->
{400, ?NOT_ALLOWED, <<"Cannot delete self">>}; {400, ?NOT_ALLOWED, <<"Cannot delete self">>};
false -> false ->
@ -293,13 +300,15 @@ user(delete, #{bindings := #{username := Username}, headers := Headers}) ->
{404, ?USER_NOT_FOUND, Reason}; {404, ?USER_NOT_FOUND, Reason};
{ok, _} -> {ok, _} ->
?SLOG(info, #{ ?SLOG(info, #{
msg => "Dashboard delete admin user", username => Username msg => "Dashboard delete admin user", username => Username0
}), }),
{204} {204}
end end
end end
end. end.
is_self_auth(?SSO_USERNAME(_, _), _) ->
fasle;
is_self_auth(Username, #{<<"authorization">> := Token}) -> is_self_auth(Username, #{<<"authorization">> := Token}) ->
is_self_auth(Username, Token); is_self_auth(Username, Token);
is_self_auth(Username, #{<<"Authorization">> := Token}) -> is_self_auth(Username, #{<<"Authorization">> := Token}) ->
@ -362,6 +371,19 @@ field_filter(_) ->
filter_result(Result) -> filter_result(Result) ->
Result. Result.
sso_parameters() ->
sso_parameters([]).
sso_parameters(Params) ->
emqx_dashboard_sso_api:sso_parameters(Params).
username(#{bindings := #{backend := local}}, Username) ->
Username;
username(#{bindings := #{backend := Backend}}, Username) ->
?SSO_USERNAME(Backend, Username);
username(_Req, Username) ->
Username.
-else. -else.
field_filter(role) -> field_filter(role) ->
@ -372,6 +394,14 @@ field_filter(_) ->
filter_result(Result) when is_list(Result) -> filter_result(Result) when is_list(Result) ->
lists:map(fun filter_result/1, Result); lists:map(fun filter_result/1, Result);
filter_result(Result) -> filter_result(Result) ->
maps:without([role], Result). maps:without([role, backend], Result).
sso_parameters() ->
sso_parameters([]).
sso_parameters(Any) ->
Any.
username(_Req, Username) ->
Username.
-endif. -endif.

View File

@ -179,6 +179,9 @@ owner(Token) ->
{atomic, []} -> {error, not_found} {atomic, []} -> {error, not_found}
end. end.
jwk(?SSO_USERNAME(Backend, Name), Password, Salt) ->
BackendBin = erlang:atom_to_binary(Backend),
jwk(<<BackendBin/binary, "-", Name/binary>>, Password, Salt);
jwk(Username, Password, Salt) -> jwk(Username, Password, Salt) ->
Key = crypto:hash(md5, <<Salt/binary, Username/binary, Password/binary>>), Key = crypto:hash(md5, <<Salt/binary, Username/binary, Password/binary>>),
#{ #{
@ -192,12 +195,17 @@ jwt_expiration_time() ->
token_ttl() -> token_ttl() ->
emqx_conf:get([dashboard, token_expired_time], ?EXPTIME). emqx_conf:get([dashboard, token_expired_time], ?EXPTIME).
format(Token, ?SSO_USERNAME(Backend, Name), Role, ExpTime) ->
format(Token, Backend, Name, Role, ExpTime);
format(Token, Username, Role, ExpTime) -> format(Token, Username, Role, ExpTime) ->
format(Token, local, Username, Role, ExpTime).
format(Token, Backend, Username, Role, ExpTime) ->
#?ADMIN_JWT{ #?ADMIN_JWT{
token = Token, token = Token,
username = Username, username = Username,
exptime = ExpTime, exptime = ExpTime,
extra = #{role => Role} extra = #{role => Role, backend => Backend}
}. }.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
# Dashboard Single sign-on # 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. 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.
@ -8,4 +8,4 @@ Please see our [contributing.md](../../CONTRIBUTING.md).
## License ## License
See [APL](../../APL.txt). See EMQ Business Source License 1.1, refer to [LICENSE](BSL.txt).

View File

@ -26,7 +26,7 @@
-callback update(Config :: config(), State :: state()) -> -callback update(Config :: config(), State :: state()) ->
{ok, NewState :: state()} | {error, Reason :: term()}. {ok, NewState :: state()} | {error, Reason :: term()}.
-callback destroy(State :: state()) -> ok. -callback destroy(State :: state()) -> ok.
-callback sign(request(), State :: state()) -> -callback login(request(), State :: state()) ->
{ok, Token :: binary()} | {error, Reason :: term()}. {ok, Token :: binary()} | {error, Reason :: term()}.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------

View File

@ -27,12 +27,13 @@
]). ]).
-export([ -export([
running/2,
login/2, login/2,
sso/2, sso/2,
backend/2 backend/2
]). ]).
-export([sso_parameters/1]).
-define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD'). -define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD').
-define(BAD_REQUEST, 'BAD_REQUEST'). -define(BAD_REQUEST, 'BAD_REQUEST').
@ -47,8 +48,7 @@ api_spec() ->
paths() -> paths() ->
[ [
"/sso", "/sso",
"/sso/login", "/sso/login/:backend",
"/sso/running",
"/sso/:backend" "/sso/:backend"
]. ].
@ -59,16 +59,17 @@ schema("/sso") ->
tags => [?TAGS], tags => [?TAGS],
desc => ?DESC(get_sso), desc => ?DESC(get_sso),
responses => #{ responses => #{
200 => array(ref(backend_status)) 200 => array(enum(emqx_dashboard_sso:types()))
} }
} }
}; };
schema("/sso/login") -> schema("/sso/login/:backend") ->
#{ #{
'operationId' => login, 'operationId' => login,
post => #{ post => #{
tags => [?TAGS], tags => [?TAGS],
desc => ?DESC(login), desc => ?DESC(login),
parameters => backend_name_in_path(),
'requestBody' => login_union(), 'requestBody' => login_union(),
responses => #{ responses => #{
200 => emqx_dashboard_api:fields([token, version, license]), 200 => emqx_dashboard_api:fields([token, version, license]),
@ -77,17 +78,6 @@ schema("/sso/login") ->
} }
} }
}; };
schema("/sso/running") ->
#{
'operationId' => running,
get => #{
tags => [?TAGS],
desc => ?DESC(get_running),
responses => #{
200 => array(enum(emqx_dashboard_sso:types()))
}
}
};
schema("/sso/:backend") -> schema("/sso/:backend") ->
#{ #{
'operationId' => backend, 'operationId' => backend,
@ -100,15 +90,6 @@ schema("/sso/:backend") ->
404 => response_schema(404) 404 => response_schema(404)
} }
}, },
post => #{
tags => [?TAGS],
desc => ?DESC(create_backend),
parameters => backend_name_in_path(),
'requestBody' => backend_union(),
responses => #{
200 => backend_union()
}
},
put => #{ put => #{
tags => [?TAGS], tags => [?TAGS],
desc => ?DESC(update_backend), desc => ?DESC(update_backend),
@ -131,22 +112,19 @@ schema("/sso/:backend") ->
}. }.
fields(backend_status) -> fields(backend_status) ->
emqx_dashboard_sso_schema:common_backend_schema(enum(emqx_dashboard_sso:types())). emqx_dashboard_sso_schema:common_backend_schema(emqx_dashboard_sso:types()).
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% API %% API
running(get, _Request) -> login(post, #{bindings := #{backend := Backend}, body := Sign}) ->
{200, emqx_dashboard_sso_manager:running()}.
login(post, #{backend := Backend} = Request) ->
case emqx_dashboard_sso_manager:lookup_state(Backend) of case emqx_dashboard_sso_manager:lookup_state(Backend) of
undefined -> undefined ->
{404, ?BACKEND_NOT_FOUND}; {404, ?BACKEND_NOT_FOUND, <<"Backend not found">>};
State -> State ->
Provider = emqx_dashboard_sso:provider(Backend), Provider = emqx_dashboard_sso:provider(Backend),
case Provider:login(Request, State) of case Provider:login(Sign, State) of
{ok, Token} -> {ok, Token} ->
?SLOG(info, #{msg => "Dashboard SSO login successfully", request => Request}), ?SLOG(info, #{msg => "Dashboard SSO login successfully", request => Sign}),
Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())), Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
{200, #{ {200, #{
token => Token, token => Token,
@ -155,7 +133,9 @@ login(post, #{backend := Backend} = Request) ->
}}; }};
{error, Reason} -> {error, Reason} ->
?SLOG(info, #{ ?SLOG(info, #{
msg => "Dashboard SSO login failed", request => Request, reason => Reason msg => "Dashboard SSO login failed",
request => Sign,
reason => Reason
}), }),
{401, ?BAD_USERNAME_OR_PWD, <<"Auth failed">>} {401, ?BAD_USERNAME_OR_PWD, <<"Auth failed">>}
end end
@ -166,7 +146,7 @@ sso(get, _Request) ->
{200, {200,
lists:map( lists:map(
fun(Backend) -> fun(Backend) ->
maps:with([backend, enabled], Backend) maps:with([backend, enable], Backend)
end, end,
maps:values(SSO) maps:values(SSO)
)}. )}.
@ -176,14 +156,15 @@ backend(get, #{bindings := #{backend := Type}}) ->
undefined -> undefined ->
{404, ?BACKEND_NOT_FOUND}; {404, ?BACKEND_NOT_FOUND};
Backend -> Backend ->
{200, Backend} {200, to_json(Backend)}
end; 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}) -> backend(put, #{bindings := #{backend := Backend}, body := Config}) ->
on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:update/2); on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:update/2);
backend(delete, #{bindings := #{backend := Backend}, body := Config}) -> backend(delete, #{bindings := #{backend := Backend}}) ->
on_backend_update(Backend, Config, fun emqx_dashboard_sso_manager:delete/2). handle_backend_update_result(emqx_dashboard_sso_manager:delete(Backend), undefined).
sso_parameters(Params) ->
backend_name_as_arg(query, [local], <<"local">>) ++ Params.
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% internal %% internal
@ -199,14 +180,18 @@ login_union() ->
hoconsc:union([Mod:login_ref() || Mod <- emqx_dashboard_sso:modules()]). hoconsc:union([Mod:login_ref() || Mod <- emqx_dashboard_sso:modules()]).
backend_name_in_path() -> backend_name_in_path() ->
backend_name_as_arg(path, [], <<"ldap">>).
backend_name_as_arg(In, Extra, Default) ->
[ [
{name, {backend,
mk( mk(
binary(), enum(Extra ++ emqx_dashboard_sso:types()),
#{ #{
in => path, in => In,
desc => ?DESC(backend_name_in_qs), desc => ?DESC(backend_name_in_qs),
example => <<"ldap">> required => true,
example => Default
} }
)} )}
]. ].
@ -216,7 +201,7 @@ on_backend_update(Backend, Config, Fun) ->
handle_backend_update_result(Result, Config). handle_backend_update_result(Result, Config).
valid_config(Backend, Config, Fun) -> valid_config(Backend, Config, Fun) ->
case maps:get(backend, Config, undefined) of case maps:get(<<"backend">>, Config, undefined) of
Backend -> Backend ->
Fun(Backend, Config); Fun(Backend, Config);
_ -> _ ->
@ -224,12 +209,20 @@ valid_config(Backend, Config, Fun) ->
end. end.
handle_backend_update_result({ok, _}, Config) -> handle_backend_update_result({ok, _}, Config) ->
{200, Config}; {200, to_json(Config)};
handle_backend_update_result(ok, _) -> handle_backend_update_result(ok, _) ->
204; 204;
handle_backend_update_result({error, not_exists}, _) -> handle_backend_update_result({error, not_exists}, _) ->
{404, ?BACKEND_NOT_FOUND}; {404, ?BACKEND_NOT_FOUND, <<"Backend not found">>};
handle_backend_update_result({error, already_exists}, _) -> handle_backend_update_result({error, already_exists}, _) ->
{400, ?BAD_REQUEST, <<"Backend already exists.">>}; {400, ?BAD_REQUEST, <<"Backend already exists">>};
handle_backend_update_result({error, Reason}, _) -> handle_backend_update_result({error, Reason}, _) ->
{400, ?BAD_REQUEST, Reason}. {400, ?BAD_REQUEST, Reason}.
to_json(Data) ->
emqx_utils_maps:jsonable_map(
Data,
fun(K, V) ->
{K, emqx_utils_maps:binary_string(V)}
end
).

View File

@ -18,7 +18,7 @@
-export([ -export([
hocon_ref/0, hocon_ref/0,
login_ref/0, login_ref/0,
sign/2, login/2,
create/1, create/1,
update/2, update/2,
destroy/1 destroy/1
@ -35,14 +35,14 @@ login_ref() ->
hoconsc:ref(?MODULE, login). hoconsc:ref(?MODULE, login).
fields(ldap) -> fields(ldap) ->
emqx_dashboard_sso_schema:common_backend_schema(ldap) ++ emqx_dashboard_sso_schema:common_backend_schema([ldap]) ++
[ [
{query_timeout, fun query_timeout/1} {query_timeout, fun query_timeout/1}
] ++ ] ++
emqx_ldap:fields(config) ++ emqx_ldap:fields(bind_opts); emqx_ldap:fields(config) ++ emqx_ldap:fields(bind_opts);
fields(login) -> fields(login) ->
[ [
emqx_dashboard_sso_schema:backend_schema(ldap) emqx_dashboard_sso_schema:backend_schema([ldap])
| emqx_dashboard_sso_schema:username_password_schema() | emqx_dashboard_sso_schema:username_password_schema()
]. ].
@ -61,7 +61,7 @@ create(Config0) ->
case emqx_dashboard_sso_manager:create_resource(ResourceId, emqx_ldap, Config) of case emqx_dashboard_sso_manager:create_resource(ResourceId, emqx_ldap, Config) of
{ok, _} -> {ok, _} ->
{ok, State#{resource_id => ResourceId}}; {ok, State#{resource_id => ResourceId}};
Error -> {error, _} = Error ->
Error Error
end. end.
@ -70,7 +70,7 @@ update(Config0, #{resource_id := ResourceId} = _State) ->
case emqx_dashboard_sso_manager:update_resource(ResourceId, emqx_ldap, Config) of case emqx_dashboard_sso_manager:update_resource(ResourceId, emqx_ldap, Config) of
{ok, _} -> {ok, _} ->
{ok, NState#{resource_id => ResourceId}}; {ok, NState#{resource_id => ResourceId}};
Error -> {error, _} = Error ->
Error Error
end. end.
@ -78,8 +78,8 @@ destroy(#{resource_id := ResourceId}) ->
_ = emqx_resource:remove_local(ResourceId), _ = emqx_resource:remove_local(ResourceId),
ok. ok.
sign( login(
#{username := Username} = Req, #{<<"username">> := Username} = Req,
#{ #{
query_timeout := Timeout, query_timeout := Timeout,
resource_id := ResourceId resource_id := ResourceId
@ -101,8 +101,7 @@ sign(
) )
of of
ok -> ok ->
User = ensure_user_exists(Username), ensure_user_exists(Username);
{ok, emqx_dashboard_token:sign(User, <<>>)};
{error, _} = Error -> {error, _} = Error ->
Error Error
end; end;
@ -128,7 +127,12 @@ parse_config(Config) ->
ensure_user_exists(Username) -> ensure_user_exists(Username) ->
case emqx_dashboard_admin:lookup_user(ldap, Username) of case emqx_dashboard_admin:lookup_user(ldap, Username) of
[User] -> [User] ->
User; {ok, emqx_dashboard_token:sign(User, <<>>)};
[] -> [] ->
emqx_dashboard_admin:add_sso_user(ldap, Username, ?ROLE_VIEWER, <<>>) case emqx_dashboard_admin:add_sso_user(ldap, Username, ?ROLE_VIEWER, <<>>) of
{ok, _} ->
ensure_user_exists(Username);
Error ->
Error
end
end. end.

View File

@ -30,7 +30,6 @@
]). ]).
-export([ -export([
create/2,
update/2, update/2,
delete/1, delete/1,
pre_config_update/3, pre_config_update/3,
@ -68,8 +67,6 @@ running() ->
emqx:get_config([emqx_dashboard_sso]) emqx:get_config([emqx_dashboard_sso])
). ).
create(Backend, Config) ->
update_config(Backend, {?FUNCTION_NAME, Backend, Config}).
update(Backend, Config) -> update(Backend, Config) ->
update_config(Backend, {?FUNCTION_NAME, Backend, Config}). update_config(Backend, {?FUNCTION_NAME, Backend, Config}).
delete(Backend) -> delete(Backend) ->
@ -85,7 +82,7 @@ lookup_state(Backend) ->
make_resource_id(Backend) -> make_resource_id(Backend) ->
BackendBin = bin(Backend), BackendBin = bin(Backend),
emqx_resource:generate_id(BackendBin). emqx_resource:generate_id(<<"sso:", BackendBin/binary>>).
create_resource(ResourceId, Module, Config) -> create_resource(ResourceId, Module, Config) ->
Result = emqx_resource:create_local( Result = emqx_resource:create_local(
@ -95,7 +92,7 @@ create_resource(ResourceId, Module, Config) ->
Config, Config,
?DEFAULT_RESOURCE_OPTS ?DEFAULT_RESOURCE_OPTS
), ),
start_resource_if_enabled(Result, ResourceId, Config). start_resource_if_enabled(ResourceId, Result, Config).
update_resource(ResourceId, Module, Config) -> update_resource(ResourceId, Module, Config) ->
Result = emqx_resource:recreate_local( Result = emqx_resource:recreate_local(
@ -127,6 +124,9 @@ init([]) ->
handle_call({update_config, Req, NewConf, OldConf}, _From, State) -> handle_call({update_config, Req, NewConf, OldConf}, _From, State) ->
Result = on_config_update(Req, NewConf, OldConf), Result = on_config_update(Req, NewConf, OldConf),
io:format(">>> on_config_update:~p~n,Req:~p~n NewConf:~p~n OldConf:~p~n", [
Result, Req, NewConf, OldConf
]),
{reply, Result, State}; {reply, Result, State};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok,
@ -166,60 +166,43 @@ start_backend_services() ->
maps:to_list(Backends) maps:to_list(Backends)
). ).
update_config(Backend, UpdateReq) -> update_config(_Backend, UpdateReq) ->
case emqx_conf:update([dashboard_sso, Backend], UpdateReq, #{override_to => cluster}) of case emqx_conf:update([dashboard_sso], UpdateReq, #{override_to => cluster}) of
{ok, UpdateResult} -> {ok, UpdateResult} ->
#{post_config_update := #{?MODULE := Result}} = UpdateResult, #{post_config_update := #{?MODULE := Result}} = UpdateResult,
{ok, Result}; Result;
Error -> Error ->
Error Error
end. 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) -> pre_config_update(_Path, {update, Backend, Config}, OldConf) ->
case maps:find(Backend, OldConf) of BackendBin = bin(Backend),
error -> {ok, OldConf#{BackendBin => Config}};
throw(not_exists);
{ok, _} ->
{ok, OldConf#{Backend => Config}}
end;
pre_config_update(_Path, {delete, Backend}, OldConf) -> pre_config_update(_Path, {delete, Backend}, OldConf) ->
case maps:find(Backend, OldConf) of BackendBin = bin(Backend),
case maps:find(BackendBin, OldConf) of
error -> error ->
throw(not_exists); throw(not_exists);
{ok, _} -> {ok, _} ->
{ok, maps:remove(Backend, OldConf)} {ok, maps:remove(BackendBin, OldConf)}
end. end.
post_config_update(_Path, UpdateReq, NewConf, OldConf, _AppEnvs) -> post_config_update(_Path, UpdateReq, NewConf, OldConf, _AppEnvs) ->
Result = call({update_config, UpdateReq, NewConf, OldConf}), Result = call({update_config, UpdateReq, NewConf, OldConf}),
{ok, Result}. {ok, Result}.
on_config_update({create, Backend, Config}, _NewConf, _OldConf) -> on_config_update({update, Backend, _Config}, NewConf, _OldConf) ->
Provider = provider(Backend),
Config = maps:get(Backend, NewConf),
case lookup(Backend) of case lookup(Backend) of
undefined -> undefined ->
Provider = provider(Backend),
on_backend_updated( on_backend_updated(
Provider:create(Config), Provider:create(Config),
fun(State) -> fun(State) ->
ets:insert(dashboard_sso, #dashboard_sso{backend = Backend, state = State}) ets:insert(dashboard_sso, #dashboard_sso{backend = Backend, state = State})
end end
); );
_Data ->
{error, already_exists}
end;
on_config_update({update, Backend, Config}, _NewConf, _OldConf) ->
case lookup(Backend) of
undefined ->
{error, not_exists};
Data -> Data ->
Provider = provider(Backend),
on_backend_updated( on_backend_updated(
Provider:update(Config, Data#dashboard_sso.state), Provider:update(Config, Data#dashboard_sso.state),
fun(State) -> fun(State) ->

View File

@ -14,7 +14,7 @@
backend_schema/1, backend_schema/1,
username_password_schema/0 username_password_schema/0
]). ]).
-import(hoconsc, [ref/2, mk/2]). -import(hoconsc, [ref/2, mk/2, enum/1]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Hocon Schema %% Hocon Schema
@ -39,6 +39,7 @@ desc(dashboard_sso) ->
desc(_) -> desc(_) ->
undefined. undefined.
-spec common_backend_schema(list(atom())) -> proplists:proplist().
common_backend_schema(Backend) -> common_backend_schema(Backend) ->
[ [
{enable, {enable,
@ -54,7 +55,7 @@ common_backend_schema(Backend) ->
backend_schema(Backend) -> backend_schema(Backend) ->
{backend, {backend,
mk(Backend, #{ mk(enum(Backend), #{
required => true, required => true,
desc => ?DESC(backend) desc => ?DESC(backend)
})}. })}.

View File

@ -1,6 +1,6 @@
{application, emqx_enterprise, [ {application, emqx_enterprise, [
{description, "EMQX Enterprise Edition"}, {description, "EMQX Enterprise Edition"},
{vsn, "0.1.2"}, {vsn, "0.1.3"},
{registered, []}, {registered, []},
{applications, [ {applications, [
kernel, kernel,

View File

@ -75,8 +75,16 @@ fields(config) ->
?HOCON(emqx_schema:timeout_duration_ms(), #{ ?HOCON(emqx_schema:timeout_duration_ms(), #{
desc => ?DESC(request_timeout), desc => ?DESC(request_timeout),
default => <<"5s">> default => <<"5s">>
})},
{ssl,
?HOCON(?R_REF(?MODULE, ssl), #{
default => #{<<"enable">> => false},
desc => ?DESC(emqx_connector_schema_lib, "ssl")
})} })}
] ++ emqx_connector_schema_lib:ssl_fields(); ];
fields(ssl) ->
Schema = emqx_schema:client_ssl_opts_schema(#{}),
lists:keydelete("user_lookup_fun", 1, Schema);
fields(bind_opts) -> fields(bind_opts) ->
[ [
{bind_password, {bind_password,

View File

@ -79,4 +79,10 @@ users_api404.desc:
version.desc: version.desc:
"""EMQX Version""" """EMQX Version"""
role.desc:
"""User role"""
backend.desc:
"""User account source"""
} }

View File

@ -10,21 +10,11 @@ login.desc:
login.label: login.label:
"""Get Dashboard Auth Token.""" """Get Dashboard Auth Token."""
get_running.desc:
"""Get Running SSO backends"""
get_running.label:
"""Running SSO"""
get_backend.desc: get_backend.desc:
"""Get details of a backend""" """Get details of a backend"""
get_backend.label: get_backend.label:
"""Backend Details""" """Backend Details"""
create_backend.desc:
"""Create a backend"""
create_backend.label:
"""Create Backend"""
update_backend.desc: update_backend.desc:
"""Update a backend""" """Update a backend"""
update_backend.label: update_backend.label: