Преглед изворни кода

refactor(gw): deps on emqx_dasboard_swagger

JianBo He пре 4 година
родитељ
комит
f033fad7b3

+ 9 - 0
apps/emqx_authn/src/emqx_authn_api.erl

@@ -65,6 +65,15 @@
         , response_users_example/0
         ]).
 
+%% export these funcs for gateway
+-export([ list_users/3
+        , add_user/3
+        , delete_user/3
+        , find_user/3
+        , update_user/4
+        , serialize_error/1
+        ]).
+
 api_spec() ->
     emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
 

+ 250 - 76
apps/emqx_gateway/src/emqx_gateway_api_authn.erl

@@ -18,21 +18,34 @@
 
 -behaviour(minirest_api).
 
+-include_lib("typerefl/include/types.hrl").
+
+-define(BAD_REQUEST, 'BAD_REQUEST').
+-define(NOT_FOUND, 'NOT_FOUND').
+-define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR').
+
+-import(hoconsc, [mk/2, ref/2]).
+-import(emqx_dashboard_swagger, [error_codes/2]).
+
 -import(emqx_gateway_http,
         [ return_http_error/2
-        , schema_bad_request/0
-        , schema_not_found/0
-        , schema_internal_error/0
-        , schema_no_content/0
         , with_gateway/2
+        , with_authn/2
         , checks/2
         ]).
 
-%% minirest behaviour callbacks
--export([api_spec/0]).
+%% minirest/dashbaord_swagger behaviour callbacks
+-export([ api_spec/0
+        , paths/0
+        , schema/1
+        ]).
 
 %% http handlers
--export([authn/2]).
+-export([ authn/2
+        , users/2
+        , users_insta/2
+        , import_users/2
+        ]).
 
 %% internal export for emqx_gateway_api_listeners module
 -export([schema_authn/0]).
@@ -42,10 +55,13 @@
 %%--------------------------------------------------------------------
 
 api_spec() ->
-    {metadata(apis()), []}.
+    emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
 
-apis() ->
-    [ {"/gateway/:name/authentication", authn}
+paths() ->
+    [ "/gateway/:name/authentication"
+    , "/gateway/:name/authentication/users"
+    , "/gateway/:name/authentication/users/:uid"
+    , "/gateway/:name/authentication/import_users"
     ].
 
 %%--------------------------------------------------------------------
@@ -83,87 +99,245 @@ authn(delete, #{bindings := #{name := Name0}}) ->
         {204}
     end).
 
+users(get, #{bindings := #{name := Name0}, query_string := Qs}) ->
+    with_authn(Name0, fun(_GwName, #{id := AuthId,
+                                     chain_name := ChainName}) ->
+        emqx_authn_api:list_users(ChainName, AuthId, page_pramas(Qs))
+    end);
+users(post, #{bindings := #{name := Name0},
+              body := Body}) ->
+    with_authn(Name0, fun(_GwName, #{id := AuthId,
+                                     chain_name := ChainName}) ->
+        emqx_authn_api:add_user(ChainName, AuthId, Body)
+    end).
+
+users_insta(get, #{bindings := #{name := Name0, uid := UserId}}) ->
+    with_authn(Name0, fun(_GwName, #{id := AuthId,
+                                     chain_name := ChainName}) ->
+        emqx_authn_api:find_user(ChainName, AuthId, UserId)
+    end);
+users_insta(put, #{bindings := #{name := Name0, uid := UserId},
+                   body := Body}) ->
+    with_authn(Name0, fun(_GwName, #{id := AuthId,
+                                     chain_name := ChainName}) ->
+        emqx_authn_api:update_user(ChainName, AuthId, UserId, Body)
+    end);
+users_insta(delete, #{bindings := #{name := Name0, uid := UserId}}) ->
+    with_authn(Name0, fun(_GwName, #{id := AuthId,
+                                     chain_name := ChainName}) ->
+        emqx_authn_api:delete_user(ChainName, AuthId, UserId)
+    end).
+
+import_users(post, #{bindings := #{name := Name0},
+                     body := Body}) ->
+    with_authn(Name0, fun(_GwName, #{id := AuthId,
+                                     chain_name := ChainName}) ->
+        case maps:get(<<"filename">>, Body, undefined) of
+            undefined ->
+                emqx_authn_api:serialize_error({missing_parameter, filename});
+            Filename ->
+                case emqx_authentication:import_users(
+                       ChainName, AuthId, Filename) of
+                    ok -> {204};
+                    {error, Reason} ->
+                        emqx_authn_api:serialize_error(Reason)
+                end
+        end
+    end).
+
+%%--------------------------------------------------------------------
+%% Utils
+
+page_pramas(Qs) ->
+    maps:with([<<"page">>, <<"limit">>], Qs).
+
 %%--------------------------------------------------------------------
 %% Swagger defines
 %%--------------------------------------------------------------------
 
