Selaa lähdekoodia

feat(authz api): support '/authorization/settings' api and update swagger schema

Signed-off-by: zhanghongtong <rory-z@outlook.com>
zhanghongtong 4 vuotta sitten
vanhempi
commit
ef1b617624

+ 66 - 109
apps/emqx_authz/src/emqx_authz_api_schema.erl

@@ -19,17 +19,17 @@
 -export([definitions/0]).
 
 definitions() ->
-    RetruenedRules = #{
+    RetruenedSources = #{
         allOf => [ #{type => object,
                      properties => #{
                         annotations => #{
                             type => object,
-                            required => [id],
+                            required => [status],
                             properties => #{
-                                id => #{
-                                    type => string
-                                },
-                                principal => minirest:ref(<<"principal">>)
+                                status => #{
+                                    type => string,
+                                    example => <<"healthy">>
+                                }
                             }
                         }
                      }
@@ -37,119 +37,76 @@ definitions() ->
                  , minirest:ref(<<"sources">>)
                  ]
     },
-    Rules = #{
-        oneOf => [ minirest:ref(<<"simple_source">>)
-                 % , minirest:ref(<<"connector_redis">>)
+    Sources = #{
+        oneOf => [  minirest:ref(<<"connector_redis">>)
                  ]
     },
-    % ConnectorRedis = #{
-    %     type => object,
-    %     required => [principal, type, enable, config, cmd]
-    %     properties => #{
-    %         principal => minirest:ref(<<"principal">>),
-    %         type => #{
-    %             type => string,
-    %             enum => [<<"redis">>],
-    %             example => <<"redis">>
-    %         },
-    %         enable => #{
-    %             type => boolean,
-    %             example => true
-    %         }
-    %         config => #{
-    %             type => 
-    %         }
-    %     }
-    % }
-    SimpleRule = #{
+    ConnectorRedis= #{
         type => object,
-        required => [principal, permission, action, topics],
+        required => [type, enable, config, cmd],
         properties => #{
-            action => #{
+            type => #{
                 type => string,
-                enum => [<<"publish">>, <<"subscribe">>, <<"all">>],
-                example => <<"publish">>
+                enum => [<<"redis">>],
+                example => <<"redis">>
             },
-            permission => #{
-                type => string,
-                enum => [<<"allow">>, <<"deny">>],
-                example => <<"allow">>
+            enable => #{
+                type => boolean,
+                example => true
             },
-            topics => #{
-                type => array,
-                items => #{
-                    oneOf => [ #{type => string, example => <<"#">>}
-                             , #{type => object,
-                                 required => [eq],
-                                 properties => #{
-                                    eq => #{type => string}
-                                 },
-                                 example => #{eq => <<"#">>}
-                                }
-                             ]
-                }
+            config => #{
+                oneOf => [ #{type => object,
+                             required => [server, redis_type, pool_size, auto_reconnect],
+                             properties => #{
+                                server => #{type => string, example => <<"127.0.0.1:3306">>},
+                                redis_type => #{type => string,
+                                                enum => [<<"single">>],
+                                                example => <<"single">>},
+                                pool_size => #{type => integer},
+                                auto_reconnect => #{type => boolean, example => true},
+                                password => #{type => string},
+                                database => #{type => string, example => mqtt}
+                             }
+                            }
+                         , #{type => object,
+                             required => [servers, redis_type, sentinel, pool_size, auto_reconnect],
+                             properties => #{
+                                servers => #{type => array,
+                                             items => #{type => string,example => <<"127.0.0.1:3306">>}},
+                                redis_type => #{type => string,
+                                                enum => [<<"sentinel">>],
+                                                example => <<"sentinel">>},
+                                sentinel => #{type => string},
+                                pool_size => #{type => integer},
+                                auto_reconnect => #{type => boolean, example => true},
+                                password => #{type => string},
+                                database => #{type => string, example => mqtt}
+                             }
+                            }
+                         , #{type => object,
+                             required => [servers, redis_type, pool_size, auto_reconnect],
+                             properties => #{
+                                servers => #{type => array,
+                                             items => #{type => string, example => <<"127.0.0.1:3306">>}},
+                                redis_type => #{type => string,
+                                                enum => [<<"cluster">>],
+                                                example => <<"cluster">>},
+                                pool_size => #{type => integer},
+                                auto_reconnect => #{type => boolean, example => true},
+                                password => #{type => string},
+                                database => #{type => string, example => mqtt}
+                             }
+                            }
+                         ],
+                type => object
             },
-            principal => minirest:ref(<<"principal">>)
-        }
-    },
-    Principal = #{
-      oneOf => [ minirest:ref(<<"principal_username">>)
-               , minirest:ref(<<"principal_clientid">>)
-               , minirest:ref(<<"principal_ipaddress">>)
-               , #{type => string, enum=>[<<"all">>], example => <<"all">>}
-               , #{type => object,
-                   required => ['and'],
-                   properties => #{'and' => #{type => array,
-                                              items => #{oneOf => [ minirest:ref(<<"principal_username">>)
-                                                                  , minirest:ref(<<"principal_clientid">>)
-                                                                  , minirest:ref(<<"principal_ipaddress">>)
-                                                                  ]}}},
-                   example => #{'and' => [#{username => <<"emqx">>}, #{clientid => <<"emqx">>}]}
-                  }
-               , #{type => object,
-                   required => ['or'],
-                   properties => #{'and' => #{type => array,
-                                              items => #{oneOf => [ minirest:ref(<<"principal_username">>)
-                                                                  , minirest:ref(<<"principal_clientid">>)
-                                                                  , minirest:ref(<<"principal_ipaddress">>)
-                                                                  ]}}},
-                   example => #{'or' => [#{username => <<"emqx">>}, #{clientid => <<"emqx">>}]}
-                  }
-               ]
-    },
-    PrincipalUsername = #{type => object,
-                           required => [username],
-                           properties => #{username => #{type => string}},
-                           example => #{username => <<"emqx">>}
-                          },
-    PrincipalClientid = #{type => object,
-                           required => [clientid],
-                           properties => #{clientid => #{type => string}},
-                           example => #{clientid => <<"emqx">>}
-                          },
-    PrincipalIpaddress = #{type => object,
-                            required => [ipaddress],
-                            properties => #{ipaddress => #{type => string}},
-                            example => #{ipaddress => <<"127.0.0.1">>}
-                           },
-    ErrorDef = #{
-        type => object,
-        properties => #{
-            code => #{
+            cmd => #{
                 type => string,
-                example => <<"BAD_REQUEST">>
-            },
-            message => #{
-                type => string
+                example => <<"HGETALL mqtt_authz">>
             }
         }
     },
-    [ #{<<"returned_sources">> => RetruenedRules}
-    , #{<<"sources">> => Rules}
-    , #{<<"simple_source">> => SimpleRule}
-    , #{<<"principal">> => Principal}
-    , #{<<"principal_username">> => PrincipalUsername}
-    , #{<<"principal_clientid">> => PrincipalClientid}
-    , #{<<"principal_ipaddress">> => PrincipalIpaddress}
-    , #{<<"error">> => ErrorDef}
+    [ #{<<"returned_sources">> => RetruenedSources}
+    , #{<<"sources">> => Sources}
+    , #{<<"connector_redis">> => ConnectorRedis}
     ].

+ 61 - 0
apps/emqx_authz/src/emqx_authz_api_settings.erl

@@ -0,0 +1,61 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_authz_api_settings).
+
+-behavior(minirest_api).
+
+-export([ api_spec/0
+        , settings/2
+        ]).
+
+api_spec() ->
+    {[settings_api()], []}.
+
+authorization_settings() ->
+    maps:remove(<<"sources">>, emqx:get_raw_config([authorization], #{})).
+
+conf_schema() ->
+    emqx_mgmt_api_configs:gen_schema(authorization_settings()).
+
+settings_api() ->
+    Metadata = #{
+        get => #{
+            description => "Get authorization settings",
+            responses => #{<<"200">> => emqx_mgmt_util:schema(conf_schema())}
+        },
+        put => #{
+            description => "Update authorization settings",
+            requestBody => emqx_mgmt_util:schema(conf_schema()),
+            responses => #{
+                <<"200">> => emqx_mgmt_util:schema(conf_schema()),
+                <<"400">> => emqx_mgmt_util:bad_request()
+            }
+        }
+    },
+    {"/authorization/settings", Metadata, settings}.
+
+settings(get, _Params) ->
+    {200, authorization_settings()};
+
+settings(put, #{body := #{<<"no_match">> := NoMatch,
+                          <<"deny_action">> := DenyAction,
+                          <<"cache">> := Cache}}) ->
+    {ok, _} = emqx:update_config([authorization, no_match], NoMatch),
+    {ok, _} = emqx:update_config([authorization, deny_action], DenyAction),
+    {ok, _} = emqx:update_config([authorization, cache], Cache),
+    ok = emqx_authz_cache:drain_cache(),
+    {200, authorization_settings()}.

