Pārlūkot izejas kodu

fix(authn): fix some bugs

zhouzb 4 gadi atpakaļ
vecāks
revīzija
2a594b1a73

+ 407 - 331
apps/emqx_authn/src/emqx_authn_api.erl

@@ -16,338 +16,414 @@
 
 -module(emqx_authn_api).
 
--include("emqx_authn.hrl").
-
--export([ create_authenticator/2
-        , delete_authenticator/2
-        , update_authenticator/2
-        , lookup_authenticator/2
-        , list_authenticators/2
-        , move_authenticator/2
-        , import_users/2
-        , add_user/2
-        , delete_user/2
-        , update_user/2
-        , lookup_user/2
-        , list_users/2
-        ]).
-
--rest_api(#{name   => create_authenticator,
-            method => 'POST',
-            path   => "/authentication/authenticators",
-            func   => create_authenticator,
-            descr  => "Create authenticator"
-           }).
-
--rest_api(#{name   => delete_authenticator,
-            method => 'DELETE',
-            path   => "/authentication/authenticators/:bin:name",
-            func   => delete_authenticator,
-            descr  => "Delete authenticator"
-           }).
-
--rest_api(#{name   => update_authenticator,
-            method => 'PUT',
-            path   => "/authentication/authenticators/:bin:name",
-            func   => update_authenticator,
-            descr  => "Update authenticator"
-           }).
-
--rest_api(#{name   => lookup_authenticator,
-            method => 'GET',
-            path   => "/authentication/authenticators/:bin:name",
-            func   => lookup_authenticator,
-            descr  => "Lookup authenticator"
-           }).
-
--rest_api(#{name   => list_authenticators,
-            method => 'GET',
-            path   => "/authentication/authenticators",
-            func   => list_authenticators,
-            descr  => "List authenticators"
-           }).
-
--rest_api(#{name   => move_authenticator,
-            method => 'POST',
-            path   => "/authentication/authenticators/:bin:name/position",
-            func   => move_authenticator,
-            descr  => "Change the order of authenticators"
-           }).
-
--rest_api(#{name   => import_users,
-            method => 'POST',
-            path   => "/authentication/authenticators/:bin:name/import-users",
-            func   => import_users,
-            descr  => "Import users"
-           }).
-
--rest_api(#{name   => add_user,
-            method => 'POST',
-            path   => "/authentication/authenticators/:bin:name/users",
-            func   => add_user,
-            descr  => "Add user"
-           }).
-
--rest_api(#{name   => delete_user,
-            method => 'DELETE',
-            path   => "/authentication/authenticators/:bin:name/users/:bin:user_id",
-            func   => delete_user,
-            descr  => "Delete user"
-           }).
-
--rest_api(#{name   => update_user,
-            method => 'PUT',
-            path   => "/authentication/authenticators/:bin:name/users/:bin:user_id",
-            func   => update_user,
-            descr  => "Update user"
-           }).
-
--rest_api(#{name   => lookup_user,
-            method => 'GET',
-            path   => "/authentication/authenticators/:bin:name/users/:bin:user_id",
-            func   => lookup_user,
-            descr  => "Lookup user"
-           }).
+-behavior(minirest_api).
 