-metadata(APIs) ->
-    metadata(APIs, []).
-metadata([], APIAcc) ->
-    lists:reverse(APIAcc);
-metadata([{Path, Fun}|More], APIAcc) ->
-    Methods = [get, post, put, delete, patch],
-    Mds = lists:foldl(fun(M, Acc) ->
-              try
-                  Acc#{M => swagger(Path, M)}
-              catch
-                  error : function_clause ->
-                      Acc
-              end
-          end, #{}, Methods),
-    metadata(More, [{Path, Mds, Fun} | APIAcc]).
-
-swagger("/gateway/:name/authentication", get) ->
-    #{ description => <<"Get the gateway authentication">>
-     , parameters => params_gateway_name_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"200">> => schema_authn()
-         , <<"204">> => schema_no_content()
-         }
+
+schema("/gateway/:name/authentication") ->
+    #{ 'operationId' => authn,
+       get =>
+         #{ description => <<"Get the gateway authentication">>
+          , parameters => params_gateway_name_in_path()
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                    <<"Ineternal Server Error">>)
+               , 200 => schema_authn()
+               , 204 => <<"Authentication does not initiated">>
+               }
+          },
+       put =>
+         #{ description => <<"Update authentication for the gateway">>
+          , parameters => params_gateway_name_in_path()
+          , requestBody => schema_authn()
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+               , 204 => <<"Updated">> %% XXX: ??? return the updated object
+               }
+          },
+       post =>
+         #{ description => <<"Add authentication for the gateway">>
+          , parameters => params_gateway_name_in_path()
+          , requestBody => schema_authn()
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+               , 204 => <<"Added">>
+               }
+          },
+       delete =>
+         #{ description => <<"Remove the gateway authentication">>
+          , parameters => params_gateway_name_in_path()
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+               , 204 => <<"Deleted">>
+              }
+          }
      };
-swagger("/gateway/:name/authentication", put) ->
-    #{ description => <<"Update authentication for the gateway">>
-     , parameters => params_gateway_name_in_path()
-     , requestBody => schema_authn()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
+schema("/gateway/:name/authentication/users") ->
+    #{ 'operationId' => users
+     , get =>
+         #{ description => <<"Get the users for the authentication">>
+          , parameters => params_gateway_name_in_path()
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+               , 200 => emqx_dashboard_swagger:schema_with_example(
+                          ref(emqx_authn_api, response_user),
+                          emqx_authn_api:response_user_examples())
+              }
+          },
+       post =>
+         #{ description => <<"Add user for the authentication">>
+          , parameters => params_gateway_name_in_path()
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+               , 201 => emqx_dashboard_swagger:schema_with_example(
+                          ref(emqx_authn_api, response_user),
+                          emqx_authn_api:response_user_examples())
+              }
+          }
      };
