fix(oidc): return to dashboard when provider calls back

fixed a bug when updating config
This commit is contained in:
firest 2024-06-26 15:49:13 +08:00
parent abc255bb02
commit 3d398873f1
6 changed files with 54 additions and 29 deletions

View File

@ -33,7 +33,7 @@
backend/2 backend/2
]). ]).
-export([sso_parameters/1, login_meta/3]). -export([sso_parameters/1, login_meta/4]).
-define(REDIRECT, 'REDIRECT'). -define(REDIRECT, 'REDIRECT').
-define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD'). -define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD').
@ -168,7 +168,7 @@ login(post, #{bindings := #{backend := Backend}, body := Body} = Request) ->
request => emqx_utils:redact(Request) request => emqx_utils:redact(Request)
}), }),
Username = maps:get(<<"username">>, Body), Username = maps:get(<<"username">>, Body),
{200, login_meta(Username, Role, Token)}; {200, login_meta(Username, Role, Token, Backend)};
{redirect, Redirect} -> {redirect, Redirect} ->
?SLOG(info, #{ ?SLOG(info, #{
msg => "dashboard_sso_login_redirect", msg => "dashboard_sso_login_redirect",
@ -286,11 +286,12 @@ to_redacted_json(Data) ->
end end
). ).
login_meta(Username, Role, Token) -> login_meta(Username, Role, Token, Backend) ->
#{ #{
username => Username, username => Username,
role => Role, role => Role,
token => Token, token => Token,
version => iolist_to_binary(proplists:get_value(version, emqx_sys:info())), version => iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
license => #{edition => emqx_release:edition()} license => #{edition => emqx_release:edition()},
backend => Backend
}. }.

View File