-%% TODO: Support pagination
--rest_api(#{name   => list_users,
-            method => 'GET',
-            path   => "/authentication/authenticators/:bin:name/users",
-            func   => list_users,
-            descr  => "List all users"
-           }).
-
-create_authenticator(Binding, Params) ->
-    do_create_authenticator(uri_decode(Binding), lists_to_map(Params)).
-
-do_create_authenticator(_Binding, Authenticator0) ->
-    Config = #{<<"emqx_authn">> => #{
-               <<"authenticators">> => [Authenticator0]
-              }},
-    #{emqx_authn := #{authenticators := [Authenticator1]}}
-        = hocon_schema:check_plain(emqx_authn_schema, Config,
-                                   #{atom_key => true, nullable => true}),
-    case emqx_authn:create_authenticator(?CHAIN, Authenticator1) of
-        {ok, Authenticator2} ->
-            return({ok, Authenticator2});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-delete_authenticator(Binding, Params) ->
-    do_delete_authenticator(uri_decode(Binding), maps:from_list(Params)).
-
-do_delete_authenticator(#{name := Name}, _Params) ->
-    case emqx_authn:delete_authenticator(?CHAIN, Name) of
-        ok ->
-            return(ok);
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-%% TODO: Support incremental update
-update_authenticator(Binding, Params) ->
-    do_update_authenticator(uri_decode(Binding), lists_to_map(Params)).
+-include("emqx_authn.hrl").
 
-%% TOOD: PUT method supports creation and update
-do_update_authenticator(#{name := Name}, NewConfig0) ->
-    case emqx_authn:lookup_authenticator(?CHAIN, Name) of
-        {ok, #{mechanism := Mechanism}} ->
-            Authenticator = #{<<"name">> => Name,
-                              <<"mechanism">> => Mechanism,
-                              <<"config">> => NewConfig0},
-            Config = #{<<"emqx_authn">> => #{
-                          <<"authenticators">> => [Authenticator]
-                      }},
+-export([ api_spec/0 ]).
+
+api_spec() ->
+    {[authenticator_api()], definitions()}.
+
+authenticator_api() ->
+    Example1 = #{name => <<"example">>,
+                 mechanism => <<"password-based">>,
+                 config => #{
+                     server_type => <<"built-in-example">>,
+                     user_id_type => <<"username">>,
+                     password_hash_algorithm => #{
+                         name => <<"sha256">>    
+                     }
+                 }},
+    Example2 = #{name => <<"example">>,
+                 mechanism => <<"password-based">>,
+                 config => #{
+                     server_type => <<"http-server">>,
+                     method => <<"post">>,
+                     url => <<"http://localhost:80/login">>,
+                     headers => #{
+                        <<"content-type">> => <<"application/json">>
+                     },
+                     form_data => #{
+                        <<"username">> => <<"${mqtt-username}">>,
+                        <<"password">> => <<"${mqtt-password}">>
+                     }
+                 }},
+    Example3 = #{name => <<"example">>,
+                 mechanism => <<"jwt">>,
+                 config => #{
+                     use_jwks => false,
+                     algorithm => <<"hmac-based">>,
+                     secret => <<"mysecret">>,
+                     secret_base64_encoded => false,
+                     verify_claims => #{
+                         <<"username">> => <<"${mqtt-username}">>
+                     }
+                 }},
+    Metadata = #{
+        post => #{
+            description => "Create authenticator",
+            requestBody => #{
+                content => #{
+                    'application/json' => #{
+                        schema => minirest:ref(<<"authenticator">>),
+                        examples => #{
+                            default => #{
+                                summary => <<"Default">>,
+                                value => emqx_json:encode(Example1)
+                            },
+                            http => #{
+                                summary => <<"Authentication provided by HTTP Server">>,
+                                value => emqx_json:encode(Example2)
+                            },
+                            jwt => #{
+                                summary => <<"JWT Authentication">>,
+                                value => emqx_json:encode(Example3)
+                            }
+                        }
+                    }
+                }
+            },
+            responses => #{
+                <<"201">> => #{
+                    description => <<"Created successfully">>,
+                    content => #{}
+                }
+            }
+        }
+    },
+    {"/authentication/authenticators", Metadata, clients}.
+
+definitions() ->
+    AuthenticatorDef = #{
+        allOf => [
             #{
-                emqx_authn := #{
-                    authenticators := [#{
-                        config := NewConfig1
-                    }]
+                type => object,
+                required => [name],
+                properties => #{
+                    name => #{
+                        type => string,
+                        example => "exmaple"
+                    }
                 }
-            } = hocon_schema:check_plain(emqx_authn_schema, Config,
-                                         #{atom_key => true, nullable => true}),
-            case emqx_authn:update_authenticator(?CHAIN, Name, NewConfig1) of
-                {ok, NAuthenticator} ->
-                    return({ok, NAuthenticator});
-                {error, Reason} ->
-                    return(serialize_error(Reason))
-            end;
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-lookup_authenticator(Binding, Params) ->
-    do_lookup_authenticator(uri_decode(Binding), maps:from_list(Params)).
-
-do_lookup_authenticator(#{name := Name}, _Params) ->
-    case emqx_authn:lookup_authenticator(?CHAIN, Name) of
-        {ok, Authenticator} ->
-            return({ok, Authenticator});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-list_authenticators(Binding, Params) ->
-    do_list_authenticators(uri_decode(Binding), maps:from_list(Params)).
-
-do_list_authenticators(_Binding, _Params) ->
-    case emqx_authn:list_authenticators(?CHAIN) of
-        {ok, Authenticators} ->
-            return({ok, Authenticators});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-move_authenticator(Binding, Params) ->
-    do_move_authenticator(uri_decode(Binding), maps:from_list(Params)).
-
-do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the front">>}) ->
-    case emqx_authn:move_authenticator_to_the_front(?CHAIN, Name) of
-        ok ->
-            return(ok);
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end;
-do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the end">>}) ->
-    case emqx_authn:move_authenticator_to_the_end(?CHAIN, Name) of
-        ok ->
-            return(ok);
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end;
-do_move_authenticator(#{name := Name}, #{<<"position">> := N}) when is_number(N) ->
-    case emqx_authn:move_authenticator_to_the_nth(?CHAIN, Name, N) of
-        ok ->
-            return(ok);
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end;
-do_move_authenticator(_Binding, _Params) ->
-    return(serialize_error({missing_parameter, <<"position">>})).
-
-import_users(Binding, Params) ->
-    do_import_users(uri_decode(Binding), maps:from_list(Params)).
-
-do_import_users(#{name := Name},
-                #{<<"filename">> := Filename}) ->
-    case emqx_authn:import_users(?CHAIN, Name, Filename) of
-        ok ->
-            return(ok);
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end;
-do_import_users(_Binding, Params) ->
-    Missed = get_missed_params(Params, [<<"filename">>, <<"file_format">>]),
-    return(serialize_error({missing_parameter, Missed})).
-
-add_user(Binding, Params) ->
-    do_add_user(uri_decode(Binding), maps:from_list(Params)).
-
-do_add_user(#{name := Name}, UserInfo) ->
-    case emqx_authn:add_user(?CHAIN, Name, UserInfo) of
-        {ok, User} ->
-            return({ok, User});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-delete_user(Binding, Params) ->
-    do_delete_user(uri_decode(Binding), maps:from_list(Params)).
-
-do_delete_user(#{name := Name,
-                 user_id := UserID}, _Params) ->
-    case emqx_authn:delete_user(?CHAIN, Name, UserID) of
-        ok ->
-            return(ok);
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-update_user(Binding, Params) ->
-    do_update_user(uri_decode(Binding), maps:from_list(Params)).
-
-do_update_user(#{name := Name,
-                 user_id := UserID}, NewUserInfo) ->
-    case emqx_authn:update_user(?CHAIN, Name, UserID, NewUserInfo) of
-        {ok, User} ->
-            return({ok, User});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-lookup_user(Binding, Params) ->
-    do_lookup_user(uri_decode(Binding), maps:from_list(Params)).
-
-do_lookup_user(#{name := Name,
-                 user_id := UserID}, _Params) ->
-    case emqx_authn:lookup_user(?CHAIN, Name, UserID) of
-        {ok, User} ->
-            return({ok, User});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-list_users(Binding, Params) ->
-    do_list_users(uri_decode(Binding), maps:from_list(Params)).
-
-do_list_users(#{name := Name}, _Params) ->
-    case emqx_authn:list_users(?CHAIN, Name) of
-        {ok, Users} ->
-            return({ok, Users});
-        {error, Reason} ->
-            return(serialize_error(Reason))
-    end.
-
-%%------------------------------------------------------------------------------
-%% Internal functions
-%%------------------------------------------------------------------------------
-
-uri_decode(Params) ->
-    maps:fold(fun(K, V, Acc) ->
-                  Acc#{K => emqx_http_lib:uri_decode(V)}
-              end, #{}, Params).
-
-lists_to_map(L) ->
-    lists_to_map(L, #{}).
-
-lists_to_map([], Acc) ->
-    Acc;
-lists_to_map([{K, V} | More], Acc) when is_list(V) ->
-    NV = lists_to_map(V),
-    lists_to_map(More, Acc#{K => NV});
-lists_to_map([{K, V} | More], Acc) ->
-    lists_to_map(More, Acc#{K => V});
-lists_to_map([_ | _] = L, _) ->
-    L.
-
-serialize_error({already_exists, {Type, ID}}) ->
-    {error, <<"ALREADY_EXISTS">>, list_to_binary(io_lib:format("~s '~s' already exists", [serialize_type(Type), ID]))};
-serialize_error({not_found, {Type, ID}}) ->
-    {error, <<"NOT_FOUND">>, list_to_binary(io_lib:format("~s '~s' not found", [serialize_type(Type), ID]))};
-serialize_error({duplicate, Name}) ->
-    {error, <<"INVALID_PARAMETER">>, list_to_binary(io_lib:format("Authenticator name '~s' is duplicated", [Name]))};
-serialize_error({missing_parameter, Names = [_ | Rest]}) ->
-    Format = ["~s," || _ <- Rest] ++ ["~s"],
-    NFormat = binary_to_list(iolist_to_binary(Format)),
-    {error, <<"MISSING_PARAMETER">>, list_to_binary(io_lib:format("The input parameters " ++ NFormat ++ " that are mandatory for processing this request are not supplied.", Names))};
-serialize_error({missing_parameter, Name}) ->
-    {error, <<"MISSING_PARAMETER">>, list_to_binary(io_lib:format("The input parameter '~s' that is mandatory for processing this request is not supplied.", [Name]))};
-serialize_error(_) ->
-    {error, <<"UNKNOWN_ERROR">>, <<"Unknown error">>}.
-
-serialize_type(authenticator) ->
-    "Authenticator".
-
-get_missed_params(Actual, Expected) ->
-    Keys = lists:foldl(fun(Key, Acc) ->
-                           case maps:is_key(Key, Actual) of
-                               true -> Acc;
-                               false -> [Key | Acc]
-                           end
-                       end, [], Expected),
-    lists:reverse(Keys).
-
-return(_) ->
-%%    TODO: V5 API
-    ok.
+            },
+            #{
+                oneOf => [ minirest:ref(<<"password_based">>)
+                         , minirest:ref(<<"jwt">>)
+                         , minirest:ref(<<"scram">>)
+                ]    
+            }
+        ]
+    },
+
+    PasswordBasedDef = #{
+        type => object,
+        properties => #{
+            mechanism => #{
+                type => string,
+                enum => [<<"password-based">>],
+                example => <<"password-based">>
+            },
+            config => #{
+                oneOf => [ minirest:ref(<<"password_based_built_in_database">>)
+                         , minirest:ref(<<"password_based_mysql">>)
+                         , minirest:ref(<<"password_based_pgsql">>)
+                         , minirest:ref(<<"password_based_http_server">>)
+                         ]
+            }
+        }
+    },
+
+    JWTDef = #{
+        type => object,
+        properties => #{
+            mechanism => #{
+                type => string,
+                enum => [<<"jwt">>],
+                example => <<"jwt">>
+            },
+            config => #{
+                type => object,
+                properties => #{
+                    use_jwks => #{
+                        type => boolean,
+                        default => false,
+                        example => false
+                    },
+                    algorithm => #{
+                        type => string,
+                        enum => [<<"hmac-based">>, <<"public-key">>],
+                        default => <<"hmac-based">>,
+                        example => <<"hmac-based">>
+                    },
+                    secret => #{
+                        type => string
+                    },
+                    secret_base64_encoded => #{
+                        type => boolean,
+                        default => false
+                    },
+                    certificate => #{
+                        type => string
+                    },
+                    verify_claims => #{
+                        type => object,
+                        additionalProperties => #{
+                            type => string
+                        }
+                    },
+                    ssl => minirest:ref(<<"ssl">>)
+                } 
+            }
+        }
+    },
+    
+    SCRAMDef = #{
+        type => object,
+        properties => #{
+            mechanism => #{
+                type => string,
+                enum => [<<"scram">>],
+                example => <<"scram">>
+            },
+            config => #{
+                type => object,
+                properties => #{
+                    server_type => #{
+                        type => string,
+                        enum => [<<"built-in-database">>],
+                        default => <<"built-in-database">>
+                    },
+                    algorithm => #{
+                        type => string,
+                        enum => [<<"sha256">>, <<"sha512">>],
+                        default => <<"sha256">>
+                    },
+                    iteration_count => #{
+                        type => integer,
+                        default => 4096
+                    }
+                }
+            }
+        }
+    },
+
+    PasswordBasedBuiltInDatabaseDef = #{
+        type => object,
+        properties => #{
+            server_type => #{
+                type => string,
+                enum => [<<"built-in-database">>],
+                example => <<"built-in-database">>
+            },
+            user_id_type => #{
+                type => string,
+                enum => [<<"username">>, <<"clientid">>],
+                default => <<"username">>,
+                example => <<"username">>
+            },
+            password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>)
+        }
+    },
+
+    PasswordBasedMySQLDef = #{
+        type => object,
+        properties => #{
+            server_type => #{
+                type => string,
+                enum => [<<"mysql">>],
+                example => <<"mysql">>
+            },
+            server => #{
+                type => string,
+                example => <<"localhost:3306">>
+            },
+            database => #{
+                type => string
+            },
+            pool_size => #{
+                type => integer,
+                default => 8
+            },
+            username => #{
+                type => string
+            },
+            password => #{
+                type => string
+            },
+            auto_reconnect => #{
+                type => boolean,
+                default => true
+            },
+            ssl => minirest:ref(<<"ssl">>),
+            password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>),
+            salt_position => #{
+                type => string,
+                enum => [<<"prefix">>, <<"suffix">>],
+                default => <<"prefix">>
+            },
+            query => #{
+                type => string,
+                example => <<"SELECT password_hash FROM mqtt_user WHERE username = ${mqtt-username}">>
+            },
+            query_timeout => #{
+                type => integer,
+                description => <<"Query timeout, Unit: Milliseconds">>,
+                default => 5000
+            }
+        }
+    },
+
+    PasswordBasedPgSQLDef = #{
+        type => object,
+        properties => #{
+            server_type => #{
+                type => string,
+                enum => [<<"pgsql">>],
+                example => <<"pgsql">>
+            },
+            server => #{
+                type => string,
+                example => <<"localhost:5432">>
+            },
+            database => #{
+                type => string
+            },
+            pool_size => #{
+                type => integer,
+                default => 8
+            },
+            username => #{
+                type => string
+            },
+            password => #{
+                type => string
+            },
+            auto_reconnect => #{
+                type => boolean,
+                default => true
+            },
+            password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>),
+            salt_position => #{
+                type => string,
+                enum => [<<"prefix">>, <<"suffix">>],
+                default => <<"prefix">>
+            },
+            query => #{
+                type => string,
+                example => <<"SELECT password_hash FROM mqtt_user WHERE username = ${mqtt-username}">>
+            }
+        }
+    },
+
+    PasswordBasedHTTPServerDef = #{
+        type => object,
+        properties => #{
+            server_type => #{
+                type => string,
+                enum => [<<"http-server">>],
+                example => <<"http-server">>
+            },
+            method => #{
+                type => string,
+                enum => [<<"get">>, <<"post">>],
+                default => <<"post">>
+            },
+            url => #{
+                type => string,
+                example => <<"http://localhost:80/login">>
+            },
+            headers => #{
+                type => object,
+                additionalProperties => #{
+                    type => string
+                }
+            },
+            format_data => #{
+                type => string
+            },
+            connect_timeout => #{
+                type => integer,
+                default => 5000
+            },
+            max_retries => #{
+                type => integer,
+                default => 5
+            },
+            retry_interval => #{
+                type => integer,
+                default => 1000
+            },
+            request_timout => #{
+                type => integer,
+                default => 5000
+            },
+            pool_size =>  #{
+                type => integer,
+                default => 8
+            }
+        }  
+    },
+
+    PasswordHashAlgorithmDef = #{
+        type => object,
+        required => [name],
+        properties => #{
+            name => #{
+                type => string,
+                enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>],
+                default => <<"sha256">>
+            },
+                salt_rounds => #{
+                type => integer,
+                default => 10
+            }
+        }
+    },
+
+    SSLDef = #{
+        type => object,
+        properties => #{
+            enable => #{
+                type => boolean,
+                default => false    
+            },
+            certfile => #{
+                type => string
+            },
+            keyfile => #{
+                type => string
+            },
+            cacertfile => #{
+                type => string
+            },
+            verify => #{
+                type => boolean,
+                default => true
+            },
+            server_name_indication => #{
+                type => object,
+                properties => #{
+                    enable => #{
+                        type => boolean,
+                        default => false    
+                    },
+                    hostname => #{
+                        type => string
+                    }
+                }
+            }
+        }
+    },
+
+    [ #{<<"authenticator">> => AuthenticatorDef}
+    , #{<<"password_based">> => PasswordBasedDef}
+    , #{<<"jwt">> => JWTDef}
+    , #{<<"scram">> => SCRAMDef}
+    , #{<<"password_based_built_in_database">> => PasswordBasedBuiltInDatabaseDef}
+    , #{<<"password_based_mysql">> => PasswordBasedMySQLDef}
+    , #{<<"password_based_pgsql">> => PasswordBasedPgSQLDef}
+    , #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
+    , #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
+    , #{<<"ssl">> => SSLDef}
+    ].

+ 57 - 55
apps/emqx_authn/src/simple_authn/emqx_authn_http.erl

@@ -26,13 +26,6 @@
         , validations/0
         ]).
 
--type accept() :: 'application/json' | 'application/x-www-form-urlencoded'.
--type content_type() :: accept().
-
--reflect_type([ accept/0
-              , content_type/0
-              ]).
-
 -export([ create/3
         , update/4
         , authenticate/2
@@ -53,45 +46,53 @@ fields("") ->
 
 fields(get) ->
     [ {method,          #{type => get,
-                          default => get}}
+                          default => post}}
+    , {headers,         fun headers_no_content_type/1}
     ] ++ common_fields();
 
 fields(post) ->
     [ {method,          #{type => post,
-                          default => get}}
-    , {content_type,    fun content_type/1}
+                          default => post}}
+    , {headers,         fun headers/1}
     ] ++ common_fields().
 
 common_fields() ->
     [ {server_type,     {enum, ['http-server']}}
     , {url,             fun url/1}
-    , {accept,          fun accept/1}
-    , {headers,         fun headers/1}
     , {form_data,       fun form_data/1}
     , {request_timeout, fun request_timeout/1}
-    ] ++ proplists:delete(base_url, emqx_connector_http:fields(config)).
+    ] ++ maps:to_list(maps:without([ base_url
+                                   , pool_type],
+                      maps:from_list(emqx_connector_http:fields(config)))).
 
 validations() ->