-swagger("/gateway/:name/authentication", post) ->
-    #{ description => <<"Add authentication for the gateway">>
-     , parameters => params_gateway_name_in_path()
-     , requestBody => schema_authn()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
+schema("/gateway/:name/authentication/users/:uid") ->
+    #{ 'operationId' => users_insta
+      , get =>
+          #{ description => <<"Get user info from the gateway "
+                              "authentication">>
+           , parameters => params_gateway_name_in_path() ++
+                           params_userid_in_path()
+           , responses =>
+               #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+                , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+                , 500 => error_codes([?INTERNAL_ERROR],
+                                     <<"Ineternal Server Error">>)
+                , 200 => emqx_dashboard_swagger:schema_with_example(
+                           ref(emqx_authn_api, response_user),
+                           emqx_authn_api:response_user_examples())
+                }
+           },
+        put =>
+          #{ description => <<"Update the user info for the gateway "
+                              "authentication">>
+           , parameters => params_gateway_name_in_path() ++
+                           params_userid_in_path()
+           , responses =>
+               #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+                , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+                , 500 => error_codes([?INTERNAL_ERROR],
+                                     <<"Ineternal Server Error">>)
+                , 200 => emqx_dashboard_swagger:schema_with_example(
+                           ref(emqx_authn_api, response_user),
+                           emqx_authn_api:response_user_examples())
+                }
+           },
+        delete =>
+          #{ description => <<"Delete the user for the gateway "
+                              "authentication">>
+           , parameters => params_gateway_name_in_path() ++
+                           params_userid_in_path() ++
+                           params_paging_in_qs()
+           , responses =>
+               #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+                , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+                , 500 => error_codes([?INTERNAL_ERROR],
+                                     <<"Ineternal Server Error">>)
+                , 200 => emqx_dashboard_swagger:schema_with_example(
+                           ref(emqx_authn_api, response_user),
+                           emqx_authn_api:response_user_examples())
+                }
+           }
      };
-swagger("/gateway/:name/authentication", delete) ->
-    #{ description => <<"Remove the gateway authentication">>
-     , parameters => params_gateway_name_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
+schema("/gateway/:name/authentication/import_users") ->
+    #{ 'operationId' => import_users
+     , post =>
+         #{ description => <<"Import users into the gateway authentication">>
+          , parameters => params_gateway_name_in_path()
+          , requestBody => emqx_dashboard_swagger:schema_with_examples(
+                             ref(emqx_authn_api, request_import_users),
+                             emqx_authn_api:request_import_users_examples()
+                            )
+          , responses =>
+              #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+               , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+               , 500 => error_codes([?INTERNAL_ERROR],
+                                     <<"Ineternal Server Error">>)
+               %% XXX: Put a hint message into 204 return ?
+               , 204 => <<"Imported">>
+              }
+          }
      }.
 
 %%--------------------------------------------------------------------
 %% params defines
 
 params_gateway_name_in_path() ->
-    [#{ name => name
-      , in => path
-      , schema => #{type => string}
-      , required => true
-      }].
+    [{name,
+      mk(binary(),
+         #{ in => path
+          , desc => <<"Gateway Name">>
+          })}
+    ].
+
+params_userid_in_path() ->
+    [{uid, mk(binary(),
+              #{ in => path
+               , desc => <<"User ID">>
+               })}
+    ].
+
+params_paging_in_qs() ->
+    [{page, mk(integer(),
+               #{ in => query
+                , desc => <<"Page Number">>
+                })},
+     {limit, mk(integer(),
+                #{ in => query
+                 , desc => <<"Page Limit">>
+                 })}
+    ].
 
 %%--------------------------------------------------------------------
 %% schemas
 
 schema_authn() ->
-    #{ description => <<"OK">>
-     , content => #{
-        'application/json' => #{
-            schema => minirest:ref(<<"AuthenticatorInstance">>)
-       }}
-     }.
+    emqx_dashboard_swagger:schema_with_examples(
+      emqx_authn_schema:authenticator_type(),
+      emqx_authn_api:authenticator_examples()
+     ).

+ 289 - 224
apps/emqx_gateway/src/emqx_gateway_api_listeners.erl

@@ -18,20 +18,32 @@
 
 -behaviour(minirest_api).
 
+-include_lib("typerefl/include/types.hrl").
+
+-define(BAD_REQUEST, 'BAD_REQUEST').
+-define(NOT_FOUND, 'NOT_FOUND').
+-define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR').
+
+-import(hoconsc, [mk/2, ref/1, ref/2]).
+-import(emqx_dashboard_swagger, [error_codes/2]).
+
 -import(emqx_gateway_http,
         [ return_http_error/2
-        , schema_bad_request/0
-        , schema_not_found/0
-        , schema_internal_error/0
-        , schema_no_content/0
         , with_gateway/2
         , checks/2
         ]).
 
 -import(emqx_gateway_api_authn, [schema_authn/0]).
 
