fix(authn): fix some bugs
This commit is contained in:
parent
a5a596e3ac
commit
2a594b1a73
|
@ -16,338 +16,414 @@
|
||||||
|
|
||||||
-module(emqx_authn_api).
|
-module(emqx_authn_api).
|
||||||
|
|
||||||
|
-behavior(minirest_api).
|
||||||
|
|
||||||
-include("emqx_authn.hrl").
|
-include("emqx_authn.hrl").
|
||||||
|
|
||||||
-export([ create_authenticator/2
|
-export([ api_spec/0 ]).
|
||||||
, 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,
|
api_spec() ->
|
||||||
method => 'POST',
|
{[authenticator_api()], definitions()}.
|
||||||
path => "/authentication/authenticators",
|
|
||||||
func => create_authenticator,
|
|
||||||
descr => "Create authenticator"
|
|
||||||
}).
|
|
||||||
|
|
||||||
-rest_api(#{name => delete_authenticator,
|
authenticator_api() ->
|
||||||
method => 'DELETE',
|
Example1 = #{name => <<"example">>,
|
||||||
path => "/authentication/authenticators/:bin:name",
|
mechanism => <<"password-based">>,
|
||||||
func => delete_authenticator,
|
config => #{
|
||||||
descr => "Delete authenticator"
|
server_type => <<"built-in-example">>,
|
||||||
}).
|
user_id_type => <<"username">>,
|
||||||
|
password_hash_algorithm => #{
|
||||||
-rest_api(#{name => update_authenticator,
|
name => <<"sha256">>
|
||||||
method => 'PUT',
|
}
|
||||||
path => "/authentication/authenticators/:bin:name",
|
}},
|
||||||
func => update_authenticator,
|
Example2 = #{name => <<"example">>,
|
||||||
descr => "Update authenticator"
|
mechanism => <<"password-based">>,
|
||||||
}).
|
config => #{
|
||||||
|
server_type => <<"http-server">>,
|
||||||
-rest_api(#{name => lookup_authenticator,
|
method => <<"post">>,
|
||||||
method => 'GET',
|
url => <<"http://localhost:80/login">>,
|
||||||
path => "/authentication/authenticators/:bin:name",
|
headers => #{
|
||||||
func => lookup_authenticator,
|
<<"content-type">> => <<"application/json">>
|
||||||
descr => "Lookup authenticator"
|
},
|
||||||
}).
|
form_data => #{
|
||||||
|
<<"username">> => <<"${mqtt-username}">>,
|
||||||
-rest_api(#{name => list_authenticators,
|
<<"password">> => <<"${mqtt-password}">>
|
||||||
method => 'GET',
|
}
|
||||||
path => "/authentication/authenticators",
|
}},
|
||||||
func => list_authenticators,
|
Example3 = #{name => <<"example">>,
|
||||||
descr => "List authenticators"
|
mechanism => <<"jwt">>,
|
||||||
}).
|
config => #{
|
||||||
|
use_jwks => false,
|
||||||
-rest_api(#{name => move_authenticator,
|
algorithm => <<"hmac-based">>,
|
||||||
method => 'POST',
|
secret => <<"mysecret">>,
|
||||||
path => "/authentication/authenticators/:bin:name/position",
|
secret_base64_encoded => false,
|
||||||
func => move_authenticator,
|
verify_claims => #{
|
||||||
descr => "Change the order of authenticators"
|
<<"username">> => <<"${mqtt-username}">>
|
||||||
}).
|
}
|
||||||
|
}},
|
||||||
-rest_api(#{name => import_users,
|
Metadata = #{
|
||||||
method => 'POST',
|
post => #{
|
||||||
path => "/authentication/authenticators/:bin:name/import-users",
|
description => "Create authenticator",
|
||||||
func => import_users,
|
requestBody => #{
|
||||||
descr => "Import users"
|
content => #{
|
||||||
}).
|
'application/json' => #{
|
||||||
|
schema => minirest:ref(<<"authenticator">>),
|
||||||
-rest_api(#{name => add_user,
|
examples => #{
|
||||||
method => 'POST',
|
default => #{
|
||||||
path => "/authentication/authenticators/:bin:name/users",
|
summary => <<"Default">>,
|
||||||
func => add_user,
|
value => emqx_json:encode(Example1)
|
||||||
descr => "Add user"
|
},
|
||||||
}).
|
http => #{
|
||||||
|
summary => <<"Authentication provided by HTTP Server">>,
|
||||||
-rest_api(#{name => delete_user,
|
value => emqx_json:encode(Example2)
|
||||||
method => 'DELETE',
|
},
|
||||||
path => "/authentication/authenticators/:bin:name/users/:bin:user_id",
|
jwt => #{
|
||||||
func => delete_user,
|
summary => <<"JWT Authentication">>,
|
||||||
descr => "Delete user"
|
value => emqx_json:encode(Example3)
|
||||||
}).
|
}
|
||||||
|
}
|
||||||
-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"
|
|
||||||
}).
|
|
||||||
|
|
||||||
%% 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)).
|
|
||||||
|
|
||||||
%% 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]
|
|
||||||
}},
|
|
||||||
#{
|
|
||||||
emqx_authn := #{
|
|
||||||
authenticators := [#{
|
|
||||||
config := NewConfig1
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
} = hocon_schema:check_plain(emqx_authn_schema, Config,
|
},
|
||||||
#{atom_key => true, nullable => true}),
|
responses => #{
|
||||||
case emqx_authn:update_authenticator(?CHAIN, Name, NewConfig1) of
|
<<"201">> => #{
|
||||||
{ok, NAuthenticator} ->
|
description => <<"Created successfully">>,
|
||||||
return({ok, NAuthenticator});
|
content => #{}
|
||||||
{error, Reason} ->
|
}
|
||||||
return(serialize_error(Reason))
|
}
|
||||||
end;
|
}
|
||||||
{error, Reason} ->
|
},
|
||||||
return(serialize_error(Reason))
|
{"/authentication/authenticators", Metadata, clients}.
|
||||||
end.
|
|
||||||
|
|
||||||
lookup_authenticator(Binding, Params) ->
|
definitions() ->
|
||||||
do_lookup_authenticator(uri_decode(Binding), maps:from_list(Params)).
|
AuthenticatorDef = #{
|
||||||
|
allOf => [
|
||||||
|
#{
|
||||||
|
type => object,
|
||||||
|
required => [name],
|
||||||
|
properties => #{
|
||||||
|
name => #{
|
||||||
|
type => string,
|
||||||
|
example => "exmaple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
oneOf => [ minirest:ref(<<"password_based">>)
|
||||||
|
, minirest:ref(<<"jwt">>)
|
||||||
|
, minirest:ref(<<"scram">>)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
do_lookup_authenticator(#{name := Name}, _Params) ->
|
PasswordBasedDef = #{
|
||||||
case emqx_authn:lookup_authenticator(?CHAIN, Name) of
|
type => object,
|
||||||
{ok, Authenticator} ->
|
properties => #{
|
||||||
return({ok, Authenticator});
|
mechanism => #{
|
||||||
{error, Reason} ->
|
type => string,
|
||||||
return(serialize_error(Reason))
|
enum => [<<"password-based">>],
|
||||||
end.
|
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">>)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
list_authenticators(Binding, Params) ->
|
JWTDef = #{
|
||||||
do_list_authenticators(uri_decode(Binding), maps:from_list(Params)).
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
do_list_authenticators(_Binding, _Params) ->
|
PasswordBasedBuiltInDatabaseDef = #{
|
||||||
case emqx_authn:list_authenticators(?CHAIN) of
|
type => object,
|
||||||
{ok, Authenticators} ->
|
properties => #{
|
||||||
return({ok, Authenticators});
|
server_type => #{
|
||||||
{error, Reason} ->
|
type => string,
|
||||||
return(serialize_error(Reason))
|
enum => [<<"built-in-database">>],
|
||||||
end.
|
example => <<"built-in-database">>
|
||||||
|
},
|
||||||
|
user_id_type => #{
|
||||||
|
type => string,
|
||||||
|
enum => [<<"username">>, <<"clientid">>],
|
||||||
|
default => <<"username">>,
|
||||||
|
example => <<"username">>
|
||||||
|
},
|
||||||
|
password_hash_algorithm => minirest:ref(<<"password_hash_algorithm">>)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
move_authenticator(Binding, Params) ->
|
PasswordBasedMySQLDef = #{
|
||||||
do_move_authenticator(uri_decode(Binding), maps:from_list(Params)).
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the front">>}) ->
|
PasswordBasedPgSQLDef = #{
|
||||||
case emqx_authn:move_authenticator_to_the_front(?CHAIN, Name) of
|
type => object,
|
||||||
ok ->
|
properties => #{
|
||||||
return(ok);
|
server_type => #{
|
||||||
{error, Reason} ->
|
type => string,
|
||||||
return(serialize_error(Reason))
|
enum => [<<"pgsql">>],
|
||||||
end;
|
example => <<"pgsql">>
|
||||||
do_move_authenticator(#{name := Name}, #{<<"position">> := <<"the end">>}) ->
|
},
|
||||||
case emqx_authn:move_authenticator_to_the_end(?CHAIN, Name) of
|
server => #{
|
||||||
ok ->
|
type => string,
|
||||||
return(ok);
|
example => <<"localhost:5432">>
|
||||||
{error, Reason} ->
|
},
|
||||||
return(serialize_error(Reason))
|
database => #{
|
||||||
end;
|
type => string
|
||||||
do_move_authenticator(#{name := Name}, #{<<"position">> := N}) when is_number(N) ->
|
},
|
||||||
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, Name, N) of
|
pool_size => #{
|
||||||
ok ->
|
type => integer,
|
||||||
return(ok);
|
default => 8
|
||||||
{error, Reason} ->
|
},
|
||||||
return(serialize_error(Reason))
|
username => #{
|
||||||
end;
|
type => string
|
||||||
do_move_authenticator(_Binding, _Params) ->
|
},
|
||||||
return(serialize_error({missing_parameter, <<"position">>})).
|
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}">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
import_users(Binding, Params) ->
|
PasswordBasedHTTPServerDef = #{
|
||||||
do_import_users(uri_decode(Binding), maps:from_list(Params)).
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
do_import_users(#{name := Name},
|
PasswordHashAlgorithmDef = #{
|
||||||
#{<<"filename">> := Filename}) ->
|
type => object,
|
||||||
case emqx_authn:import_users(?CHAIN, Name, Filename) of
|
required => [name],
|
||||||
ok ->
|
properties => #{
|
||||||
return(ok);
|
name => #{
|
||||||
{error, Reason} ->
|
type => string,
|
||||||
return(serialize_error(Reason))
|
enum => [<<"plain">>, <<"md5">>, <<"sha">>, <<"sha256">>, <<"sha512">>, <<"bcrypt">>],
|
||||||
end;
|
default => <<"sha256">>
|
||||||
do_import_users(_Binding, Params) ->
|
},
|
||||||
Missed = get_missed_params(Params, [<<"filename">>, <<"file_format">>]),
|
salt_rounds => #{
|
||||||
return(serialize_error({missing_parameter, Missed})).
|
type => integer,
|
||||||
|
default => 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
add_user(Binding, Params) ->
|
SSLDef = #{
|
||||||
do_add_user(uri_decode(Binding), maps:from_list(Params)).
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
do_add_user(#{name := Name}, UserInfo) ->
|
[ #{<<"authenticator">> => AuthenticatorDef}
|
||||||
case emqx_authn:add_user(?CHAIN, Name, UserInfo) of
|
, #{<<"password_based">> => PasswordBasedDef}
|
||||||
{ok, User} ->
|
, #{<<"jwt">> => JWTDef}
|
||||||
return({ok, User});
|
, #{<<"scram">> => SCRAMDef}
|
||||||
{error, Reason} ->
|
, #{<<"password_based_built_in_database">> => PasswordBasedBuiltInDatabaseDef}
|
||||||
return(serialize_error(Reason))
|
, #{<<"password_based_mysql">> => PasswordBasedMySQLDef}
|
||||||
end.
|
, #{<<"password_based_pgsql">> => PasswordBasedPgSQLDef}
|
||||||
|
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
|
||||||
delete_user(Binding, Params) ->
|
, #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
|
||||||
do_delete_user(uri_decode(Binding), maps:from_list(Params)).
|
, #{<<"ssl">> => SSLDef}
|
||||||
|
].
|
||||||
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.
|
|
|
@ -26,13 +26,6 @@
|
||||||
, validations/0
|
, 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
|
-export([ create/3
|
||||||
, update/4
|
, update/4
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
|
@ -53,45 +46,53 @@ fields("") ->
|
||||||
|
|
||||||
fields(get) ->
|
fields(get) ->
|
||||||
[ {method, #{type => get,
|
[ {method, #{type => get,
|
||||||
default => get}}
|
default => post}}
|
||||||
|
, {headers, fun headers_no_content_type/1}
|
||||||
] ++ common_fields();
|
] ++ common_fields();
|
||||||
|
|
||||||
fields(post) ->
|
fields(post) ->
|
||||||
[ {method, #{type => post,
|
[ {method, #{type => post,
|
||||||
default => get}}
|
default => post}}
|
||||||
, {content_type, fun content_type/1}
|
, {headers, fun headers/1}
|
||||||
] ++ common_fields().
|
] ++ common_fields().
|
||||||
|
|
||||||
common_fields() ->
|
common_fields() ->
|
||||||
[ {server_type, {enum, ['http-server']}}
|
[ {server_type, {enum, ['http-server']}}
|
||||||
, {url, fun url/1}
|
, {url, fun url/1}
|
||||||
, {accept, fun accept/1}
|
|
||||||
, {headers, fun headers/1}
|
|
||||||
, {form_data, fun form_data/1}
|
, {form_data, fun form_data/1}
|
||||||
, {request_timeout, fun request_timeout/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() ->
|
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(type) -> binary();
|
||||||
url(nullable) -> false;
|
url(nullable) -> false;
|
||||||
url(validate) -> [fun check_url/1];
|
url(validate) -> [fun check_url/1];
|
||||||
url(_) -> undefined.
|
url(_) -> undefined.
|
||||||
|
|
||||||
accept(type) -> accept();
|
headers(type) -> map();
|
||||||
accept(default) -> 'application/json';
|
headers(converter) ->
|
||||||
accept(_) -> undefined.
|
fun(Headers) ->
|
||||||
|
maps:merge(default_headers(), transform_header_name(Headers))
|
||||||
content_type(type) -> content_type();
|
end;
|
||||||
content_type(default) -> 'application/json';
|
headers(default) -> default_headers();
|
||||||
content_type(_) -> undefined.
|
|
||||||
|
|
||||||
headers(type) -> list();
|
|
||||||
headers(default) -> [];
|
|
||||||
headers(_) -> undefined.
|
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(nullable) -> false;
|
||||||
form_data(validate) -> [fun check_form_data/1];
|
form_data(validate) -> [fun check_form_data/1];
|
||||||
form_data(_) -> undefined.
|
form_data(_) -> undefined.
|
||||||
|
@ -107,28 +108,17 @@ request_timeout(_) -> undefined.
|
||||||
create(ChainID, AuthenticatorName,
|
create(ChainID, AuthenticatorName,
|
||||||
#{method := Method,
|
#{method := Method,
|
||||||
url := URL,
|
url := URL,
|
||||||
accept := Accept,
|
|
||||||
headers := Headers,
|
headers := Headers,
|
||||||
form_data := FormData,
|
form_data := FormData,
|
||||||
request_timeout := RequestTimeout} = Config) ->
|
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,
|
#{path := Path,
|
||||||
query := Query} = URIMap = parse_url(URL),
|
query := Query} = URIMap = parse_url(URL),
|
||||||
BaseURL = generate_base_url(URIMap),
|
BaseURL = generate_base_url(URIMap),
|
||||||
State = #{method => Method,
|
State = #{method => Method,
|
||||||
path => Path,
|
path => Path,
|
||||||
base_query => cow_qs:parse_qs(list_to_binary(Query)),
|
base_query => cow_qs:parse_qs(list_to_binary(Query)),
|
||||||
accept => Accept,
|
headers => maps:to_list(Headers),
|
||||||
content_type => ContentType,
|
form_data => FormData,
|
||||||
headers => NHeaders,
|
|
||||||
form_data => NFormData,
|
|
||||||
request_timeout => RequestTimeout},
|
request_timeout => RequestTimeout},
|
||||||
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
|
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
|
||||||
case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of
|
case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of
|
||||||
|
@ -182,26 +172,38 @@ check_url(URL) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
check_form_data(FormData) ->
|
check_form_data(FormData) ->
|
||||||
KVs = binary:split(FormData, [<<"&">>], [global]),
|
lists:any(fun({_, V}) ->
|
||||||
case false =:= lists:any(fun(T) -> T =:= <<>> end, KVs) of
|
not is_binary(V)
|
||||||
true ->
|
end, maps:to_list(FormData)).
|
||||||
NKVs = [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs],
|
|
||||||
false =:=
|
default_headers() ->
|
||||||
lists:any(fun({K, V}) ->
|
maps:put(<<"content-type">>,
|
||||||
K =:= <<>> orelse V =:= <<>>;
|
<<"application/json">>,
|
||||||
(_) ->
|
default_headers_no_content_type()).
|
||||||
true
|
|
||||||
end, NKVs);
|
default_headers_no_content_type() ->
|
||||||
false ->
|
#{ <<"accept">> => <<"application/json">>
|
||||||
false
|
, <<"cache-control">> => <<"no-cache">>
|
||||||
end.
|
, <<"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) ->
|
check_ssl_opts(Conf) ->
|
||||||
emqx_connector_http:check_ssl_opts("url", Conf).
|
emqx_connector_http:check_ssl_opts("url", Conf).
|
||||||
|
|
||||||
preprocess_form_data(FormData) ->
|
check_headers(Conf) ->
|
||||||
KVs = binary:split(FormData, [<<"&">>], [global]),
|
Method = hocon_schema:get_value("method", Conf),
|
||||||
[list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs].
|
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) ->
|
parse_url(URL) ->
|
||||||
{ok, URIMap} = emqx_http_lib:uri_parse(URL),
|
{ok, URIMap} = emqx_http_lib:uri_parse(URL),
|
||||||
|
@ -220,7 +222,6 @@ generate_base_url(#{scheme := Scheme,
|
||||||
generate_request(Credential, #{method := Method,
|
generate_request(Credential, #{method := Method,
|
||||||
path := Path,
|
path := Path,
|
||||||
base_query := BaseQuery,
|
base_query := BaseQuery,
|
||||||
content_type := ContentType,
|
|
||||||
headers := Headers,
|
headers := Headers,
|
||||||
form_data := FormData0}) ->
|
form_data := FormData0}) ->
|
||||||
FormData = replace_placeholders(FormData0, Credential),
|
FormData = replace_placeholders(FormData0, Credential),
|
||||||
|
@ -230,6 +231,7 @@ generate_request(Credential, #{method := Method,
|
||||||
{NPath, Headers};
|
{NPath, Headers};
|
||||||
post ->
|
post ->
|
||||||
NPath = append_query(Path, BaseQuery),
|
NPath = append_query(Path, BaseQuery),
|
||||||
|
ContentType = proplists:get_value(<<"content-type">>, Headers),
|
||||||
Body = serialize_body(ContentType, FormData),
|
Body = serialize_body(ContentType, FormData),
|
||||||
{NPath, Headers, Body}
|
{NPath, Headers, Body}
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
-export([ structs/0
|
-export([ structs/0
|
||||||
, fields/1
|
, fields/1
|
||||||
, validations/0
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/3
|
||||||
|
@ -81,19 +80,13 @@ fields(ssl_enable) ->
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(ssl_disable) ->
|
fields(ssl_disable) ->
|
||||||
[ {enable, #{type => false}} ];
|
[ {enable, #{type => false}} ].
|
||||||
|
|
||||||
fields(claim) ->
|
|
||||||
[ {"$name", fun expected_claim_value/1} ].
|
|
||||||
|
|
||||||
validations() ->
|
|
||||||
[ {check_verify_claims, fun check_verify_claims/1} ].
|
|
||||||
|
|
||||||
secret(type) -> string();
|
secret(type) -> string();
|
||||||
secret(_) -> undefined.
|
secret(_) -> undefined.
|
||||||
|
|
||||||
secret_base64_encoded(type) -> boolean();
|
secret_base64_encoded(type) -> boolean();
|
||||||
secret_base64_encoded(defualt) -> false;
|
secret_base64_encoded(default) -> false;
|
||||||
secret_base64_encoded(_) -> undefined.
|
secret_base64_encoded(_) -> undefined.
|
||||||
|
|
||||||
certificate(type) -> string();
|
certificate(type) -> string();
|
||||||
|
@ -123,13 +116,15 @@ verify(_) -> undefined.
|
||||||
server_name_indication(type) -> string();
|
server_name_indication(type) -> string();
|
||||||
server_name_indication(_) -> undefined.
|
server_name_indication(_) -> undefined.
|
||||||
|
|
||||||
verify_claims(type) -> hoconsc:array(hoconsc:ref(claim));
|
verify_claims(type) -> list();
|
||||||
verify_claims(default) -> [];
|
verify_claims(default) -> #{};
|
||||||
|
verify_claims(validate) -> [fun check_verify_claims/1];
|
||||||
|
verify_claims(converter) ->
|
||||||
|
fun(VerifyClaims) ->
|
||||||
|
maps:to_list(VerifyClaims)
|
||||||
|
end;
|
||||||
verify_claims(_) -> undefined.
|
verify_claims(_) -> undefined.
|
||||||
|
|
||||||
expected_claim_value(type) -> string();
|
|
||||||
expected_claim_value(_) -> undefined.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -57,6 +57,10 @@ password_hash_algorithm(type) -> {union, [hoconsc:ref(bcrypt), hoconsc:ref(other
|
||||||
password_hash_algorithm(default) -> #{<<"name">> => sha256};
|
password_hash_algorithm(default) -> #{<<"name">> => sha256};
|
||||||
password_hash_algorithm(_) -> undefined.
|
password_hash_algorithm(_) -> undefined.
|
||||||
|
|
||||||
|
salt_rounds(type) -> integer();
|
||||||
|
salt_rounds(default) -> 10;
|
||||||
|
salt_rounds(_) -> undefined.
|
||||||
|
|
||||||
salt_position(type) -> {enum, [prefix, suffix]};
|
salt_position(type) -> {enum, [prefix, suffix]};
|
||||||
salt_position(default) -> prefix;
|
salt_position(default) -> prefix;
|
||||||
salt_position(_) -> undefined.
|
salt_position(_) -> undefined.
|
||||||
|
|
Loading…
Reference in New Issue