Переглянути джерело

Merge pull request #11658 from lafirest/fix/sso_misc

Fix/sso misc
lafirest 2 роки тому
батько
коміт
13b5e4dbc9

+ 1 - 1
apps/emqx_bridge_kafka/test/emqx_bridge_kafka_impl_producer_SUITE.erl

@@ -1260,7 +1260,7 @@ auth_header_() ->
     auth_header_(<<"admin">>, <<"public">>).
     auth_header_(<<"admin">>, <<"public">>).
 
 
 auth_header_(Username, Password) ->
 auth_header_(Username, Password) ->
-    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(Username, Password),
     {"Authorization", "Bearer " ++ binary_to_list(Token)}.
     {"Authorization", "Bearer " ++ binary_to_list(Token)}.
 
 
 api_path(Parts) ->
 api_path(Parts) ->

+ 9 - 7
apps/emqx_dashboard/src/emqx_dashboard_api.erl

@@ -77,7 +77,7 @@ schema("/login") ->
             summary => <<"Dashboard authentication">>,
             summary => <<"Dashboard authentication">>,
             'requestBody' => fields([username, password]),
             'requestBody' => fields([username, password]),
             responses => #{
             responses => #{
-                200 => fields([token, version, license]),
+                200 => fields([role, token, version, license]),
                 401 => response_schema(401)
                 401 => response_schema(401)
             },
             },
             security => []
             security => []
