feat: saml integration for dashboard sso
This commit is contained in:
parent
d9466eef63
commit
c9e0d4fc30
|
@ -3,5 +3,6 @@
|
||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
{deps, [
|
{deps, [
|
||||||
{emqx_ldap, {path, "../../apps/emqx_ldap"}},
|
{emqx_ldap, {path, "../../apps/emqx_ldap"}},
|
||||||
{emqx_dashboard, {path, "../../apps/emqx_dashboard"}}
|
{emqx_dashboard, {path, "../../apps/emqx_dashboard"}},
|
||||||
|
{esaml, {git, "https://github.com/JimMoen/esaml", {branch, "master"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
kernel,
|
kernel,
|
||||||
stdlib,
|
stdlib,
|
||||||
emqx_dashboard,
|
emqx_dashboard,
|
||||||
emqx_ldap
|
emqx_ldap,
|
||||||
|
esaml
|
||||||
]},
|
]},
|
||||||
{mod, {emqx_dashboard_sso_app, []}},
|
{mod, {emqx_dashboard_sso_app, []}},
|
||||||
{env, []},
|
{env, []},
|
||||||
|
|
|
@ -77,4 +77,7 @@ provider(Backend) ->
|
||||||
maps:get(Backend, backends()).
|
maps:get(Backend, backends()).
|
||||||
|
|
||||||
backends() ->
|
backends() ->
|
||||||
#{ldap => emqx_dashboard_sso_ldap}.
|
#{
|
||||||
|
ldap => emqx_dashboard_sso_ldap,
|
||||||
|
saml => emqx_dashboard_sso_saml
|
||||||
|
}.
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
ref/1
|
ref/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-import(emqx_dashboard_sso, [provider/1]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
api_spec/0,
|
api_spec/0,
|
||||||
fields/1,
|
fields/1,
|
||||||
|
@ -28,11 +30,14 @@
|
||||||
running/2,
|
running/2,
|
||||||
login/2,
|
login/2,
|
||||||
sso/2,
|
sso/2,
|
||||||
backend/2
|
backend/2,
|
||||||
|
sp_saml_metadata/2,
|
||||||
|
sp_saml_callback/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([sso_parameters/1]).
|
-export([sso_parameters/1]).
|
||||||
|
|
||||||
|
-define(REDIRECT, 'REDIRECT').
|
||||||
-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').
|
||||||
-define(BACKEND_NOT_FOUND, 'BACKEND_NOT_FOUND').
|
-define(BACKEND_NOT_FOUND, 'BACKEND_NOT_FOUND').
|
||||||
|
@ -48,7 +53,10 @@ paths() ->
|
||||||
"/sso",
|
"/sso",
|
||||||
"/sso/:backend",
|
"/sso/:backend",
|
||||||
"/sso/running",
|
"/sso/running",
|
||||||
"/sso/login/:backend"
|
"/sso/login/:backend",
|
||||||
|
"/sso_saml/acs",
|
||||||
|
"/sso_saml/metadata"
|
||||||
|
%% "/sso_saml/logout"
|
||||||
].
|
].
|
||||||
|
|
||||||
schema("/sso/running") ->
|
schema("/sso/running") ->
|
||||||
|
@ -74,6 +82,9 @@ schema("/sso") ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
%% Visit "/sso/login/saml" to start the saml authentication process -- first check to see if
|
||||||
|
%% we are already logged in, otherwise we will make an AuthnRequest and send it to
|
||||||
|
%% our IDP
|
||||||
schema("/sso/login/:backend") ->
|
schema("/sso/login/:backend") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => login,
|
'operationId' => login,
|
||||||
|
@ -84,6 +95,8 @@ schema("/sso/login/:backend") ->
|
||||||
'requestBody' => login_union(),
|
'requestBody' => login_union(),
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => emqx_dashboard_api:fields([role, token, version, license]),
|
200 => emqx_dashboard_api:fields([role, token, version, license]),
|
||||||
|
%% Redirect to IDP for saml
|
||||||
|
302 => response_schema(302),
|
||||||
401 => response_schema(401),
|
401 => response_schema(401),
|
||||||
404 => response_schema(404)
|
404 => response_schema(404)
|
||||||
},
|
},
|
||||||
|
@ -121,8 +134,41 @@ schema("/sso/:backend") ->
|
||||||
404 => response_schema(404)
|
404 => response_schema(404)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
%% Handles HTTP-POST bound assertions coming back from the IDP.
|
||||||
|
schema("/sso_saml/acs") ->
|
||||||
|
#{
|
||||||
|
'operationId' => sp_saml_callback,
|
||||||
|
post => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(saml_sso_acs),
|
||||||
|
%% 'requestbody' => saml_response(),
|
||||||
|
%% SAMLResponse and RelayState
|
||||||
|
%% should return 302 to redirect to dashboard
|
||||||
|
responses => #{
|
||||||
|
302 => response_schema(302),
|
||||||
|
401 => response_schema(401),
|
||||||
|
404 => response_schema(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sso_saml/metadata") ->
|
||||||
|
#{
|
||||||
|
'operationId' => sp_saml_metadata,
|
||||||
|
get => #{
|
||||||
|
tags => [?TAGS],
|
||||||
|
desc => ?DESC(sp_saml_metadata),
|
||||||
|
'requestbody' => saml_metadata_response(),
|
||||||
|
responses => #{
|
||||||
|
200 => emqx_dashboard_api:fields([token, version, license]),
|
||||||
|
404 => response_schema(404)
|
||||||
|
}
|
||||||
|
}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
%% TODO:
|
||||||
|
%% schema("/sso_saml/logout") ->
|
||||||
|
|
||||||
fields(backend_status) ->
|
fields(backend_status) ->
|
||||||
emqx_dashboard_sso_schema:common_backend_schema(emqx_dashboard_sso:types()).
|
emqx_dashboard_sso_schema:common_backend_schema(emqx_dashboard_sso:types()).
|
||||||
|
|
||||||
|
@ -141,22 +187,19 @@ running(get, _Request) ->
|
||||||
maps:values(SSO)
|
maps:values(SSO)
|
||||||
)}.
|
)}.
|
||||||
|
|
||||||
login(post, #{bindings := #{backend := Backend}, body := Sign}) ->
|
login(post, #{bindings := #{backend := Backend}, body := Sign, headers := Headers}) ->
|
||||||
case emqx_dashboard_sso_manager:lookup_state(Backend) of
|
case emqx_dashboard_sso_manager:lookup_state(Backend) of
|
||||||
undefined ->
|
undefined ->
|
||||||
{404, ?BACKEND_NOT_FOUND, <<"Backend not found">>};
|
{404, ?BACKEND_NOT_FOUND, <<"Backend not found">>};
|
||||||
State ->
|
State ->
|
||||||
Provider = emqx_dashboard_sso:provider(Backend),
|
Provider = provider(Backend),
|
||||||
case emqx_dashboard_sso:login(Provider, Sign, State) of
|
case emqx_dashboard_sso:login(Provider, Sign, State) of
|
||||||
{ok, Role, Token} ->
|
{ok, Role, Token} ->
|
||||||
?SLOG(info, #{msg => "dashboard_sso_login_successful", request => Sign}),
|
?SLOG(info, #{msg => "dashboard_sso_login_successful", request => Sign}),
|
||||||
Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
|
{200, login_reply(Role, Token)};
|
||||||
{200, #{
|
{redirect, RedirectFun} ->
|
||||||
role => Role,
|
?SLOG(info, #{msg => "dashboard_sso_login_redirect", request => Sign}),
|
||||||
token => Token,
|
RedirectFun(Headers);
|
||||||
version => Version,
|
|
||||||
license => #{edition => emqx_release:edition()}
|
|
||||||
}};
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(info, #{
|
?SLOG(info, #{
|
||||||
msg => "dashboard_sso_login_failed",
|
msg => "dashboard_sso_login_failed",
|
||||||
|
@ -191,11 +234,41 @@ backend(delete, #{bindings := #{backend := Backend}}) ->
|
||||||
?SLOG(info, #{msg => "Delete SSO backend", backend => Backend}),
|
?SLOG(info, #{msg => "Delete SSO backend", backend => Backend}),
|
||||||
handle_backend_update_result(emqx_dashboard_sso_manager:delete(Backend), undefined).
|
handle_backend_update_result(emqx_dashboard_sso_manager:delete(Backend), undefined).
|
||||||
|
|
||||||
|
sp_saml_metadata(get, _Req) ->
|
||||||
|
case emqx_dashboard_sso_manager:lookup_state(saml) of
|
||||||
|
undefined ->
|
||||||
|
{404, ?BACKEND_NOT_FOUND, <<"Backend not found">>};
|
||||||
|
#{sp := SP} = _State ->
|
||||||
|
SignedXml = SP:generate_metadata(),
|
||||||
|
Metadata = xmerl:export([SignedXml], xmerl_xml),
|
||||||
|
{200, [{<<"Content-Type">>, <<"text/xml">>}], Metadata}
|
||||||
|
end.
|
||||||
|
|
||||||
|
sp_saml_callback(post, Req) ->
|
||||||
|
case emqx_dashboard_sso_manager:lookup_state(saml) of
|
||||||
|
undefined ->
|
||||||
|
{404, ?BACKEND_NOT_FOUND, <<"Backend not found">>};
|
||||||
|
State ->
|
||||||
|
case (provider(saml)):callback(Req, State) of
|
||||||
|
{ok, Token} ->
|
||||||
|
{200, [{<<"Content-Type">>, <<"text/html">>}], login_reply(Token)};
|
||||||
|
{error, Reason} ->
|
||||||
|
?SLOG(info, #{
|
||||||
|
msg => "dashboard_saml_sso_login_failed",
|
||||||
|
request => Req,
|
||||||
|
reason => Reason
|
||||||
|
}),
|
||||||
|
{403, #{code => <<"UNAUTHORIZED">>, message => Reason}}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
sso_parameters(Params) ->
|
sso_parameters(Params) ->
|
||||||
backend_name_as_arg(query, [local], <<"local">>) ++ Params.
|
backend_name_as_arg(query, [local], <<"local">>) ++ Params.
|
||||||
|
|
||||||
%% -------------------------------------------------------------------------------------------------
|
%% -------------------------------------------------------------------------------------------------
|
||||||
%% internal
|
%% internal
|
||||||
|
response_schema(302) ->
|
||||||
|
emqx_dashboard_swagger:error_codes([?REDIRECT], ?DESC(redirect));
|
||||||
response_schema(401) ->
|
response_schema(401) ->
|
||||||
emqx_dashboard_swagger:error_codes([?BAD_USERNAME_OR_PWD], ?DESC(login_failed401));
|
emqx_dashboard_swagger:error_codes([?BAD_USERNAME_OR_PWD], ?DESC(login_failed401));
|
||||||
response_schema(404) ->
|
response_schema(404) ->
|
||||||
|
@ -207,6 +280,18 @@ backend_union() ->
|
||||||
login_union() ->
|
login_union() ->
|
||||||
hoconsc:union([emqx_dashboard_sso:login_ref(Mod) || Mod <- emqx_dashboard_sso:modules()]).
|
hoconsc:union([emqx_dashboard_sso:login_ref(Mod) || Mod <- emqx_dashboard_sso:modules()]).
|
||||||
|
|
||||||
|
saml_metadata_response() ->
|
||||||
|
#{
|
||||||
|
'content' => #{
|
||||||
|
'application/xml' => #{
|
||||||
|
schema => #{
|
||||||
|
type => <<"string">>,
|
||||||
|
format => <<"binary">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
backend_name_in_path() ->
|
backend_name_in_path() ->
|
||||||
backend_name_as_arg(path, [], <<"ldap">>).
|
backend_name_as_arg(path, [], <<"ldap">>).
|
||||||
|
|
||||||
|
@ -228,13 +313,10 @@ on_backend_update(Backend, Config, Fun) ->
|
||||||
Result = valid_config(Backend, Config, Fun),
|
Result = valid_config(Backend, Config, Fun),
|
||||||
handle_backend_update_result(Result, Config).
|
handle_backend_update_result(Result, Config).
|
||||||
|
|
||||||
valid_config(Backend, Config, Fun) ->
|
valid_config(Backend, #{<<"backend">> := Backend} = Config, Fun) ->
|
||||||
case maps:get(<<"backend">>, Config, undefined) of
|
|
||||||
Backend ->
|
|
||||||
Fun(Backend, Config);
|
Fun(Backend, Config);
|
||||||
_ ->
|
valid_config(_, _, _) ->
|
||||||
{error, invalid_config}
|
{error, invalid_config}.
|
||||||
end.
|
|
||||||
|
|
||||||
handle_backend_update_result({ok, _}, Config) ->
|
handle_backend_update_result({ok, _}, Config) ->
|
||||||
{200, to_json(Config)};
|
{200, to_json(Config)};
|
||||||
|
@ -254,3 +336,11 @@ to_json(Data) ->
|
||||||
{K, emqx_utils_maps:binary_string(V)}
|
{K, emqx_utils_maps:binary_string(V)}
|
||||||
end
|
end
|
||||||
).
|
).
|
||||||
|
|
||||||
|
login_reply(Role, Token) ->
|
||||||
|
#{
|
||||||
|
role => Role,
|
||||||
|
token => Token,
|
||||||
|
version => iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
|
||||||
|
license => #{edition => emqx_release:edition()}
|
||||||
|
}.
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
start(_StartType, _StartArgs) ->
|
||||||
|
{ok, _} = application:ensure_all_started(esaml),
|
||||||
emqx_dashboard_sso_sup:start_link().
|
emqx_dashboard_sso_sup:start_link().
|
||||||
|
|
||||||
stop(_State) ->
|
stop(_State) ->
|
||||||
|
|
|
@ -77,7 +77,7 @@ delete(Backend) ->
|
||||||
lookup_state(Backend) ->
|
lookup_state(Backend) ->
|
||||||
case ets:lookup(dashboard_sso, Backend) of
|
case ets:lookup(dashboard_sso, Backend) of
|
||||||
[Data] ->
|
[Data] ->
|
||||||
Data#dashboard_sso.state;
|
Data;
|
||||||
[] ->
|
[] ->
|
||||||
undefined
|
undefined
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_sso_saml).
|
||||||
|
|
||||||
|
-include_lib("emqx_dashboard/include/emqx_dashboard.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("esaml/include/esaml.hrl").
|
||||||
|
|
||||||
|
-behaviour(emqx_dashboard_sso).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
fields/1,
|
||||||
|
desc/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
hocon_ref/0,
|
||||||
|
login_ref/0,
|
||||||
|
login/2,
|
||||||
|
create/1,
|
||||||
|
update/2,
|
||||||
|
destroy/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([callback/2]).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Hocon Schema
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
hocon_ref() ->
|
||||||
|
hoconsc:ref(?MODULE, saml).
|
||||||
|
|
||||||
|
login_ref() ->
|
||||||
|
hoconsc:ref(?MODULE, login).
|
||||||
|
|
||||||
|
fields(saml) ->
|
||||||
|
emqx_dashboard_sso_schema:common_backend_schema([saml]) ++
|
||||||
|
[
|
||||||
|
{dashboard_addr, fun dashboard_addr/1},
|
||||||
|
{idp_metadata_url, fun idp_metadata_url/1},
|
||||||
|
{sp_sign_request, fun sp_sign_request/1},
|
||||||
|
{sp_public_key, fun sp_public_key/1},
|
||||||
|
{sp_private_key, fun sp_private_key/1}
|
||||||
|
];
|
||||||
|
fields(login) ->
|
||||||
|
[
|
||||||
|
emqx_dashboard_sso_schema:backend_schema([saml])
|
||||||
|
].
|
||||||
|
|
||||||
|
dashboard_addr(type) -> binary();
|
||||||
|
%% without any path
|
||||||
|
dashboard_addr(desc) -> ?DESC(dashboard_addr);
|
||||||
|
dashboard_addr(default) -> <<"https://127.0.0.1:18083">>;
|
||||||
|
dashboard_addr(_) -> undefined.
|
||||||
|
|
||||||
|
%% TOOD: support raw xml metadata in hocon (maybe?🤔)
|
||||||
|
idp_metadata_url(type) -> binary();
|
||||||
|
idp_metadata_url(desc) -> ?DESC(idp_metadata_url);
|
||||||
|
idp_metadata_url(default) -> <<"https://idp.example.com">>;
|
||||||
|
idp_metadata_url(_) -> undefined.
|
||||||
|
|
||||||
|
sp_sign_request(type) -> boolean();
|
||||||
|
sp_sign_request(desc) -> ?DESC(sign_request);
|
||||||
|
sp_sign_request(default) -> false;
|
||||||
|
sp_sign_request(_) -> undefined.
|
||||||
|
|
||||||
|
sp_public_key(type) -> binary();
|
||||||
|
sp_public_key(desc) -> ?DESC(sp_public_key);
|
||||||
|
sp_public_key(default) -> <<"Pub Key">>;
|
||||||
|
sp_public_key(_) -> undefined.
|
||||||
|
|
||||||
|
sp_private_key(type) -> binary();
|
||||||
|
sp_private_key(desc) -> ?DESC(sp_private_key);
|
||||||
|
sp_private_key(required) -> false;
|
||||||
|
sp_private_key(format) -> <<"password">>;
|
||||||
|
sp_private_key(sensitive) -> true;
|
||||||
|
sp_private_key(_) -> undefined.
|
||||||
|
|
||||||
|
desc(saml) ->
|
||||||
|
"saml";
|
||||||
|
desc(_) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% APIs
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
create(
|
||||||
|
#{
|
||||||
|
dashboard_addr := DashboardAddr,
|
||||||
|
idp_metadata_url := IDPMetadataURL,
|
||||||
|
sp_sign_request := SignRequest
|
||||||
|
} = Config
|
||||||
|
) ->
|
||||||
|
BaseURL = binary_to_list(DashboardAddr) ++ "/api/v5",
|
||||||
|
%% {Config, State} = parse_config(Config),
|
||||||
|
SP = esaml_sp:setup(#esaml_sp{
|
||||||
|
%% TODO: save cert and key then return path
|
||||||
|
%% TODO: #esaml_sp.key #esaml_sp.certificate support
|
||||||
|
%% key = PrivKey,
|
||||||
|
%% certificate = Cert,
|
||||||
|
sp_sign_requests = SignRequest,
|
||||||
|
trusted_fingerprints = [],
|
||||||
|
consume_uri = BaseURL ++ "/sso_saml/acs",
|
||||||
|
metadata_uri = BaseURL ++ "/sso_saml/metadata",
|
||||||
|
org = #esaml_org{
|
||||||
|
name = "EMQX Team",
|
||||||
|
displayname = "EMQX Dashboard",
|
||||||
|
url = DashboardAddr
|
||||||
|
},
|
||||||
|
tech = #esaml_contact{
|
||||||
|
name = "EMQX Team",
|
||||||
|
email = "contact@emqx.io"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
IdpMeta = esaml_util:load_metadata(binary_to_list(IDPMetadataURL)),
|
||||||
|
|
||||||
|
{ok, Config#{idp_meta => IdpMeta, sp => SP}}.
|
||||||
|
|
||||||
|
update(_Config0, State) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
destroy(#{resource_id := ResourceId}) ->
|
||||||
|
_ = emqx_resource:remove_local(ResourceId),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
login(_Req, #{sp := SP, idp_meta := #esaml_idp_metadata{login_location = IDP}} = _State) ->
|
||||||
|
SignedXml = SP:generate_authn_request(IDP),
|
||||||
|
Target = esaml_binding:encode_http_redirect(IDP, SignedXml, <<>>),
|
||||||
|
%% TODO: _Req acutally is HTTP request body, not fully request
|
||||||
|
RedirectFun = fun(Headers) ->
|
||||||
|
case is_msie(Headers) of
|
||||||
|
true ->
|
||||||
|
Html = esaml_binding:encode_http_post(IDP, SignedXml, <<>>),
|
||||||
|
{200,
|
||||||
|
[
|
||||||
|
{<<"Cache-Control">>, <<"no-cache">>},
|
||||||
|
{<<"Pragma">>, <<"no-cache">>}
|
||||||
|
],
|
||||||
|
Html};
|
||||||
|
false ->
|
||||||
|
{302, redirect_header(Target), <<"Redirecting...">>}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
{redirect, RedirectFun}.
|
||||||
|
|
||||||
|
callback(Req, #{sp := SP} = _State) ->
|
||||||
|
case esaml_cowboy:validate_assertion(SP, fun esaml_util:check_dupe_ets/2, Req) of
|
||||||
|
{ok, Assertion, _RelayState, _Req2} ->
|
||||||
|
Subject = Assertion#esaml_assertion.subject,
|
||||||
|
Username = Subject#esaml_subject.name,
|
||||||
|
ensure_user_exists(Username);
|
||||||
|
{error, Reason0, _Req2} ->
|
||||||
|
Reason = [
|
||||||
|
"Access denied, assertion failed validation:\n", io_lib:format("~p\n", [Reason0])
|
||||||
|
],
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% -define(DIR, <<"SAML_SSO_sp_certs">>).
|
||||||
|
%% -define(RSA_KEYS_A, [sp_public_key, sp_private_key]).
|
||||||
|
|
||||||
|
is_msie(Headers) ->
|
||||||
|
UA = maps:get(<<"user-agent">>, Headers, <<"">>),
|
||||||
|
not (binary:match(UA, <<"MSIE">>) =:= nomatch).
|
||||||
|
|
||||||
|
redirect_header(TargetUrl) ->
|
||||||
|
[
|
||||||
|
{<<"Cache-Control">>, <<"no-cache">>},
|
||||||
|
{<<"Pragma">>, <<"no-cache">>},
|
||||||
|
{<<"Location">>, TargetUrl}
|
||||||
|
].
|
||||||
|
|
||||||
|
%% TODO: unify with emqx_dashboard_sso_manager:ensure_user_exists/1
|
||||||
|
ensure_user_exists(Username) ->
|
||||||
|
case emqx_dashboard_admin:lookup_user(saml, Username) of
|
||||||
|
[User] ->
|
||||||
|
emqx_dashboard_token:sign(User, <<>>);
|
||||||
|
[] ->
|
||||||
|
case emqx_dashboard_admin:add_sso_user(saml, Username, ?ROLE_VIEWER, <<>>) of
|
||||||
|
{ok, _} ->
|
||||||
|
ensure_user_exists(Username);
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end
|
||||||
|
end.
|
|
@ -84,6 +84,7 @@
|
||||||
%% in conflict by erlavro and rocketmq
|
%% in conflict by erlavro and rocketmq
|
||||||
, {jsone, {git, "https://github.com/emqx/jsone.git", {tag, "1.7.1"}}}
|
, {jsone, {git, "https://github.com/emqx/jsone.git", {tag, "1.7.1"}}}
|
||||||
, {uuid, {git, "https://github.com/okeuday/uuid.git", {tag, "v2.0.6"}}}
|
, {uuid, {git, "https://github.com/okeuday/uuid.git", {tag, "v2.0.6"}}}
|
||||||
|
%% , {esaml, {git, "
|
||||||
%% trace
|
%% trace
|
||||||
, {opentelemetry_api, {git_subdir, "https://github.com/emqx/opentelemetry-erlang", {tag, "v1.3.0-emqx"}, "apps/opentelemetry_api"}}
|
, {opentelemetry_api, {git_subdir, "https://github.com/emqx/opentelemetry-erlang", {tag, "v1.3.0-emqx"}, "apps/opentelemetry_api"}}
|
||||||
, {opentelemetry, {git_subdir, "https://github.com/emqx/opentelemetry-erlang", {tag, "v1.3.0-emqx"}, "apps/opentelemetry"}}
|
, {opentelemetry, {git_subdir, "https://github.com/emqx/opentelemetry-erlang", {tag, "v1.3.0-emqx"}, "apps/opentelemetry"}}
|
||||||
|
|
|
@ -30,6 +30,15 @@ delete_backend.desc:
|
||||||
delete_backend.label:
|
delete_backend.label:
|
||||||
"""Delete Backend"""
|
"""Delete Backend"""
|
||||||
|
|
||||||
|
saml_sso_acs.desc:
|
||||||
|
"""SAML SSO ACS URL"""
|
||||||
|
|
||||||
|
sp_saml_metadata.desc:
|
||||||
|
"""SP SAML Metadata"""
|
||||||
|
|
||||||
|
redirect.desc:
|
||||||
|
"""Redirect to IDP SSO login page"""
|
||||||
|
|
||||||
login_failed401.desc:
|
login_failed401.desc:
|
||||||
"""Login failed. Bad username or password"""
|
"""Login failed. Bad username or password"""
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
emqx_dashboard_sso_saml {
|
||||||
|
|
||||||
|
dashboard_addr.desc:
|
||||||
|
"""The address of the EMQX Dashboard."""
|
||||||
|
dashboard_addr.label:
|
||||||
|
"""Dashboard Address"""
|
||||||
|
|
||||||
|
idp_metadata_url.desc:
|
||||||
|
"""The URL of the IdP metadata."""
|
||||||
|
idp_metadata_url.label:
|
||||||
|
"""IdP Metadata URL"""
|
||||||
|
|
||||||
|
sign_request.desc:
|
||||||
|
"""Whether to sign the SAML request."""
|
||||||
|
sign_request.label:
|
||||||
|
"""Sign SAML Request"""
|
||||||
|
|
||||||
|
sp_public_key.desc:
|
||||||
|
"""The public key of the SP."""
|
||||||
|
sp_public_key.label:
|
||||||
|
"""SP Public Key"""
|
||||||
|
|
||||||
|
sp_private_key.desc:
|
||||||
|
"""The private key of the SP."""
|
||||||
|
sp_private_key.label:
|
||||||
|
"""SP Private Key"""
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue