Просмотр исходного кода

feat(authz): support api

Signed-off-by: zhanghongtong <rory-z@outlook.com>
zhanghongtong 4 лет назад
Родитель
Сommit
c26ec5c0dd

+ 30 - 16
apps/emqx_authz/src/emqx_authz.erl

@@ -41,42 +41,56 @@ register_metrics() ->
 init() ->
     ok = register_metrics(),
     emqx_config_handler:add_handler(?CONF_KEY_PATH, ?MODULE),
-    NRules = [init_rule(Rule) || Rule <- lookup()],
+    NRules = [init_rule(Rule) || Rule <- emqx_config:get(?CONF_KEY_PATH, [])],
     ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [NRules]}, -1).
 
 lookup() ->
-    emqx_config:get(?CONF_KEY_PATH, []).
+    {_M, _F, A}= find_action_in_hooks(),
+    A.
 
 update(Cmd, Rules) ->
     emqx_config:update(emqx_authz_schema, ?CONF_KEY_PATH, {Cmd, Rules}).
 
 %% For now we only support re-creating the entire rule list
-pre_config_update({head, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
-    [Rule | OldConf];
-pre_config_update({tail, Rule}, OldConf) when is_map(Rule), is_list(OldConf) ->
-    OldConf ++ [Rule];
-pre_config_update({_, NewConf}, _OldConf) ->
+pre_config_update({head, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
+    Rules ++ OldConf;
+pre_config_update({tail, Rules}, OldConf) when is_list(Rules), is_list(OldConf) ->
+    OldConf ++ Rules;
+pre_config_update({_, Rules}, _OldConf) when is_list(Rules)->
     %% overwrite the entire config!
-    case is_list(NewConf) of
-        true -> NewConf;
-        false -> [NewConf]
-    end.
+    Rules.
 
 post_config_update(_, undefined, _OldConf) ->
-    %_ = [release_rules(Rule) || Rule <- OldConf],
     ok;
+post_config_update({head, Rules}, _NewRules, _OldConf) ->
+    InitedRules = [init_rule(Rule) || Rule <- check_rules(Rules)],
+    ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:append(InitedRules, lookup())]}, -1),
+    ok = emqx_authz_cache:drain_cache();
+post_config_update({tail, Rules}, _NewRules, _OldConf) ->
+    InitedRules = [init_rule(Rule) || Rule <- check_rules(Rules)],
+    emqx_hooks:put('client.authorize', {?MODULE, authorize, [lists:append(InitedRules, lookup())]}, -1),
+    ok = emqx_authz_cache:drain_cache();
 post_config_update(_, NewRules, _OldConf) ->
-    %_ = [release_rules(Rule) || Rule <- OldConf],
+    %% overwrite the entire config!
+    OldInitedRules = lookup(),
     InitedRules = [init_rule(Rule) || Rule <- NewRules],
-    Action = find_action_in_hooks(),
-    ok = emqx_hooks:del('client.authorize', Action),
-    ok = emqx_hooks:add('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
+    ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [InitedRules]}, -1),
+    lists:foreach(fun (#{type := _Type, enable := true, metadata := #{id := Id}}) ->
+                         ok = emqx_resource:remove(Id);
+                      (_) -> ok
+                  end, OldInitedRules),
     ok = emqx_authz_cache:drain_cache().
 
 %%--------------------------------------------------------------------
 %% Internal functions
 %%--------------------------------------------------------------------
 
+check_rules(RawRules) ->
+    {ok, Conf} = hocon:binary(jsx:encode(#{<<"authorization">> => #{<<"rules">> => RawRules}}), #{format => richmap}),
+    CheckConf = hocon_schema:check(emqx_authz_schema, Conf, #{atom_key => true}),
+    #{authorization := #{rules := Rules}} = hocon_schema:richmap_to_map(CheckConf),
+    Rules.
+
 find_action_in_hooks() ->
     Callbacks = emqx_hooks:lookup('client.authorize'),
     [Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ],

+ 99 - 65
apps/emqx_authz/src/emqx_authz_api.erl

@@ -16,74 +16,108 @@
 
 -module(emqx_authz_api).
 
--include("emqx_authz.hrl").
-
--rest_api(#{name   => lookup_authz,
-            method => 'GET',
-            path   => "/authz",
-            func   => lookup_authz,
-            descr  => "Lookup Authorization"
-           }).
-
--rest_api(#{name   => update_authz,
-            method => 'PUT',
-            path   => "/authz",
-            func   => update_authz,
-            descr  => "Rewrite authz list"
-           }).
-
--rest_api(#{name   => append_authz,
-            method => 'POST',
-            path   => "/authz/append",
-            func   => append_authz,
-            descr  => "Add a new rule at the end of the authz list"
-           }).
+-behavior(minirest_api).
 
--rest_api(#{name   => push_authz,
-            method => 'POST',
-            path   => "/authz/push",
-            func   => push_authz,
-            descr  => "Add a new rule at the start of the authz list"
-           }).
+-include("emqx_authz.hrl").
 
--export([ lookup_authz/2
-        , update_authz/2
-        , append_authz/2
-        , push_authz/2
+-define(EXAMPLE_RETURNED_RULES,
+        #{rules => [ #{principal => <<"all">>,
+                       permission => <<"allow">>,
+                       action => <<"all">>,
+                       topics => [<<"#">>],
+                       metadata => #{id => 1}
+                      }
+                   ]
+        }).
+
+-define(EXAMPLE_RULE1, #{principal => <<"all">>,
+                         permission => <<"allow">>,
+                         action => <<"all">>,
+                         topics => [<<"#">>]}).
+
+-export([ api_spec/0
+        , authorization/2
         ]).
 