-    [ {check_ssl_opts, fun check_ssl_opts/1} ].
+    [ {check_ssl_opts, fun check_ssl_opts/1}
+    , {check_headers, fun check_headers/1}
+    ].
 
 url(type) -> binary();
 url(nullable) -> false;
 url(validate) -> [fun check_url/1];
 url(_) -> undefined.
 
-accept(type) -> accept();
-accept(default) -> 'application/json';
-accept(_) -> undefined.
-
-content_type(type) -> content_type();
-content_type(default) -> 'application/json';
-content_type(_) -> undefined.
-
-headers(type) -> list();
-headers(default) -> [];
+headers(type) -> map();
+headers(converter) ->
+    fun(Headers) ->
+       maps:merge(default_headers(), transform_header_name(Headers))
+    end;
+headers(default) -> default_headers();
 headers(_) -> undefined.
 
-form_data(type) -> binary();
+headers_no_content_type(type) -> map();
+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.
+
+%% TODO: Using map()
+form_data(type) -> map();
 form_data(nullable) -> false;
 form_data(validate) -> [fun check_form_data/1];
 form_data(_) -> undefined.
@@ -107,28 +108,17 @@ request_timeout(_) -> undefined.
 create(ChainID, AuthenticatorName,
         #{method := Method,
           url := URL,
-          accept := Accept,
           headers := Headers,
           form_data := FormData,
           request_timeout := RequestTimeout} = Config) ->