@@ -219,14 +219,16 @@ login(post, #{body := Params}) ->
     Username = maps:get(<<"username">>, Params),
     Username = maps:get(<<"username">>, Params),
     Password = maps:get(<<"password">>, Params),
     Password = maps:get(<<"password">>, Params),
     case emqx_dashboard_admin:sign_token(Username, Password) of
     case emqx_dashboard_admin:sign_token(Username, Password) of
-        {ok, Token} ->
+        {ok, Role, Token} ->
             ?SLOG(info, #{msg => "dashboard_login_successful", username => Username}),
             ?SLOG(info, #{msg => "dashboard_login_successful", username => Username}),
             Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
             Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
-            {200, #{
-                token => Token,
-                version => Version,
-                license => #{edition => emqx_release:edition()}
-            }};
+            {200,
+                filter_result(#{
+                    role => Role,
+                    token => Token,
+                    version => Version,
+                    license => #{edition => emqx_release:edition()}
+                })};
         {error, R} ->
         {error, R} ->
             ?SLOG(info, #{msg => "dashboard_login_failed", username => Username, reason => R}),
             ?SLOG(info, #{msg => "dashboard_login_failed", username => Username, reason => R}),
             {401, ?BAD_USERNAME_OR_PWD, <<"Auth failed">>}
             {401, ?BAD_USERNAME_OR_PWD, <<"Auth failed">>}

+ 7 - 1
apps/emqx_dashboard/src/emqx_dashboard_swagger.erl

@@ -28,7 +28,7 @@
 -export([error_codes/1, error_codes/2]).
 -export([error_codes/1, error_codes/2]).
 -export([file_schema/1]).
 -export([file_schema/1]).
 -export([base_path/0]).
 -export([base_path/0]).
--export([relative_uri/1]).
+-export([relative_uri/1, get_relative_uri/1]).
 -export([compose_filters/2]).
 -export([compose_filters/2]).
 
 
 -export([
 -export([
@@ -212,6 +212,12 @@ base_path() ->
 relative_uri(Uri) ->
 relative_uri(Uri) ->
     base_path() ++ Uri.
     base_path() ++ Uri.
 
 
+-spec get_relative_uri(uri_string:uri_string()) -> {ok, uri_string:uri_string()} | error.
+get_relative_uri(<<?BASE_PATH, Path/binary>>) ->
+    {ok, Path};
+get_relative_uri(_Path) ->
+    error.
+
 file_schema(FileName) ->
 file_schema(FileName) ->
     #{
     #{
         content => #{
         content => #{

+ 2 - 2
apps/emqx_dashboard/src/emqx_dashboard_token.erl

@@ -56,7 +56,7 @@
 %%--------------------------------------------------------------------
 %%--------------------------------------------------------------------
 %% jwt function
 %% jwt function
 -spec sign(User :: dashboard_user(), Password :: binary()) ->
 -spec sign(User :: dashboard_user(), Password :: binary()) ->
-    {ok, Token :: binary()} | {error, Reason :: term()}.
+    {ok, dashboard_user_role(), Token :: binary()} | {error, Reason :: term()}.
 sign(User, Password) ->
 sign(User, Password) ->
     do_sign(User, Password).
     do_sign(User, Password).
 
 
@@ -120,7 +120,7 @@ do_sign(#?ADMIN{username = Username} = User, Password) ->
     Role = emqx_dashboard_admin:role(User),
     Role = emqx_dashboard_admin:role(User),
     JWTRec = format(Token, Username, Role, ExpTime),
     JWTRec = format(Token, Username, Role, ExpTime),
     _ = mria:transaction(?DASHBOARD_SHARD, fun mnesia:write/1, [JWTRec]),
     _ = mria:transaction(?DASHBOARD_SHARD, fun mnesia:write/1, [JWTRec]),
-    {ok, Token}.
+    {ok, Role, Token}.
 
 
 -spec do_verify(_, Token :: binary()) ->
 -spec do_verify(_, Token :: binary()) ->
     Result ::
     Result ::

+ 3 - 2
apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl

@@ -120,6 +120,7 @@ t_rest_api(_Config) ->
     ?assertEqual(
     ?assertEqual(
         [
         [
             filter_req(#{
             filter_req(#{
+                <<"backend">> => <<"local">>,
                 <<"username">> => <<"admin">>,
                 <<"username">> => <<"admin">>,
                 <<"description">> => <<"administrator">>,
                 <<"description">> => <<"administrator">>,
                 <<"role">> => ?ROLE_SUPERUSER
                 <<"role">> => ?ROLE_SUPERUSER
@@ -269,7 +270,7 @@ auth_header_() ->
     auth_header_(<<"admin">>, <<"public">>).
     auth_header_(<<"admin">>, <<"public">>).
 
 
 auth_header_(Username, Password) ->
 auth_header_(Username, Password) ->
-    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(Username, Password),
     {"Authorization", "Bearer " ++ binary_to_list(Token)}.
     {"Authorization", "Bearer " ++ binary_to_list(Token)}.
 
 
 api_path(Parts) ->
 api_path(Parts) ->
@@ -286,6 +287,6 @@ filter_req(Req) ->
 -else.
 -else.
 
 
 filter_req(Req) ->
 filter_req(Req) ->
-    maps:without([role, <<"role">>], Req).
+    maps:without([role, <<"role">>, backend, <<"backend">>], Req).
 
 
 -endif.
 -endif.

+ 4 - 3
apps/emqx_dashboard/test/emqx_dashboard_admin_SUITE.erl

@@ -174,15 +174,16 @@ t_clean_token(_) ->
     Password = <<"public_www1">>,
     Password = <<"public_www1">>,
     NewPassword = <<"public_www2">>,
     NewPassword = <<"public_www2">>,
     {ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, <<"desc">>),
     {ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, <<"desc">>),
-    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
-    FakeReq = #{method => <<"GET">>},
+    {ok, _, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    FakePath = erlang:list_to_binary(emqx_dashboard_swagger:relative_uri("/fake")),
+    FakeReq = #{method => <<"GET">>, path => FakePath},
     {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     %% change password
     %% change password
     {ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword),
     {ok, _} = emqx_dashboard_admin:change_password(Username, Password, NewPassword),
     timer:sleep(5),
     timer:sleep(5),
     {error, not_found} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     {error, not_found} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     %% remove user
     %% remove user
-    {ok, Token2} = emqx_dashboard_admin:sign_token(Username, NewPassword),
+    {ok, _, Token2} = emqx_dashboard_admin:sign_token(Username, NewPassword),
     {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token2),
     {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token2),
     {ok, _} = emqx_dashboard_admin:remove_user(Username),
     {ok, _} = emqx_dashboard_admin:remove_user(Username),
     timer:sleep(5),
     timer:sleep(5),

+ 1 - 1
apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl

@@ -116,7 +116,7 @@ auth_header(Username) ->
     auth_header(Username, <<"public">>).
     auth_header(Username, <<"public">>).
 
 
 auth_header(Username, Password) ->
 auth_header(Username, Password) ->
-    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(Username, Password),
     {"Authorization", "Bearer " ++ binary_to_list(Token)}.
     {"Authorization", "Bearer " ++ binary_to_list(Token)}.
 
 
 multipart_formdata_request(Url, Fields, Files) ->
 multipart_formdata_request(Url, Fields, Files) ->

+ 1 - 1
apps/emqx_dashboard/test/emqx_dashboard_haproxy_SUITE.erl

@@ -55,7 +55,7 @@ t_status(_Config) ->
         [binary, {active, false}, {packet, raw}]
         [binary, {active, false}, {packet, raw}]
     ),
     ),
     ok = gen_tcp:send(Socket, ranch_proxy_header:header(ProxyInfo)),
     ok = gen_tcp:send(Socket, ranch_proxy_header:header(ProxyInfo)),
-    {ok, Token} = emqx_dashboard_admin:sign_token(<<"admin">>, <<"public">>),
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(<<"admin">>, <<"public">>),
     ok = gen_tcp:send(
     ok = gen_tcp:send(
         Socket,
         Socket,
         "GET /status HTTP/1.1\r\n"
         "GET /status HTTP/1.1\r\n"

+ 14 - 5
apps/emqx_dashboard_rbac/src/emqx_dashboard_rbac.erl

@@ -12,9 +12,15 @@
 %%=====================================================================
 %%=====================================================================
 %% API
 %% API
 check_rbac(Req, Extra) ->
 check_rbac(Req, Extra) ->
-    Method = cowboy_req:method(Req),
     Role = role(Extra),
     Role = role(Extra),
-    check_rbac_with_method(Role, Method).
+    Method = cowboy_req:method(Req),
+    AbsPath = cowboy_req:path(Req),
+    case emqx_dashboard_swagger:get_relative_uri(AbsPath) of
+        {ok, Path} ->
+            check_rbac(Role, Method, Path);
+        _ ->
+            false
+    end.
 
 
 %% For compatibility
 %% For compatibility
 role(#?ADMIN{role = undefined}) ->
 role(#?ADMIN{role = undefined}) ->
@@ -35,11 +41,14 @@ valid_role(Role) ->
             {error, <<"Role does not exist">>}
             {error, <<"Role does not exist">>}
     end.
     end.
 %% ===================================================================
 %% ===================================================================
-check_rbac_with_method(?ROLE_SUPERUSER, _) ->
+check_rbac(?ROLE_SUPERUSER, _, _) ->
+    true;
+check_rbac(?ROLE_VIEWER, <<"GET">>, _) ->
     true;
     true;
-check_rbac_with_method(?ROLE_VIEWER, <<"GET">>) ->
+%% this API is a special case
+check_rbac(?ROLE_VIEWER, <<"POST">>, <<"/logout">>) ->
     true;
     true;
-check_rbac_with_method(_, _) ->
+check_rbac(_, _, _) ->
     false.
     false.
 
 
 role_list() ->
 role_list() ->

+ 14 - 2
apps/emqx_dashboard_rbac/test/emqx_dashboard_rbac_SUITE.erl

@@ -135,8 +135,9 @@ t_clean_token(_) ->
     Desc = <<"desc">>,
     Desc = <<"desc">>,
     NewDesc = <<"new desc">>,
     NewDesc = <<"new desc">>,
     {ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, Desc),
     {ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, Desc),
-    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
-    FakeReq = #{method => <<"GET">>},
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    FakePath = erlang:list_to_binary(emqx_dashboard_swagger:relative_uri("/fake")),
+    FakeReq = #{method => <<"GET">>, path => FakePath},
     {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     %% change description
     %% change description
     {ok, _} = emqx_dashboard_admin:update_user(Username, ?ROLE_SUPERUSER, NewDesc),
     {ok, _} = emqx_dashboard_admin:update_user(Username, ?ROLE_SUPERUSER, NewDesc),
@@ -148,6 +149,17 @@ t_clean_token(_) ->
     {error, not_found} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     {error, not_found} = emqx_dashboard_admin:verify_token(FakeReq, Token),
     ok.
     ok.
 
 
+t_login_out(_) ->
+    Username = <<"admin_token">>,
+    Password = <<"public_www1">>,
+    Desc = <<"desc">>,
+    {ok, _} = emqx_dashboard_admin:add_user(Username, Password, ?ROLE_SUPERUSER, Desc),
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    FakePath = erlang:list_to_binary(emqx_dashboard_swagger:relative_uri("/logout")),
+    FakeReq = #{method => <<"POST">>, path => FakePath},
+    {ok, Username} = emqx_dashboard_admin:verify_token(FakeReq, Token),
+    ok.
+
 add_default_superuser() ->
 add_default_superuser() ->
     {ok, _NewUser} = emqx_dashboard_admin:add_user(
     {ok, _NewUser} = emqx_dashboard_admin:add_user(
         ?DEFAULT_SUPERUSER,
         ?DEFAULT_SUPERUSER,

+ 2 - 1
apps/emqx_dashboard_sso/src/emqx_dashboard_sso.erl

@@ -5,6 +5,7 @@
 -module(emqx_dashboard_sso).
 -module(emqx_dashboard_sso).
 
 
 -include_lib("hocon/include/hoconsc.hrl").
 -include_lib("hocon/include/hoconsc.hrl").
+-include_lib("emqx_dashboard/include/emqx_dashboard.hrl").
 
 
 -export([
 -export([
     hocon_ref/1,
     hocon_ref/1,
@@ -38,7 +39,7 @@
     {ok, NewState :: state()} | {error, Reason :: term()}.
     {ok, NewState :: state()} | {error, Reason :: term()}.
 -callback destroy(State :: state()) -> ok.
 -callback destroy(State :: state()) -> ok.
 -callback login(request(), State :: state()) ->
 -callback login(request(), State :: state()) ->
-    {ok, Token :: binary()} | {error, Reason :: term()}.
+    {ok, dashboard_user_role(), Token :: binary()} | {error, Reason :: term()}.
 
 
 %%------------------------------------------------------------------------------
 %%------------------------------------------------------------------------------
 %% Callback Interface
 %% Callback Interface

+ 3 - 2
apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl

@@ -83,7 +83,7 @@ schema("/sso/login/:backend") ->
             parameters => backend_name_in_path(),
             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([role, token, version, license]),
                 401 => response_schema(401),
                 401 => response_schema(401),
                 404 => response_schema(404)
                 404 => response_schema(404)
             },
             },
@@ -148,10 +148,11 @@ login(post, #{bindings := #{backend := Backend}, body := Sign}) ->
         State ->
         State ->
             Provider = emqx_dashboard_sso:provider(Backend),
             Provider = emqx_dashboard_sso:provider(Backend),
             case emqx_dashboard_sso:login(Provider, Sign, State) of
             case emqx_dashboard_sso:login(Provider, Sign, State) of
-                {ok, 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())),
                     Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
                     {200, #{
                     {200, #{
+                        role => Role,
                         token => Token,
                         token => Token,
                         version => Version,
                         version => Version,
                         license => #{edition => emqx_release:edition()}
                         license => #{edition => emqx_release:edition()}

+ 13 - 17
apps/emqx_dashboard_sso/src/emqx_dashboard_sso_ldap.erl

@@ -106,23 +106,19 @@ ensure_bind_password(Config) ->
     Config#{bind_password => <<"${password}">>}.
     Config#{bind_password => <<"${password}">>}.
 
 
 adjust_ldap_fields(Fields) ->
 adjust_ldap_fields(Fields) ->
-    adjust_ldap_fields(Fields, []).
-
-adjust_ldap_fields([{filter, Meta} | T], Acc) ->
-    adjust_ldap_fields(
-        T,
-        [
-            {filter, Meta#{
-                default => <<"(objectClass=user)">>,
-                example => <<"(objectClass=user)">>
-            }}
-            | Acc
-        ]
-    );
-adjust_ldap_fields([Any | T], Acc) ->
-    adjust_ldap_fields(T, [Any | Acc]);
-adjust_ldap_fields([], Acc) ->
-    lists:reverse(Acc).
+    lists:map(fun adjust_ldap_field/1, Fields).
+
+adjust_ldap_field({base_dn, Meta}) ->
+    {base_dn, maps:remove(example, Meta)};
+adjust_ldap_field({filter, Meta}) ->
+    Default = <<"(& (objectClass=person) (uid=${username}))">>,
+    {filter, Meta#{
+        desc => ?DESC(filter),
+        default => Default,
+        example => Default
+    }};
+adjust_ldap_field(Any) ->
+    Any.
 
 
 login(
 login(
     #{<<"username">> := Username} = Req,
     #{<<"username">> := Username} = Req,

+ 1 - 1
apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl

@@ -101,7 +101,7 @@ t_first_login(_) ->
     },
     },
     %% this API is authorization-free
     %% this API is authorization-free
     {ok, 200, Result} = request_without_authorization(post, Path, Req),
     {ok, 200, Result} = request_without_authorization(post, Path, Req),
-    ?assertMatch(#{license := _, token := _}, decode_json(Result)),
+    ?assertMatch(#{license := _, token := _, role := ?ROLE_VIEWER}, decode_json(Result)),
     ?assertMatch(
     ?assertMatch(
         [#?ADMIN{username = ?SSO_USERNAME(ldap, ?LDAP_USER)}],
         [#?ADMIN{username = ?SSO_USERNAME(ldap, ?LDAP_USER)}],
         emqx_dashboard_admin:lookup_user(ldap, ?LDAP_USER)
         emqx_dashboard_admin:lookup_user(ldap, ?LDAP_USER)

+ 1 - 1
apps/emqx_management/test/emqx_mgmt_api_plugins_SUITE.erl

@@ -252,7 +252,7 @@ describe_plugins(Name) ->
     end.
     end.
 
 
 install_plugin(FilePath) ->
 install_plugin(FilePath) ->
-    {ok, Token} = emqx_dashboard_admin:sign_token(<<"admin">>, <<"public">>),
+    {ok, _Role, Token} = emqx_dashboard_admin:sign_token(<<"admin">>, <<"public">>),
     Path = emqx_mgmt_api_test_util:api_path(["plugins", "install"]),
     Path = emqx_mgmt_api_test_util:api_path(["plugins", "install"]),
     case
     case
         emqx_mgmt_api_test_util:upload_request(
         emqx_mgmt_api_test_util:upload_request(

+ 7 - 0
rel/i18n/emqx_dashboard_sso_ldap.hocon

@@ -8,4 +8,11 @@ query_timeout.desc:
 
 
 query_timeout.label:
 query_timeout.label:
 """Query Timeout"""
 """Query Timeout"""
+
+filter.desc:
+"""The filter for matching users in LDAP is by default `(&(objectClass=person)(uid=${username}))`. For Active Directory, it should be set to `(&(objectClass=user)(sAMAccountName=${username}))` by default. Please refer to [LDAP Filters](https://ldap.com/ldap-filters/) for more details."""
+
+filter.label:
+"""Filter"""
+
 }
 }