+ 33 - 164
apps/emqx_authz/src/emqx_authz_api.erl

@@ -14,31 +14,29 @@
 %% limitations under the License.
 %%--------------------------------------------------------------------
 
--module(emqx_authz_api).
+-module(emqx_authz_api_sources).
 
 -behavior(minirest_api).
 
 -include("emqx_authz.hrl").
 
--define(EXAMPLE_RETURNED_RULE1,
-        #{principal => <<"all">>,
-          permission => <<"allow">>,
-          action => <<"all">>,
-          topics => [<<"#">>],
-          annotations => #{id => 1}
-         }).
-
+-define(EXAMPLE_REDIS,
+        #{type=> redis,
+          config => #{server => <<"127.0.0.1:3306">>,
+                      redis_type => single,
+                      pool_size => 1,
+                      auto_reconnect => true
+                     },
+          cmd => <<"HGETALL mqtt_authz">>}).
+-define(EXAMPLE_RETURNED_REDIS,
+        maps:put(annotations, #{status => healthy}, ?EXAMPLE_REDIS)
+        ).
 
 -define(EXAMPLE_RETURNED_RULES,
-        #{sources => [?EXAMPLE_RETURNED_RULE1
-                   ]
+        #{sources => [?EXAMPLE_RETURNED_REDIS
+                     ]
         }).
 
--define(EXAMPLE_RULE1, #{principal => <<"all">>,
-                         permission => <<"allow">>,
-                         action => <<"all">>,
-                         topics => [<<"#">>]}).
-
 -export([ api_spec/0
         , sources/2
         , source/2
@@ -107,9 +105,9 @@ sources_api() ->
                     'application/json' => #{
                         schema => minirest:ref(<<"sources">>),
                         examples => #{
-                            simple_source => #{
-                                summary => <<"Sources">>,
-                                value => jsx:encode(?EXAMPLE_RULE1)
+                            redis => #{
+                                summary => <<"Redis">>,
+                                value => jsx:encode(?EXAMPLE_REDIS)
                             }
                        }
                     }
@@ -117,23 +115,7 @@ sources_api() ->
             },
             responses => #{
                 <<"204">> => #{description => <<"Created">>},
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Bad Request">>,
-                                    value => #{
-                                        code => <<"BAD_REQUEST">>,
-                                        message => <<"Bad Request">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                <<"400">> => emqx_mgmt_util:bad_request()
             }
         },
         put => #{
@@ -146,9 +128,9 @@ sources_api() ->
                             items => minirest:ref(<<"returned_sources">>)
                         },
                         examples => #{
-                            sources => #{
-                                summary => <<"Sources">>,
-                                value => jsx:encode([?EXAMPLE_RULE1])
+                            redis => #{
+                                summary => <<"Redis">>,
+                                value => jsx:encode([?EXAMPLE_REDIS])
                             }
                         }
                     }
@@ -156,23 +138,7 @@ sources_api() ->
             },
             responses => #{
                 <<"204">> => #{description => <<"Created">>},
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Bad Request">>,
-                                    value => #{
-                                        code => <<"BAD_REQUEST">>,
-                                        message => <<"Bad Request">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                <<"400">> => emqx_mgmt_util:bad_request()
             }
         }
     },
@@ -201,29 +167,13 @@ source_api() ->
                             examples => #{
                                 sources => #{
                                     summary => <<"Sources">>,
-                                    value => jsx:encode(?EXAMPLE_RETURNED_RULE1)
+                                    value => jsx:encode(?EXAMPLE_RETURNED_REDIS)
                                 }
                             }
                          }
                     }
                 },
-                <<"404">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Not Found">>,
-                                    value => #{
-                                        code => <<"NOT_FOUND">>,
-                                        message => <<"source xxx not found">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                <<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>)
             }
         },
         put => #{
@@ -243,9 +193,9 @@ source_api() ->
                     'application/json' => #{
                         schema => minirest:ref(<<"sources">>),
                         examples => #{
-                            simple_source => #{
-                                summary => <<"Sources">>,
-                                value => jsx:encode(?EXAMPLE_RULE1)
+                            redis => #{
+                                summary => <<"Redis">>,
+                                value => jsx:encode(?EXAMPLE_REDIS)
                             }
                        }
                     }
@@ -253,47 +203,15 @@ source_api() ->
             },
             responses => #{
                 <<"204">> => #{description => <<"No Content">>},
-                <<"404">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Not Found">>,
-                                    value => #{
-                                        code => <<"NOT_FOUND">>,
-                                        message => <<"source xxx not found">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                },
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Bad Request">>,
-                                    value => #{
-                                        code => <<"BAD_REQUEST">>,
-                                        message => <<"Bad Request">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                <<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>),
+                <<"400">> => emqx_mgmt_util:bad_request()
             }
         },
         delete => #{
             description => "Delete source",
             parameters => [
                 #{
-                    name => id,
+                    name => type,
                     in => path,
                     schema => #{
                        type => string
@@ -303,23 +221,7 @@ source_api() ->
             ],
             responses => #{
                 <<"204">> => #{description => <<"No Content">>},
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Bad Request">>,
-                                    value => #{
-                                        code => <<"BAD_REQUEST">>,
-                                        message => <<"Bad Request">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                <<"400">> => emqx_mgmt_util:bad_request()
             }
         }
     },
@@ -378,38 +280,8 @@ move_source_api() ->
                 <<"204">> => #{
                     description => <<"No Content">>
                 },
-                <<"404">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{ 'application/json' => #{ schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Not Found">>,
-                                    value => #{
-                                        code => <<"NOT_FOUND">>,
-                                        message => <<"source xxx not found">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                },
-                <<"400">> => #{
-                    description => <<"Bad Request">>,
-                    content => #{
-                        'application/json' => #{
-                            schema => minirest:ref(<<"error">>),
-                            examples => #{
-                                example1 => #{
-                                    summary => <<"Bad Request">>,
-                                    value => #{
-                                        code => <<"BAD_REQUEST">>,
-                                        message => <<"Bad Request">>
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
+                <<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>),
+                <<"400">> => emqx_mgmt_util:bad_request()
             }
         }
     },
@@ -519,6 +391,3 @@ move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Pos
             {400, #{code => <<"BAD_REQUEST">>,
                     messgae => atom_to_binary(Reason)}}
     end.
-
-
-

+ 135 - 0
apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl

@@ -0,0 +1,135 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_authz_api_settings_SUITE).
+
+-compile(nowarn_export_all).
+-compile(export_all).
+
+-include("emqx_authz.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+-define(CONF_DEFAULT, <<"authorization: {sources: []}">>).
+
+-import(emqx_ct_http, [ request_api/3
+                      , request_api/5
+                      , get_http_data/1
+                      , create_default_app/0
+                      , delete_default_app/0
+                      , default_auth_header/0
+                      , auth_header/2
+                      ]).
+
+-define(HOST, "http://127.0.0.1:18083/").
+-define(API_VERSION, "v5").
+-define(BASE_PATH, "api").
+
+all() ->
+    emqx_ct:all(?MODULE).
+
+groups() ->
+    [].
+
+init_per_suite(Config) ->
+    ok = emqx_ct_helpers:start_apps([emqx_authz, emqx_dashboard], fun set_special_configs/1),
+    {ok, _} = emqx:update_config([authorization, cache, enable], false),
+    {ok, _} = emqx:update_config([authorization, no_match], deny),
+
+    Config.
+
+end_per_suite(_Config) ->
+    emqx_ct_helpers:stop_apps([emqx_resource, emqx_authz, emqx_dashboard]),
+    ok.
+
+set_special_configs(emqx_dashboard) ->
+    Config = #{
+        default_username => <<"admin">>,
+        default_password => <<"public">>,
+        listeners => [#{
+            protocol => http,
+            port => 18083
+        }]
+    },
+    emqx_config:put([emqx_dashboard], Config),
+    ok;
+set_special_configs(_App) ->
+    ok.
+
+%%------------------------------------------------------------------------------
+%% Testcases
+%%------------------------------------------------------------------------------
+
+t_api(_) ->
+    Settings1 = #{<<"no_match">> => <<"deny">>,
+                  <<"deny_action">> => <<"disconnect">>,
+                  <<"cache">> => #{
+                      <<"enable">> => false,
+                      <<"max_size">> => 32,
+                      <<"ttl">> => 60000
+                     }
+                 },
+
+    {ok, 200, Result1} = request(put, uri(["authorization", "settings"]), Settings1),
+    {ok, 200, Result1} = request(get, uri(["authorization", "settings"]), []),
+    ?assertEqual(Settings1, jsx:decode(Result1)),
+
+    Settings2 = #{<<"no_match">> => <<"allow">>,
+                  <<"deny_action">> => <<"ignore">>,
+                  <<"cache">> => #{
+                      <<"enable">> => true,
+                      <<"max_size">> => 32,
+                      <<"ttl">> => 60000
+                     }
+                 },
+
+    {ok, 200, Result2} = request(put, uri(["authorization", "settings"]), Settings2),
+    {ok, 200, Result2} = request(get, uri(["authorization", "settings"]), []),
+    ?assertEqual(Settings2, jsx:decode(Result2)),
+
+    ok.
+
+%%--------------------------------------------------------------------
+%% HTTP Request
+%%--------------------------------------------------------------------
+
+request(Method, Url, Body) ->
+    Request = case Body of
+        [] -> {Url, [auth_header_()]};
+        _ -> {Url, [auth_header_()], "application/json", jsx:encode(Body)}
+    end,
+    ct:pal("Method: ~p, Request: ~p", [Method, Request]),
+    case httpc:request(Method, Request, [], [{body_format, binary}]) of
+        {error, socket_closed_remotely} ->
+            {error, socket_closed_remotely};
+        {ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } ->
+            {ok, Code, Return};
+        {ok, {Reason, _, _}} ->
+            {error, Reason}
+    end.
+
+uri() -> uri([]).
+uri(Parts) when is_list(Parts) ->
+    NParts = [E || E <- Parts],
+    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]).
+
+get_sources(Result) ->
+    maps:get(<<"sources">>, jsx:decode(Result), []).
+
+auth_header_() ->
+    Username = <<"admin">>,
+    Password = <<"public">>,
+    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
+    {"Authorization", "Bearer " ++ binary_to_list(Token)}.

+ 1 - 1
apps/emqx_authz/test/emqx_authz_api_SUITE.erl

@@ -13,7 +13,7 @@
 %% limitations under the License.
 %%--------------------------------------------------------------------
 
--module(emqx_authz_api_SUITE).
+-module(emqx_authz_api_sources_SUITE).
 
 -compile(nowarn_export_all).
 -compile(export_all).