-%% minirest behaviour callbacks
--export([api_spec/0]).
+%% minirest/dashbaord_swagger behaviour callbacks
+-export([ api_spec/0
+        , paths/0
+        , schema/1
+        ]).
+
+-export([ roots/0
+        , fields/1
+        ]).
 
 %% http handlers
 -export([ listeners/2
@@ -44,12 +56,12 @@
 %%--------------------------------------------------------------------
 
 api_spec() ->
-    {metadata(apis()), []}.
+    emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
 
-apis() ->
-    [ {"/gateway/:name/listeners", listeners}
-    , {"/gateway/:name/listeners/:id", listeners_insta}
-    , {"/gateway/:name/listeners/:id/authentication", listeners_insta_authn}
+paths() ->
+    [ "/gateway/:name/listeners"
+    , "/gateway/:name/listeners/:id"
+    , "/gateway/:name/listeners/:id/authentication"
     ].
 
 %%--------------------------------------------------------------------
@@ -149,219 +161,228 @@ listeners_insta_authn(delete, #{bindings := #{name := Name0,
 %% Swagger defines
 %%--------------------------------------------------------------------
 
-metadata(APIs) ->
-    metadata(APIs, []).
-metadata([], APIAcc) ->
-    lists:reverse(APIAcc);
-metadata([{Path, Fun}|More], APIAcc) ->
-    Methods = [get, post, put, delete, patch],
-    Mds = lists:foldl(fun(M, Acc) ->
-              try
-                  Acc#{M => swagger(Path, M)}
-              catch
-                  error : function_clause ->
-                      Acc
-              end
-          end, #{}, Methods),
-    metadata(More, [{Path, Mds, Fun} | APIAcc]).
-
-swagger("/gateway/:name/listeners", get) ->
-    #{ description => <<"Get the gateway listeners">>
-     , parameters => params_gateway_name_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"200">> => schema_listener_list()
-         }
-     };
-swagger("/gateway/:name/listeners", post) ->
-    #{ description => <<"Create the gateway listener">>
-     , parameters => params_gateway_name_in_path()
-     , requestBody => schema_listener()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"200">> => schema_listener_list()
-         }
-     };
-swagger("/gateway/:name/listeners/:id", get) ->
-    #{ description => <<"Get the gateway listener configurations">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"200">> => schema_listener()
-         }
-      };
-swagger("/gateway/:name/listeners/:id", delete) ->
-    #{ description => <<"Delete the gateway listener">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
-      };
-swagger("/gateway/:name/listeners/:id", put) ->
-    #{ description => <<"Update the gateway listener">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , requestBody => schema_listener()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"200">> => schema_no_content()
-         }
+schema("/gateway/:name/listeners") ->
+    #{ 'operationId' => listeners,
+       get =>
+         #{ description => <<"Get the gateway listeners">>
+          , parameters => params_gateway_name_in_path()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 200 => emqx_dashboard_swagger:schema_with_examples(
+                         hoconsc:array(ref(listener)),
+                         examples_listener_list())
+              }
+          },
+       post =>
+         #{ description => <<"Create the gateway listener">>
+          , parameters => params_gateway_name_in_path()
+          , requestBody => emqx_dashboard_swagger:schema_with_examples(
+                             ref(listener),
+                             examples_listener())
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 204 => <<"Created">>
+              }
+          }
      };
-swagger("/gateway/:name/listeners/:id/authentication", get) ->
-    #{ description => <<"Get the listener's authentication info">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"200">> => schema_authn()
-         , <<"204">> => schema_no_content()
-         }
+schema("/gateway/:name/listeners/:id") ->
+    #{ 'operationId' => listeners_insta,
+       get =>
+         #{ description => <<"Get the gateway listener configurations">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 200 => emqx_dashboard_swagger:schema_with_examples(
+                         ref(listener),
+                         examples_listener())
+              }
+           },
+       delete =>
+         #{ description => <<"Delete the gateway listener">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 204 => <<"Deleted">>
+              }
+           },
+       put =>
+         #{ description => <<"Update the gateway listener">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , requestBody => emqx_dashboard_swagger:schema_with_examples(
+                             ref(listener),
+                             examples_listener())
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 200 => <<"Updated">>
+              }
+          }
      };