-lookup_authz(_Bindings, _Params) ->
-    return({ok, emqx_authz:lookup()}).
-
-update_authz(_Bindings, Params) ->
-    Rules = form_rules(Params),
-    return(emqx_authz:update(replace, Rules)).
-
-append_authz(_Bindings, Params) ->
-    Rules = form_rules(Params),
-    return(emqx_authz:update(tail, Rules)).
-
-push_authz(_Bindings, Params) ->
-    Rules = form_rules(Params),
-    return(emqx_authz:update(head, Rules)).
-
-%%------------------------------------------------------------------------------
-%% Interval Funcs
-%%------------------------------------------------------------------------------
-
-form_rules(Params) ->
-    Params.
-
-%%--------------------------------------------------------------------
-%% EUnits
-%%--------------------------------------------------------------------
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
+api_spec() ->
+    {[ authorization_api()
+     ], definitions()}.
+
+definitions() -> emqx_authz_api_schema:definitions().
+
+authorization_api() ->
+    Metadata = #{
+        get => #{
+            description => "List authorization rules",
+            parameters => [],
+            responses => #{
+                <<"200">> => #{
+                    description => <<"OK">>,
+                    content => #{
+                        'application/json' => #{
+                            schema => #{
+                                type => object,
+                                required => [rules],
+                                properties => #{rules => #{
+                                                  type => array,
+                                                  items => minirest:ref(<<"returned_rules">>)
+                                                 }
+                                               }
+                            },
+                            examples => #{
+                                rules => #{
+                                    summary => <<"Rules">>,
+                                    value => jsx:encode(?EXAMPLE_RETURNED_RULES)
+                                }
+                            }
+                         }
+                    }
+                },
+                <<"404">> => #{description => <<"Not Found">>}
+            }
+        },
+        post => #{
+            description => "Add new rule",
+            requestBody => #{
+                content => #{
+                    'application/json' => #{
+                        schema => minirest:ref(<<"rules">>),
+                        examples => #{
+                            simple_rule => #{
+                                summary => <<"Rules">>,
+                                value => jsx:encode(?EXAMPLE_RULE1)
+                            }
+                       }
+                    }
+                }
+            },
+            responses => #{
+                <<"201">> => #{description => <<"Created">>},
+                <<"400">> => #{description => <<"Bad Request">>}
+            }
+        }
+    },
+    {"/authorization", Metadata, authorization}.
+
+authorization(get, _Request) ->
+    Rules = lists:foldl(fun (#{type := _Type, enable := true, metadata := #{id := Id} = MataData} = Rule, AccIn) ->
+                                NRule = case emqx_resource:health_check(Id) of
+                                    ok ->
+                                        Rule#{metadata => MataData#{status => healthy}};
+                                    _ ->
+                                        Rule#{metadata => MataData#{status => unhealthy}}
+                                end,
+                                lists:append(AccIn, [NRule]);
+                            (Rule, AccIn) ->
+                                lists:append(AccIn, [Rule])
+                        end, [], emqx_authz:lookup()),
+    {200, #{rules => [Rules]}};
+authorization(post, Request) ->
+    {ok, Body, _} = cowboy_req:read_body(Request),
+    RawConfig = jsx:decode(Body, [return_maps]),
+    case emqx_authz:update(head, [RawConfig]) of
+        ok -> {201};
+        {error, Reason} -> {400, #{messgae => atom_to_binary(Reason)}}
+    end.
 
--endif.
 
-return(_) ->
-%%    TODO: V5 api
-    ok.

+ 144 - 0
apps/emqx_authz/src/emqx_authz_api_schema.erl

@@ -0,0 +1,144 @@
+%%--------------------------------------------------------------------
+%% 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_schema).
+
+-export([definitions/0]).
+
+definitions() ->
+    RetruenedRules = #{
+        allOf => [ #{type => object,
+                     properties => #{
+                        annotations => #{
+                            type => object,
+                            required => [id],
+                            properties => #{
+                                id => #{
+                                    type => string
+                                },
+                                principal => minirest:ref(<<"principal">>)
+                            }
+                            
+                        }
+                        
+                     }
+                   }
+                 , minirest:ref(<<"rules">>)
+                 ]
+    },
+    Rules = #{
+        oneOf => [ minirest:ref(<<"simple_rule">>)
+                 % , 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 = #{
+        type => object,
+        required => [principal, permission, action, topics],
+        properties => #{
+            action => #{
+                type => string,
+                enum => [<<"publish">>, <<"subscribe">>, <<"all">>],
+                example => <<"publish">>
+            },
+            permission => #{
+                type => string,
+                enum => [<<"allow">>, <<"deny">>],
+                example => <<"allow">>
+            },
+            topics => #{
+                type => array,
+                items => #{
+                    oneOf => [ #{type => string, example => <<"#">>}
+                             , #{type => object,
+                                 required => [eq],
+                                 properties => #{
+                                    eq => #{type => string}
+                                 },
+                                 example => #{eq => <<"#">>}
+                                }
+                             ]
+                }
+            },
+            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">>}
+                           },
+    [ #{<<"returned_rules">> => RetruenedRules}
+    , #{<<"rules">> => Rules}
+    , #{<<"simple_rule">> => SimpleRule}
+    , #{<<"principal">> => Principal}
+    , #{<<"principal_username">> => PrincipalUsername}
+    , #{<<"principal_clientid">> => PrincipalClientid}
+    , #{<<"principal_ipaddress">> => PrincipalIpaddress}
+    ].