-    ContentType = maps:get(content_type, Config, undefined),
-    DefaultHeader0 = case ContentType of
-                         undefined -> #{};
-                         _ -> #{<<"content-type">> => atom_to_binary(ContentType, utf8)}
-                     end,
-    DefaultHeader = DefaultHeader0#{<<"accept">> => atom_to_binary(Accept, utf8)},
-    NHeaders = maps:to_list(maps:merge(DefaultHeader, maps:from_list(Headers))),
-    NFormData = preprocess_form_data(FormData),
     #{path := Path,
       query := Query} = URIMap = parse_url(URL),
     BaseURL = generate_base_url(URIMap),
     State = #{method          => Method,
               path            => Path,
               base_query      => cow_qs:parse_qs(list_to_binary(Query)),
-              accept          => Accept,
-              content_type    => ContentType,
-              headers         => NHeaders,
-              form_data       => NFormData,
+              headers         => maps:to_list(Headers),
+              form_data       => FormData,
               request_timeout => RequestTimeout},
     ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
     case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of
@@ -182,26 +172,38 @@ check_url(URL) ->
     end.
 
 check_form_data(FormData) ->
-    KVs = binary:split(FormData, [<<"&">>], [global]),
-    case false =:= lists:any(fun(T) -> T =:= <<>> end, KVs) of
-        true ->
-            NKVs = [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs],
-            false =:= 
-                lists:any(fun({K, V}) ->
-                              K =:= <<>> orelse V =:= <<>>;
-                             (_) ->
-                              true
-                          end, NKVs);
-        false ->
-            false
-    end.
+    lists:any(fun({_, V}) ->
+                  not is_binary(V)
+              end, maps:to_list(FormData)).
+
+default_headers() ->
+    maps:put(<<"content-type">>,
+             <<"application/json">>,
+             default_headers_no_content_type()).
+
+default_headers_no_content_type() ->
+    #{ <<"accept">> => <<"application/json">>
+     , <<"cache-control">> => <<"no-cache">>
+     , <<"connection">> => <<"keep-alive">>
+     , <<"keep-alive">> => <<"timeout=5">>
+     }.
+
+transform_header_name(Headers) ->
+    maps:fold(fun(K0, V, Acc) ->
+                  K = list_to_binary(string:to_lower(binary_to_list(K0))),
+                  maps:put(K, V, Acc)
+              end, #{}, Headers).
 
 check_ssl_opts(Conf) ->
     emqx_connector_http:check_ssl_opts("url", Conf).
 
-preprocess_form_data(FormData) ->
-    KVs = binary:split(FormData, [<<"&">>], [global]),
-    [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs].
+check_headers(Conf) ->
+    Method = hocon_schema:get_value("method", Conf),
+    Headers = hocon_schema:get_value("headers", Conf),
+    case Method =:= get andalso maps:get(<<"content-type">>, Headers, undefined) =/= undefined of
+        true -> false;
+        false -> true
+    end.
 
 parse_url(URL) ->
     {ok, URIMap} = emqx_http_lib:uri_parse(URL),
@@ -220,7 +222,6 @@ generate_base_url(#{scheme := Scheme,
 generate_request(Credential, #{method := Method,
                                path := Path,
                                base_query := BaseQuery,
-                               content_type := ContentType,
                                headers := Headers,
                                form_data := FormData0}) ->
     FormData = replace_placeholders(FormData0, Credential),
@@ -230,6 +231,7 @@ generate_request(Credential, #{method := Method,
             {NPath, Headers};
         post ->
             NPath = append_query(Path, BaseQuery),
+            ContentType = proplists:get_value(<<"content-type">>, Headers),
             Body = serialize_body(ContentType, FormData),
             {NPath, Headers, Body}
     end.

+ 9 - 14
apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl

@@ -22,7 +22,6 @@
 
 -export([ structs/0
         , fields/1
-        , validations/0
         ]).
 
 -export([ create/3
@@ -81,19 +80,13 @@ fields(ssl_enable) ->
     ];
 
 fields(ssl_disable) ->
-    [ {enable, #{type => false}} ];
-
-fields(claim) ->
-    [ {"$name", fun expected_claim_value/1} ].
-
-validations() ->
-    [ {check_verify_claims, fun check_verify_claims/1} ].
+    [ {enable, #{type => false}} ].
 
 secret(type) -> string();
 secret(_) -> undefined.
 
 secret_base64_encoded(type) -> boolean();
-secret_base64_encoded(defualt) -> false;
+secret_base64_encoded(default) -> false;
 secret_base64_encoded(_) -> undefined.
 
 certificate(type) -> string();
@@ -123,13 +116,15 @@ verify(_) -> undefined.
 server_name_indication(type) -> string();
 server_name_indication(_) -> undefined.
 
-verify_claims(type) -> hoconsc:array(hoconsc:ref(claim));
-verify_claims(default) -> [];
+verify_claims(type) -> list();
+verify_claims(default) -> #{};
+verify_claims(validate) -> [fun check_verify_claims/1];
+verify_claims(converter) ->
+    fun(VerifyClaims) ->
+        maps:to_list(VerifyClaims)
+    end;
 verify_claims(_) -> undefined.
 
-expected_claim_value(type) -> string();
-expected_claim_value(_) -> undefined.
-
 %%------------------------------------------------------------------------------
 %% APIs
 %%------------------------------------------------------------------------------

+ 4 - 0
apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl

@@ -57,6 +57,10 @@ password_hash_algorithm(type) -> {union, [hoconsc:ref(bcrypt), hoconsc:ref(other
 password_hash_algorithm(default) -> #{<<"name">> => sha256};
 password_hash_algorithm(_) -> undefined.
 
+salt_rounds(type) -> integer();
+salt_rounds(default) -> 10;
+salt_rounds(_) -> undefined.
+
 salt_position(type) -> {enum, [prefix, suffix]};
 salt_position(default) -> prefix;
 salt_position(_) -> undefined.