-swagger("/gateway/:name/listeners/:id/authentication", post) ->
-    #{ description => <<"Add authentication for the listener">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , requestBody => schema_authn()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
-     };
-swagger("/gateway/:name/listeners/:id/authentication", put) ->
-    #{ description => <<"Update authentication for the listener">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , requestBody => schema_authn()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
-     };
-swagger("/gateway/:name/listeners/:id/authentication", delete) ->
-    #{ description => <<"Remove authentication for the listener">>
-     , parameters => params_gateway_name_in_path()
-                     ++ params_listener_id_in_path()
-     , responses =>
-        #{ <<"400">> => schema_bad_request()
-         , <<"404">> => schema_not_found()
-         , <<"500">> => schema_internal_error()
-         , <<"204">> => schema_no_content()
-         }
+schema("/gateway/:name/listeners/:id/authentication") ->
+    #{ 'operationId' => listeners_insta_authn,
+       get =>
+         #{ description => <<"Get the listener's authentication info">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 200 => schema_authn()
+              , 204 => <<"Authentication does not initiated">>
+              }
+          },
+       post =>
+         #{ description => <<"Add authentication for the listener">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , requestBody => schema_authn()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 204 => <<"Added">>
+              }
+          },
+       put =>
+         #{ description => <<"Update authentication for the listener">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , requestBody => schema_authn()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 204 => <<"Updated">>
+              }
+          },
+       delete =>
+         #{ description => <<"Remove authentication for the listener">>
+          , parameters => params_gateway_name_in_path()
+                          ++ params_listener_id_in_path()
+          , responses =>
+             #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
+              , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
+              , 500 => error_codes([?INTERNAL_ERROR],
+                                   <<"Ineternal Server Error">>)
+              , 204 => <<"Deleted">>
+              }
+          }
      }.
 
 %%--------------------------------------------------------------------
 %% params defines
 
 params_gateway_name_in_path() ->
-    [#{ name => name
-      , in => path
-      , schema => #{type => string}
-      , required => true
-      }].
+    [{name,
+      mk(binary(),
+         #{ in => path
+          , desc => <<"Gateway Name">>
+          })}
+    ].
 
 params_listener_id_in_path() ->
-    [#{ name => id
-      , in => path
-      , schema => #{type => string}
-      , required => true
-      }].
+    [{id,
+      mk(binary(),
+         #{ in => path
+          , desc => <<"Listener ID">>
+          })}
+    ].
 
 %%--------------------------------------------------------------------
 %% schemas
 
