fix(authz): refine authz-http api with default headers

This commit is contained in:
JimMoen 2022-04-12 18:42:08 +08:00
parent c67e565755
commit 341973880d
5 changed files with 95 additions and 59 deletions

View File

@ -0,0 +1,20 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2022 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.
%%--------------------------------------------------------------------
%% config root name all auth providers have to agree on.
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, "authorization").
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM, authorization).
-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY, <<"authorization">>).

View File

@ -24,6 +24,7 @@
-elvis([{elvis_style, invalid_dynamic_call, disable}]). -elvis([{elvis_style, invalid_dynamic_call, disable}]).
-include("emqx_authentication.hrl"). -include("emqx_authentication.hrl").
-include("emqx_access_control.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-type duration() :: integer(). -type duration() :: integer().
@ -159,9 +160,9 @@ roots(high) ->
)}, )},
%% NOTE: authorization schema here is only to keep emqx app prue %% NOTE: authorization schema here is only to keep emqx app prue
%% the full schema for EMQX node is injected in emqx_conf_schema. %% the full schema for EMQX node is injected in emqx_conf_schema.
{"authorization", {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME,
sc( sc(
ref("authorization"), ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME),
#{} #{}
)} )}
]; ];

View File

@ -14,6 +14,8 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-include_lib("emqx/include/emqx_access_control.hrl").
-define(APP, emqx_authz). -define(APP, emqx_authz).
-define(ALLOW_DENY(A), -define(ALLOW_DENY(A),
@ -45,6 +47,11 @@
-define(RE_PLACEHOLDER, "\\$\\{[a-z0-9_]+\\}"). -define(RE_PLACEHOLDER, "\\$\\{[a-z0-9_]+\\}").
%% has to be the same as the root field name defined in emqx_schema
-define(CONF_NS, ?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME).
-define(CONF_NS_ATOM, ?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM).
-define(CONF_NS_BINARY, ?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY).
%% API examples %% API examples
-define(USERNAME_RULES_EXAMPLE, #{ -define(USERNAME_RULES_EXAMPLE, #{
username => user1, username => user1,

View File

@ -16,36 +16,32 @@
-module(emqx_authz_api_schema). -module(emqx_authz_api_schema).
-include("emqx_authz.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl").
-import(hoconsc, [mk/2, enum/1]). -import(hoconsc, [mk/2, enum/1]).
-import(emqx_schema, [mk_duration/2]). -import(emqx_schema, [mk_duration/2]).
-export([fields/1, authz_sources_types/1]). -export([
fields/1,
authz_sources_types/1
]).
fields(http) -> %%------------------------------------------------------------------------------
authz_common_fields(http) ++ %% Hocon Schema
[ %%------------------------------------------------------------------------------
{url, fun url/1},
{method, #{ fields(http_get) ->
type => enum([get, post]), [
default => get, {method, #{type => get, default => get, required => true}},
required => true {headers, fun headers_no_content_type/1}
}}, ] ++ authz_http_common_fields();
{headers, fun headers/1}, fields(http_post) ->
{body, map([{fuzzy, term(), binary()}])}, [
{request_timeout, mk_duration("Request timeout", #{default => "30s"})} {method, #{type => post, default => post, required => true}},
] ++ {headers, fun headers/1}
maps:to_list( ] ++ authz_http_common_fields();
maps:without(
[
base_url,
pool_type
],
maps:from_list(emqx_connector_http:fields(config))
)
);
fields(built_in_database) -> fields(built_in_database) ->
authz_common_fields(built_in_database); authz_common_fields(built_in_database);
fields(mongo_single) -> fields(mongo_single) ->
@ -101,6 +97,23 @@ fields(position) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% http type funcs %% http type funcs
authz_http_common_fields() ->
authz_common_fields(http) ++
[
{url, fun url/1},
{body, map([{fuzzy, term(), binary()}])},
{request_timeout, mk_duration("Request timeout", #{default => "30s"})}
] ++
maps:to_list(
maps:without(
[
base_url,
pool_type
],
maps:from_list(emqx_connector_http:fields(config))
)
).
url(type) -> binary(); url(type) -> binary();
url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
url(required) -> true; url(required) -> true;
@ -119,6 +132,19 @@ headers(default) ->
headers(_) -> headers(_) ->
undefined. undefined.
headers_no_content_type(type) ->
map();
headers_no_content_type(desc) ->
"List of HTTP headers.";
headers_no_content_type(converter) ->
fun(Headers) ->
maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
end;
headers_no_content_type(default) ->
default_headers_no_content_type();
headers_no_content_type(_) ->
undefined.
%% headers %% headers
default_headers() -> default_headers() ->
maps:put( maps:put(
@ -208,9 +234,11 @@ enable(_) -> undefined.
authz_sources_types(Type) -> authz_sources_types(Type) ->
case Type of case Type of
simple -> simple ->
[mongodb, redis]; [http, mongodb, redis];
detailed -> detailed ->
[ [
http_get,
http_post,
mongo_single, mongo_single,
mongo_rs, mongo_rs,
mongo_sharded, mongo_sharded,
@ -220,7 +248,6 @@ authz_sources_types(Type) ->
] ]
end ++ end ++
[ [
http,
built_in_database, built_in_database,
mysql, mysql,
postgresql, postgresql,

View File

@ -16,6 +16,7 @@
-module(emqx_authz_schema). -module(emqx_authz_schema).
-include("emqx_authz.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl").
@ -40,9 +41,6 @@
headers/1 headers/1
]). ]).
-import(emqx_schema, [mk_duration/2]).
-include_lib("hocon/include/hoconsc.hrl").
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Hocon Schema %% Hocon Schema
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -197,7 +195,9 @@ http_common_fields() ->
[ [
{url, fun url/1}, {url, fun url/1},
{request_timeout, {request_timeout,
mk_duration("Request timeout", #{default => "30s", desc => "Request timeout."})}, emqx_schema:mk_duration("Request timeout", #{
default => "30s", desc => "Request timeout."
})},
{body, #{type => map(), required => false, desc => "HTTP request body."}} {body, #{type => map(), required => false, desc => "HTTP request body."}}
] ++ ] ++
maps:to_list( maps:to_list(
@ -232,8 +232,7 @@ mongo_common_fields() ->
validations() -> validations() ->
[ [
{check_ssl_opts, fun check_ssl_opts/1}, {check_ssl_opts, fun check_ssl_opts/1}
{check_headers, fun check_headers/1}
]. ].
headers(type) -> headers(type) ->
@ -259,6 +258,13 @@ headers_no_content_type(converter) ->
end; end;
headers_no_content_type(default) -> headers_no_content_type(default) ->
default_headers_no_content_type(); default_headers_no_content_type();
headers_no_content_type(validator) ->
fun(Headers) ->
case lists:keyfind(<<"content-type">>, 1, Headers) of
false -> ok;
_ -> {error, do_not_include_content_type}
end
end;
headers_no_content_type(_) -> headers_no_content_type(_) ->
undefined. undefined.
@ -297,6 +303,7 @@ transform_header_name(Headers) ->
Headers Headers
). ).
%% TODO: fix me, not work
check_ssl_opts(Conf) -> check_ssl_opts(Conf) ->
case hocon_maps:get("config.url", Conf) of case hocon_maps:get("config.url", Conf) of
undefined -> undefined ->
@ -315,25 +322,6 @@ check_ssl_opts(Conf) ->
end end
end. end.
check_headers(Conf) ->
case hocon_maps:get("config.method", Conf) of
undefined ->
true;
Method0 ->
Method = to_bin(Method0),
Headers = hocon_maps:get("config.headers", Conf),
case Method of
<<"post">> ->
true;
_ when Headers =:= undefined -> true;
_ when is_list(Headers) ->
case lists:member(<<"content-type">>, Headers) of
false -> true;
true -> {Method0, do_not_include_content_type}
end
end
end.
union_array(Item) when is_list(Item) -> union_array(Item) when is_list(Item) ->
hoconsc:array(hoconsc:union(Item)). hoconsc:array(hoconsc:union(Item)).
@ -376,10 +364,3 @@ to_list(A) when is_atom(A) ->
atom_to_list(A); atom_to_list(A);
to_list(B) when is_binary(B) -> to_list(B) when is_binary(B) ->
binary_to_list(B). binary_to_list(B).
to_bin(A) when is_atom(A) ->
atom_to_binary(A);
to_bin(B) when is_binary(B) ->
B;
to_bin(L) when is_list(L) ->
list_to_binary(L).