@ -107,7 +107,14 @@ get_backend_status(Backend, _) ->
end. end.
update(Backend, Config) -> update(Backend, Config) ->
update_config(Backend, {?FUNCTION_NAME, Backend, Config}). UpdateConf =
case emqx:get_raw_config(?MOD_KEY_PATH(Backend), #{}) of
RawConf when is_map(RawConf) ->
emqx_utils:deobfuscate(Config, RawConf);
null ->
Config
end,
update_config(Backend, {?FUNCTION_NAME, Backend, UpdateConf}).
delete(Backend) -> delete(Backend) ->
update_config(Backend, {?FUNCTION_NAME, Backend}). update_config(Backend, {?FUNCTION_NAME, Backend}).

View File

@ -62,9 +62,8 @@ fields(oidc) ->
#{desc => ?DESC(clientid), required => true} #{desc => ?DESC(clientid), required => true}
)}, )},
{secret, {secret,
?HOCON( emqx_schema_secret:mk(
binary(), maps:merge(#{desc => ?DESC(secret), required => true}, #{})
#{desc => ?DESC(secret), required => true}
)}, )},
{scopes, {scopes,
?HOCON( ?HOCON(
@ -82,7 +81,7 @@ fields(oidc) ->
default => <<"http://127.0.0.1:18083">> default => <<"http://127.0.0.1:18083">>
})}, })},
{session_expiry, {session_expiry,
?HOCON(emqx_schema:timeout_duration_ms(), #{ ?HOCON(emqx_schema:timeout_duration_s(), #{
desc => ?DESC(session_expiry), desc => ?DESC(session_expiry),
default => <<"30s">> default => <<"30s">>
})}, })},
@ -217,7 +216,7 @@ login(
oidcc:create_redirect_url( oidcc:create_redirect_url(
?PROVIDER_SVR_NAME, ?PROVIDER_SVR_NAME,
ClientId, ClientId,
Secret, emqx_secret:unwrap(Secret),
Opts#{ Opts#{
state => State, state => State,
client_jwks => ClientJwks, client_jwks => ClientJwks,

View File

@ -30,6 +30,14 @@
-define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD'). -define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD').
-define(BACKEND_NOT_FOUND, 'BACKEND_NOT_FOUND'). -define(BACKEND_NOT_FOUND, 'BACKEND_NOT_FOUND').
-define(RESPHEADERS, #{
<<"cache-control">> => <<"no-cache">>,
<<"pragma">> => <<"no-cache">>,
<<"content-type">> => <<"text/plain">>
}).
-define(REDIRECT_BODY, <<"Redirecting...">>).
-define(TAGS, <<"Dashboard Single Sign-On">>). -define(TAGS, <<"Dashboard Single Sign-On">>).
-define(BACKEND, oidc). -define(BACKEND, oidc).
-define(BASE_PATH, "/api/v5"). -define(BASE_PATH, "/api/v5").
@ -66,11 +74,12 @@ schema("/sso/oidc/callback") ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
code_callback(get, #{query_string := QS}) -> code_callback(get, #{query_string := QS}) ->
case ensure_sso_state(QS) of case ensure_sso_state(QS) of
{ok, Username, Role, DashboardToken} -> {ok, Target} ->
?SLOG(info, #{ ?SLOG(info, #{
msg => "dashboard_sso_login_successful" msg => "dashboard_sso_login_successful"
}), }),
{200, login_meta(Username, Role, DashboardToken)};
{302, ?RESPHEADERS#{<<"location">> => Target}, ?REDIRECT_BODY};
{error, invalid_backend} -> {error, invalid_backend} ->
{404, #{code => ?BACKEND_NOT_FOUND, message => <<"Backend not found">>}}; {404, #{code => ?BACKEND_NOT_FOUND, message => <<"Backend not found">>}};
{error, Reason} -> {error, Reason} ->
@ -130,7 +139,7 @@ retrieve_token(
Code, Code,
Name, Name,
ClientId, ClientId,
Secret, emqx_secret:unwrap(Secret),
Data#{ Data#{
redirect_uri => make_callback_url(Cfg), redirect_uri => make_callback_url(Cfg),
client_jwks => ClientJwks, client_jwks => ClientJwks,
@ -144,18 +153,21 @@ retrieve_token(
Error Error
end. end.
retrieve_userinfo(Token, #{ retrieve_userinfo(
Token,
#{
name := Name, name := Name,
client_jwks := ClientJwks, client_jwks := ClientJwks,
config := #{clientid := ClientId, secret := Secret}, config := #{clientid := ClientId, secret := Secret},
name_tokens := NameTks name_tokens := NameTks
}) -> } = Cfg
) ->
case case
oidcc:retrieve_userinfo( oidcc:retrieve_userinfo(
Token, Token,
Name, Name,
ClientId, ClientId,
Secret, emqx_secret:unwrap(Secret),
#{client_jwks => ClientJwks} #{client_jwks => ClientJwks}
) )
of of
@ -165,29 +177,29 @@ retrieve_userinfo(Token, #{
user_info => UserInfo user_info => UserInfo
}), }),
Username = emqx_placeholder:proc_tmpl(NameTks, UserInfo), Username = emqx_placeholder:proc_tmpl(NameTks, UserInfo),
ensure_user_exists(Username); ensure_user_exists(Cfg, Username);
{error, _Reason} = Error -> {error, _Reason} = Error ->
Error Error
end. end.
-dialyzer({nowarn_function, ensure_user_exists/1}). -dialyzer({nowarn_function, ensure_user_exists/2}).
ensure_user_exists(<<>>) -> ensure_user_exists(_Cfg, <<>>) ->
{error, <<"Username can not be empty">>}; {error, <<"Username can not be empty">>};
ensure_user_exists(<<"undefined">>) -> ensure_user_exists(_Cfg, <<"undefined">>) ->
{error, <<"Username can not be undefined">>}; {error, <<"Username can not be undefined">>};
ensure_user_exists(Username) -> ensure_user_exists(Cfg, Username) ->
case emqx_dashboard_admin:lookup_user(?BACKEND, Username) of case emqx_dashboard_admin:lookup_user(?BACKEND, Username) of
[User] -> [User] ->
case emqx_dashboard_token:sign(User, <<>>) of case emqx_dashboard_token:sign(User, <<>>) of
{ok, Role, Token} -> {ok, Role, Token} ->
{ok, Username, Role, Token}; {ok, login_redirect_target(Cfg, Username, Role, Token)};
Error -> Error ->
Error Error
end; end;
[] -> [] ->
case emqx_dashboard_admin:add_sso_user(?BACKEND, Username, ?ROLE_VIEWER, <<>>) of case emqx_dashboard_admin:add_sso_user(?BACKEND, Username, ?ROLE_VIEWER, <<>>) of
{ok, _} -> {ok, _} ->
ensure_user_exists(Username); ensure_user_exists(Cfg, Username);
Error -> Error ->
Error Error
end end
@ -195,3 +207,8 @@ ensure_user_exists(Username) ->
make_callback_url(#{config := #{dashboard_addr := Addr}}) -> make_callback_url(#{config := #{dashboard_addr := Addr}}) ->
list_to_binary(binary_to_list(Addr) ++ ?BASE_PATH ++ ?CALLBACK_PATH). list_to_binary(binary_to_list(Addr) ++ ?BASE_PATH ++ ?CALLBACK_PATH).
login_redirect_target(#{config := #{dashboard_addr := Addr}}, Username, Role, Token) ->
LoginMeta = emqx_dashboard_sso_api:login_meta(Username, Role, Token, oidc),
MetaBin = base64:encode(emqx_utils_json:encode(LoginMeta)),
<<Addr/binary, "/?login_meta=", MetaBin/binary>>.

View File

@ -42,7 +42,7 @@
start_link(Cfg) -> start_link(Cfg) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Cfg, []). gen_server:start_link({local, ?MODULE}, ?MODULE, Cfg, []).
start(Name, #{issuer := Issuer, session_expiry := SessionExpiry}) -> start(Name, #{issuer := Issuer, session_expiry := SessionExpiry0}) ->
case case
emqx_dashboard_sso_sup:start_child( emqx_dashboard_sso_sup:start_child(
oidcc_provider_configuration_worker, oidcc_provider_configuration_worker,
@ -57,6 +57,7 @@ start(Name, #{issuer := Issuer, session_expiry := SessionExpiry}) ->
{error, _} = Error -> {error, _} = Error ->
Error; Error;
_ -> _ ->
SessionExpiry = timer:seconds(SessionExpiry0),
emqx_dashboard_sso_sup:start_child(?MODULE, [SessionExpiry]) emqx_dashboard_sso_sup:start_child(?MODULE, [SessionExpiry])
end. end.

View File

@ -273,7 +273,7 @@ is_msie(Headers) ->
not (binary:match(UA, <<"MSIE">>) =:= nomatch). not (binary:match(UA, <<"MSIE">>) =:= nomatch).
login_redirect_target(DashboardAddr, Username, Role, Token) -> login_redirect_target(DashboardAddr, Username, Role, Token) ->
LoginMeta = emqx_dashboard_sso_api:login_meta(Username, Role, Token), LoginMeta = emqx_dashboard_sso_api:login_meta(Username, Role, Token, saml),
<<DashboardAddr/binary, "/?login_meta=", (base64_login_meta(LoginMeta))/binary>>. <<DashboardAddr/binary, "/?login_meta=", (base64_login_meta(LoginMeta))/binary>>.
base64_login_meta(LoginMeta) -> base64_login_meta(LoginMeta) ->