-schema_listener_list() ->
-    emqx_mgmt_util:array_schema(
-      #{ type => object
-       , properties => properties_listener()
-       },
-      <<"Listener list">>
-     ).
-
-schema_listener() ->
-    emqx_mgmt_util:schema(
-      #{ type => object
-       , properties => properties_listener()
-       }
-     ).
-
-%%--------------------------------------------------------------------
-%% properties
-
-properties_listener() ->
-    emqx_mgmt_util:properties(
-      raw_properties_common_listener() ++
-      [ {tcp, object, raw_properties_tcp_opts()}
-      , {ssl, object, raw_properties_ssl_opts()}
-      , {udp, object, raw_properties_udp_opts()}
-      , {dtls, object, raw_properties_dtls_opts()}
-      ]).
-
-raw_properties_tcp_opts() ->
-    [ {active_n, integer, <<>>}
-    , {backlog, integer, <<>>}
-    , {buffer, string, <<>>}
-    , {recbuf, string, <<>>}
-    , {sndbuf, string, <<>>}
-    , {high_watermark, string, <<>>}
-    , {nodelay, boolean, <<>>}
-    , {reuseaddr, boolean, <<>>}
-    , {send_timeout, string, <<>>}
-    , {send_timeout_close, boolean, <<>>}
-    ].
-
-raw_properties_ssl_opts() ->
-    [ {cacertfile, string, <<>>}
-    , {certfile, string, <<>>}
-    , {keyfile, string, <<>>}
-    , {verify, string, <<>>}
-    , {fail_if_no_peer_cert, boolean, <<>>}
-    , {server_name_indication, boolean, <<>>}
-    , {depth, integer, <<>>}
-    , {password, string, <<>>}
-    , {handshake_timeout, string, <<>>}
-    , {versions, {array, string}, <<>>}
-    , {ciphers, {array, string}, <<>>}
-    , {user_lookup_fun, string, <<>>}
-    , {reuse_sessions, boolean, <<>>}
-    , {secure_renegotiate, boolean, <<>>}
-    , {honor_cipher_order, boolean, <<>>}
-    , {dhfile, string, <<>>}
-    ].
-
-raw_properties_udp_opts() ->
-    [ {active_n, integer, <<>>}
-    , {buffer, string, <<>>}
-    , {recbuf, string, <<>>}
-    , {sndbuf, string, <<>>}
-    , {reuseaddr, boolean, <<>>}
+roots() ->
+    [ listener
     ].
 
-raw_properties_dtls_opts() ->
+fields(listener) ->
+    common_listener_opts() ++
+    [ {tcp,
+       mk(ref(tcp_listener_opts),
+          #{ nullable => {true, recursively}
+           , desc => <<"The tcp socket options for tcp or ssl listener">>
+           })}
+    , {ssl,
+       mk(ref(ssl_listener_opts),
+          #{ nullable => {true, recursively}
+           , desc => <<"The ssl socket options for ssl listener">>
+           })}
+    , {udp,
+       mk(ref(udp_listener_opts),
+          #{ nullable => {true, recursively}
+           , desc => <<"The udp socket options for udp or dtls listener">>
+           })}
+    , {dtls,
+       mk(ref(dtls_listener_opts),
+          #{ nullable => {true, recursively}
+           , desc => <<"The dtls socket options for dtls listener">>
+           })}
+    ];
+fields(tcp_listener_opts) ->
+    [ {active_n, mk(integer(), #{})}
+    , {backlog, mk(integer(), #{})}
+    , {buffer, mk(binary(), #{})}
+    , {recbuf, mk(binary(), #{})}
+    , {sndbuf, mk(binary(), #{})}
+    , {high_watermark, mk(binary(), #{})}
+    , {nodelay, mk(boolean(), #{})}
+    , {reuseaddr, boolean()}
+    , {send_timeout, binary()}
+    , {send_timeout_close, boolean()}
+    ];
+fields(ssl_listener_opts) ->
+    [ {cacertfile, binary()}
+    , {certfile, binary()}
+    , {keyfile, binary()}
+    , {verify, binary()}
+    , {fail_if_no_peer_cert, boolean()}
+    , {server_name_indication, boolean()}
+    , {depth, integer()}
+    , {password, binary()}
+    , {handshake_timeout, binary()}
+    , {versions, hoconsc:array(binary())}
+    , {ciphers, hoconsc:array(binary())}
+    , {user_lookup_fun, binary()}
+    , {reuse_sessions, boolean()}
+    , {secure_renegotiate, boolean()}
+    , {honor_cipher_order, boolean()}
+    , {dhfile, binary()}
+    ];
+fields(udp_listener_opts) ->
+    [ {active_n, integer()}
+    , {buffer, binary()}
+    , {recbuf, binary()}
+    , {sndbuf, binary()}
+    , {reuseaddr, boolean()}
+    ];
+fields(dtls_listener_opts) ->
     Ls = lists_key_without(
       [versions,ciphers,handshake_timeout], 1,
-      raw_properties_ssl_opts()
+      fields(ssl_listener_opts)
      ),
-    [ {versions, {array, string}, <<>>}
-    , {ciphers, {array, string}, <<>>}
+    [ {versions, hoconsc:array(binary())}
+    , {ciphers, hoconsc:array(binary())}
     | Ls].
 
 lists_key_without([], _N, L) ->
@@ -369,23 +390,67 @@ lists_key_without([], _N, L) ->
 lists_key_without([K|Ks], N, L) ->
     lists_key_without(Ks, N, lists:keydelete(K, N, L)).
 
-raw_properties_common_listener() ->
-    [ {enable, boolean, <<"Whether to enable this listener">>}
-    , {id, string, <<"Listener Id">>}
-    , {name, string, <<"Listener name">>}
-    , {type, string,
-       <<"Listener type. Enum: tcp, udp, ssl, dtls">>,
-       [<<"tcp">>, <<"ssl">>, <<"udp">>, <<"dtls">>]}
-    , {running, boolean, <<"Listener running status">>}
-    , {bind, string, <<"Listener bind address or port">>}
-    , {acceptors, integer, <<"Listener acceptors number">>}
-    , {access_rules, {array, string}, <<"Listener Access rules for client">>}
-    , {max_conn_rate, integer, <<"Max connection rate for the listener">>}
-    , {max_connections, integer, <<"Max connections for the listener">>}
-    , {mountpoint, string,
-       <<"The Mounpoint for clients of the listener. "
-         "The gateway-level mountpoint configuration can be overloaded "
-         "when it is not null or empty string">>}
+common_listener_opts() ->
+    [ {enable,
+       mk(boolean(),
+          #{ nullable => true
+           , desc => <<"Whether to enable this listener">>})}
+    , {id,
+       mk(binary(),
+          #{ nullable => true
+           , desc => <<"Listener Id">>})}
+    , {name,
+       mk(binary(),
+          #{ nullable => true
+           , desc => <<"Listener name">>})}
+    , {type,
+       mk(hoconsc:enum([tcp, ssl, udp, dtls]),
+          #{ nullable => true
+           , desc => <<"Listener type. Enum: tcp, udp, ssl, dtls">>})}
+    , {running,
+       mk(boolean(),
+          #{ nullable => true
+           , desc => <<"Listener running status">>})}
+    , {bind,
+       mk(binary(),
+          #{ nullable => true
+           , desc => <<"Listener bind address or port">>})}
+    , {acceptors,
+       mk(integer(),
+          #{ nullable => true
+           , desc => <<"Listener acceptors number">>})}
+    , {access_rules,
+       mk(hoconsc:array(binary()),
+          #{ nullable => true
+           , desc => <<"Listener Access rules for client">>})}
+    , {max_conn_rate,
+       mk(integer(),
+          #{ nullable => true
+           , desc => <<"Max connection rate for the listener">>})}
+    , {max_connections,
+       mk(integer(),
+          #{ nullable => true
+           , desc => <<"Max connections for the listener">>})}
+    , {mountpoint,
+       mk(binary(),
+          #{ nullable => true
+           , desc =>
+<<"The Mounpoint for clients of the listener. "
+  "The gateway-level mountpoint configuration can be overloaded "
+  "when it is not null or empty string">>})}
     %% FIXME:
-    , {authentication, string, <<"NOT-SUPPORTED-NOW">>}
-   ].
+    , {authentication,
+       mk(emqx_authn_schema:authenticator_type(),
+          #{ nullable => {true, recursively}
+           , desc => <<"The authenticatior for this listener">>
+           })}
+    ].
+
+%%--------------------------------------------------------------------
+%% examples
+
+examples_listener_list() ->
+    [examples_listener()].
+
+examples_listener() ->
+    #{id => true}.

+ 27 - 2
apps/emqx_gateway/src/emqx_gateway_http.erl

@@ -53,6 +53,7 @@
 %% Utils for http, swagger, etc.
 -export([ return_http_error/2
         , with_gateway/2
+        , with_authn/2
         , checks/2
         , schema_bad_request/0
         , schema_not_found/0
@@ -159,14 +160,31 @@ remove_listener(ListenerId) ->
 
 -spec authn(gateway_name()) -> map().
 authn(GwName) ->
+    %% XXX: Need append chain-nanme, authenticator-id?
     Path = [gateway, GwName, authentication],
-    emqx_map_lib:jsonable_map(emqx:get_config(Path)).
+    ChainName = emqx_gateway_utils:global_chain(GwName),
+    wrap_chain_name(
+      ChainName,
+      emqx_map_lib:jsonable_map(emqx:get_config(Path))
+     ).
 
 -spec authn(gateway_name(), binary()) -> map().
 authn(GwName, ListenerId) ->
     {_, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId),
     Path = [gateway, GwName, listeners, Type, Name, authentication],
-    emqx_map_lib:jsonable_map(emqx:get_config(Path)).
+    ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name),
+    wrap_chain_name(
+      ChainName,
+      emqx_map_lib:jsonable_map(emqx:get_config(Path))
+     ).
+
+wrap_chain_name(ChainName, Conf) ->
+    case emqx_authentication:list_authenticators(ChainName) of
+        {ok, [#{id := Id} | _]} ->
+            Conf#{chain_name => ChainName, id => Id};
+        _ ->
+            Conf
+    end.
 
 -spec add_authn(gateway_name(), map()) -> ok.
 add_authn(GwName, AuthConf) ->
@@ -303,6 +321,13 @@ codestr(401) -> 'NOT_SUPPORTED_NOW';
 codestr(404) -> 'RESOURCE_NOT_FOUND';
 codestr(500) -> 'UNKNOW_ERROR'.
 
+-spec with_authn(binary(), function()) -> any().
+with_authn(GwName0, Fun) ->
+    with_gateway(GwName0, fun(GwName) ->
+        Authn = emqx_gateway_http:authn(GwName),
+        Fun(GwName, Authn)
+    end).
+
 -spec with_gateway(binary(), function()) -> any().
 with_gateway(GwName0, Fun) ->
     try

+ 3 - 19
apps/emqx_gateway/src/emqx_gateway_insta_sup.erl

@@ -219,23 +219,6 @@ detailed_gateway_info(State) ->
 %% Internal funcs
 %%--------------------------------------------------------------------
 
-%% same with emqx_authentication:global_chain/1
-global_chain(mqtt) ->
-    'mqtt:global';
-global_chain('mqtt-sn') ->
-    'mqtt-sn:global';
-global_chain(coap) ->
-    'coap:global';
-global_chain(lwm2m) ->
-    'lwm2m:global';
-global_chain(stomp) ->
-    'stomp:global';
-global_chain(_) ->
-    'unknown:global'.
-
-listener_chain(GwName, Type, LisName) ->
-    emqx_gateway_utils:listener_id(GwName, Type, LisName).
-
 %% There are two layer authentication configs
 %%       stomp.authn
 %%           /                   \
@@ -266,10 +249,11 @@ do_init_authn([_BadConf|More], Names) ->
 authns(GwName, Config) ->
     Listeners = maps:to_list(maps:get(listeners, Config, #{})),
     lists:append(
-      [ [{listener_chain(GwName, LisType, LisName), authn_conf(Opts)}
+      [ [{emqx_gateway_utils:listener_chain(GwName, LisType, LisName),
+          authn_conf(Opts)}
         || {LisName, Opts} <- maps:to_list(LisNames) ]
       || {LisType, LisNames} <- Listeners])
-    ++ [{global_chain(GwName), authn_conf(Config)}].
+    ++ [{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}].
 
 authn_conf(Conf) ->
     maps:get(authentication, Conf, #{enable => false}).

+ 19 - 0
apps/emqx_gateway/src/emqx_gateway_utils.erl

@@ -34,6 +34,8 @@
         , listener_id/3
         , parse_listener_id/1
         , is_running/2
+        , global_chain/1
+        , listener_chain/3
         ]).
 
 -export([ stringfy/1
@@ -159,6 +161,23 @@ is_running(ListenerId, #{<<"bind">> := ListenOn0}) ->
         false
     end.
 
+%% same with emqx_authentication:global_chain/1
+global_chain(mqtt) ->
+    'mqtt:global';
+global_chain('mqtt-sn') ->
+    'mqtt-sn:global';
+global_chain(coap) ->
+    'coap:global';
+global_chain(lwm2m) ->
+    'lwm2m:global';
+global_chain(stomp) ->
+    'stomp:global';
+global_chain(_) ->
+    'unknown:global'.
+
+listener_chain(GwName, Type, LisName) ->
+    listener_id(GwName, Type, LisName).
+
 bin(A) when is_atom(A) ->
     atom_to_binary(A);
 bin(L) when is_list(L); is_binary(L) ->

+ 1 - 1
apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl

@@ -164,7 +164,7 @@ t_random_test(_) ->
 random_test_body() ->
     Data = generate_random_binary(),
     case catch parse(Data) of
-        {ok, _Msg} -> ok;
+        Msg when is_record(Msg, mqtt_sn_message) -> ok;
         {'EXIT', {Err, _Stack}}
           when Err =:= unkown_message_type;
                Err =:= malformed_message_len;