emqx_authn_http_SUITE.erl 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_authn_http_SUITE).
  17. -compile(nowarn_export_all).
  18. -compile(export_all).
  19. -include_lib("emqx_auth/include/emqx_authn.hrl").
  20. -include_lib("eunit/include/eunit.hrl").
  21. -include_lib("common_test/include/ct.hrl").
  22. -include_lib("emqx/include/emqx_placeholder.hrl").
  23. -define(PATH, [?CONF_NS_ATOM]).
  24. -define(HTTP_PORT, 32333).
  25. -define(HTTP_PATH, "/auth/[...]").
  26. -define(CREDENTIALS, #{
  27. clientid => <<"clienta">>,
  28. username => <<"plain">>,
  29. password => <<"plain">>,
  30. peerhost => {127, 0, 0, 1},
  31. listener => 'tcp:default',
  32. protocol => mqtt,
  33. cert_subject => <<"cert_subject_data">>,
  34. cert_common_name => <<"cert_common_name_data">>,
  35. client_attrs => #{<<"group">> => <<"g1">>}
  36. }).
  37. -define(SERVER_RESPONSE_JSON(Result), ?SERVER_RESPONSE_JSON(Result, false)).
  38. -define(SERVER_RESPONSE_JSON(Result, IsSuperuser),
  39. emqx_utils_json:encode(#{
  40. result => Result,
  41. is_superuser => IsSuperuser
  42. })
  43. ).
  44. -define(SERVER_RESPONSE_URLENCODE(Result, IsSuperuser),
  45. list_to_binary(
  46. "result=" ++
  47. uri_encode(Result) ++ "&" ++
  48. "is_superuser=" ++
  49. uri_encode(IsSuperuser)
  50. )
  51. ).
  52. -define(EXCEPTION_ALLOW, ?EXCEPTION_ALLOW(false)).
  53. -define(EXCEPTION_ALLOW(IsSuperuser), {ok, #{is_superuser := IsSuperuser}}).
  54. -define(EXCEPTION_DENY, {error, not_authorized}).
  55. -define(EXCEPTION_IGNORE, ignore).
  56. all() ->
  57. emqx_common_test_helpers:all(?MODULE).
  58. init_per_suite(Config) ->
  59. Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_auth, emqx_auth_http], #{
  60. work_dir => ?config(priv_dir, Config)
  61. }),
  62. [{apps, Apps} | Config].
  63. end_per_suite(Config) ->
  64. emqx_authn_test_lib:delete_authenticators(
  65. [authentication],
  66. ?GLOBAL
  67. ),
  68. ok = emqx_cth_suite:stop(?config(apps, Config)),
  69. ok.
  70. init_per_testcase(_Case, Config) ->
  71. {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
  72. emqx_authn_test_lib:delete_authenticators(
  73. [authentication],
  74. ?GLOBAL
  75. ),
  76. {ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH),
  77. Config.
  78. end_per_testcase(_Case, _Config) ->
  79. ok = emqx_authn_http_test_server:stop().
  80. %%------------------------------------------------------------------------------
  81. %% Tests
  82. %%------------------------------------------------------------------------------
  83. t_create(_Config) ->
  84. AuthConfig = raw_http_auth_config(),
  85. {ok, _} = emqx:update_config(
  86. ?PATH,
  87. {create_authenticator, ?GLOBAL, AuthConfig}
  88. ),
  89. {ok, [#{provider := emqx_authn_http}]} = emqx_authn_chains:list_authenticators(?GLOBAL).
  90. t_create_invalid(_Config) ->
  91. AuthConfig = raw_http_auth_config(),
  92. InvalidConfigs =
  93. [
  94. AuthConfig#{<<"headers">> => []},
  95. AuthConfig#{<<"method">> => <<"delete">>}
  96. ],
  97. lists:foreach(
  98. fun(Config) ->
  99. ct:pal("creating authenticator with invalid config: ~p", [Config]),
  100. {error, _} =
  101. try
  102. emqx:update_config(
  103. ?PATH,
  104. {create_authenticator, ?GLOBAL, Config}
  105. )
  106. catch
  107. throw:Error ->
  108. {error, Error}
  109. end,
  110. ?assertEqual(
  111. {error, {not_found, {chain, ?GLOBAL}}},
  112. emqx_authn_chains:list_authenticators(?GLOBAL)
  113. )
  114. end,
  115. InvalidConfigs
  116. ).
  117. t_authenticate(_Config) ->
  118. ok = lists:foreach(
  119. fun(Sample) ->
  120. ct:pal("test_user_auth sample: ~p", [Sample]),
  121. test_user_auth(Sample)
  122. end,
  123. samples()
  124. ).
  125. test_user_auth(#{
  126. handler := Handler,
  127. config_params := SpecificConfgParams,
  128. result := Expect
  129. }) ->
  130. Result = perform_user_auth(SpecificConfgParams, Handler, ?CREDENTIALS),
  131. ?assertEqual(Expect, Result).
  132. perform_user_auth(SpecificConfgParams, Handler, Credentials) ->
  133. AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams),
  134. {ok, _} = emqx:update_config(
  135. ?PATH,
  136. {create_authenticator, ?GLOBAL, AuthConfig}
  137. ),
  138. ok = emqx_authn_http_test_server:set_handler(Handler),
  139. Result = emqx_access_control:authenticate(Credentials),
  140. emqx_authn_test_lib:delete_authenticators(
  141. [authentication],
  142. ?GLOBAL
  143. ),
  144. Result.
  145. t_authenticate_path_placeholders(_Config) ->
  146. ok = emqx_authn_http_test_server:set_handler(
  147. fun(Req0, State) ->
  148. Req =
  149. case cowboy_req:path(Req0) of
  150. <<"/auth/p%20ath//us%20er/auth//">> ->
  151. cowboy_req:reply(
  152. 200,
  153. #{<<"content-type">> => <<"application/json">>},
  154. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  155. Req0
  156. );
  157. Path ->
  158. ct:pal("Unexpected path: ~p", [Path]),
  159. cowboy_req:reply(403, Req0)
  160. end,
  161. {ok, Req, State}
  162. end
  163. ),
  164. Credentials = ?CREDENTIALS#{
  165. username => <<"us er">>
  166. },
  167. AuthConfig = maps:merge(
  168. raw_http_auth_config(),
  169. #{
  170. <<"url">> => <<"http://127.0.0.1:32333/auth/p%20ath//${username}/auth//">>,
  171. <<"body">> => #{}
  172. }
  173. ),
  174. {ok, _} = emqx:update_config(
  175. ?PATH,
  176. {create_authenticator, ?GLOBAL, AuthConfig}
  177. ),
  178. ?assertMatch(
  179. {ok, #{is_superuser := false}},
  180. emqx_access_control:authenticate(Credentials)
  181. ),
  182. _ = emqx_authn_test_lib:delete_authenticators(
  183. [authentication],
  184. ?GLOBAL
  185. ).
  186. t_no_value_for_placeholder(_Config) ->
  187. Handler = fun(Req0, State) ->
  188. {ok, RawBody, Req1} = cowboy_req:read_body(Req0),
  189. #{
  190. <<"cert_subject">> := <<"">>,
  191. <<"cert_common_name">> := <<"">>
  192. } = emqx_utils_json:decode(RawBody, [return_maps]),
  193. Req = cowboy_req:reply(
  194. 200,
  195. #{<<"content-type">> => <<"application/json">>},
  196. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  197. Req1
  198. ),
  199. {ok, Req, State}
  200. end,
  201. SpecificConfgParams = #{
  202. <<"method">> => <<"post">>,
  203. <<"headers">> => #{<<"content-type">> => <<"application/json">>},
  204. <<"body">> => #{
  205. <<"cert_subject">> => ?PH_CERT_SUBJECT,
  206. <<"cert_common_name">> => ?PH_CERT_CN_NAME
  207. }
  208. },
  209. AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams),
  210. {ok, _} = emqx:update_config(
  211. ?PATH,
  212. {create_authenticator, ?GLOBAL, AuthConfig}
  213. ),
  214. ok = emqx_authn_http_test_server:set_handler(Handler),
  215. Credentials = maps:without([cert_subject, cert_common_name], ?CREDENTIALS),
  216. ?assertMatch({ok, _}, emqx_access_control:authenticate(Credentials)),
  217. emqx_authn_test_lib:delete_authenticators(
  218. [authentication],
  219. ?GLOBAL
  220. ).
  221. t_disallowed_placeholders_preserved(_Config) ->
  222. Config = #{
  223. <<"method">> => <<"post">>,
  224. <<"headers">> => #{<<"content-type">> => <<"application/json">>},
  225. <<"body">> => #{
  226. <<"username">> => ?PH_USERNAME,
  227. <<"password">> => ?PH_PASSWORD,
  228. <<"this">> => <<"${whatisthis}">>
  229. }
  230. },
  231. Handler = fun(Req0, State) ->
  232. {ok, Body, Req1} = cowboy_req:read_body(Req0),
  233. #{
  234. <<"username">> := <<"plain">>,
  235. <<"password">> := <<"plain">>,
  236. <<"this">> := <<"${whatisthis}">>
  237. } = emqx_utils_json:decode(Body),
  238. Req = cowboy_req:reply(
  239. 200,
  240. #{<<"content-type">> => <<"application/json">>},
  241. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  242. Req1
  243. ),
  244. {ok, Req, State}
  245. end,
  246. ?assertMatch({ok, _}, perform_user_auth(Config, Handler, ?CREDENTIALS)),
  247. % NOTE: disallowed placeholder left intact, which makes the URL invalid
  248. ConfigUrl = Config#{
  249. <<"url">> => <<"http://127.0.0.1:32333/auth/${whatisthis}">>
  250. },
  251. ?assertMatch({error, _}, perform_user_auth(ConfigUrl, Handler, ?CREDENTIALS)).
  252. t_destroy(_Config) ->
  253. AuthConfig = raw_http_auth_config(),
  254. {ok, _} = emqx:update_config(
  255. ?PATH,
  256. {create_authenticator, ?GLOBAL, AuthConfig}
  257. ),
  258. Headers = #{<<"content-type">> => <<"application/json">>},
  259. Response = ?SERVER_RESPONSE_JSON(allow),
  260. ok = emqx_authn_http_test_server:set_handler(
  261. fun(Req0, State) ->
  262. Req = cowboy_req:reply(200, Headers, Response, Req0),
  263. {ok, Req, State}
  264. end
  265. ),
  266. {ok, [#{provider := emqx_authn_http, state := State}]} =
  267. emqx_authn_chains:list_authenticators(?GLOBAL),
  268. Credentials = maps:with([username, password], ?CREDENTIALS),
  269. ?assertMatch(
  270. ?EXCEPTION_ALLOW,
  271. emqx_authn_http:authenticate(
  272. Credentials,
  273. State
  274. )
  275. ),
  276. emqx_authn_test_lib:delete_authenticators(
  277. [authentication],
  278. ?GLOBAL
  279. ),
  280. % Authenticator should not be usable anymore
  281. ?assertMatch(
  282. ?EXCEPTION_IGNORE,
  283. emqx_authn_http:authenticate(
  284. Credentials,
  285. State
  286. )
  287. ).
  288. t_update(_Config) ->
  289. CorrectConfig = raw_http_auth_config(),
  290. IncorrectConfig =
  291. CorrectConfig#{<<"url">> => <<"http://127.0.0.1:32333/invalid">>},
  292. {ok, _} = emqx:update_config(
  293. ?PATH,
  294. {create_authenticator, ?GLOBAL, IncorrectConfig}
  295. ),
  296. Headers = #{<<"content-type">> => <<"application/json">>},
  297. Response = ?SERVER_RESPONSE_JSON(allow),
  298. ok = emqx_authn_http_test_server:set_handler(
  299. fun(Req0, State) ->
  300. Req = cowboy_req:reply(200, Headers, Response, Req0),
  301. {ok, Req, State}
  302. end
  303. ),
  304. ?assertMatch(
  305. ?EXCEPTION_DENY,
  306. emqx_access_control:authenticate(?CREDENTIALS)
  307. ),
  308. % We update with config with correct query, provider should update and work properly
  309. {ok, _} = emqx:update_config(
  310. ?PATH,
  311. {update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}
  312. ),
  313. ?assertMatch(
  314. ?EXCEPTION_ALLOW,
  315. emqx_access_control:authenticate(?CREDENTIALS)
  316. ).
  317. t_is_superuser(_Config) ->
  318. Config = raw_http_auth_config(),
  319. {ok, _} = emqx:update_config(
  320. ?PATH,
  321. {create_authenticator, ?GLOBAL, Config}
  322. ),
  323. Checks = [
  324. %% {ContentType, ExpectedIsSuperuser, ResponseIsSuperuser}
  325. %% Is Superuser
  326. {json, true, <<"1">>},
  327. {json, true, 1},
  328. {json, true, 123},
  329. {json, true, <<"true">>},
  330. {json, true, true},
  331. %% Not Superuser
  332. {json, false, <<"">>},
  333. {json, false, <<"0">>},
  334. {json, false, 0},
  335. {json, false, null},
  336. {json, false, undefined},
  337. {json, false, <<"false">>},
  338. {json, false, false},
  339. {json, false, <<"val">>},
  340. %% Is Superuser
  341. {form, true, <<"1">>},
  342. {form, true, 1},
  343. {form, true, 123},
  344. {form, true, <<"true">>},
  345. {form, true, true},
  346. %% Not Superuser
  347. {form, false, <<"">>},
  348. {form, false, <<"0">>},
  349. {form, false, 0},
  350. {form, false, null},
  351. {form, false, undefined},
  352. {form, false, <<"false">>},
  353. {form, false, false},
  354. {form, false, <<"val">>}
  355. ],
  356. lists:foreach(fun test_is_superuser/1, Checks).
  357. test_is_superuser({Kind, ExpectedValue, ServerResponse}) ->
  358. {ContentType, Res} =
  359. case Kind of
  360. json ->
  361. {<<"application/json; charset=utf-8">>,
  362. ?SERVER_RESPONSE_JSON(allow, ServerResponse)};
  363. form ->
  364. {<<"application/x-www-form-urlencoded; charset=utf-8">>,
  365. ?SERVER_RESPONSE_URLENCODE(allow, ServerResponse)}
  366. end,
  367. ok = emqx_authn_http_test_server:set_handler(
  368. fun(Req0, State) ->
  369. Req = cowboy_req:reply(
  370. 200,
  371. #{<<"content-type">> => ContentType},
  372. Res,
  373. Req0
  374. ),
  375. {ok, Req, State}
  376. end
  377. ),
  378. ?assertMatch(
  379. ?EXCEPTION_ALLOW(ExpectedValue),
  380. emqx_access_control:authenticate(?CREDENTIALS)
  381. ).
  382. t_ignore_allow_deny(_Config) ->
  383. Config = raw_http_auth_config(),
  384. {ok, _} = emqx:update_config(
  385. ?PATH,
  386. {create_authenticator, ?GLOBAL, Config}
  387. ),
  388. Checks = [
  389. %% only one chain, ignore by authn http and deny by default
  390. {deny, ?SERVER_RESPONSE_JSON(ignore)},
  391. {{allow, true}, ?SERVER_RESPONSE_JSON(allow, true)},
  392. {{allow, false}, ?SERVER_RESPONSE_JSON(allow)},
  393. {{allow, false}, ?SERVER_RESPONSE_JSON(allow, false)},
  394. {deny, ?SERVER_RESPONSE_JSON(deny)},
  395. {deny, ?SERVER_RESPONSE_JSON(deny, true)},
  396. {deny, ?SERVER_RESPONSE_JSON(deny, false)}
  397. ],
  398. lists:foreach(fun test_ignore_allow_deny/1, Checks).
  399. test_ignore_allow_deny({ExpectedValue, ServerResponse}) ->
  400. ok = emqx_authn_http_test_server:set_handler(
  401. fun(Req0, State) ->
  402. Req = cowboy_req:reply(
  403. 200,
  404. #{<<"content-type">> => <<"application/json">>},
  405. ServerResponse,
  406. Req0
  407. ),
  408. {ok, Req, State}
  409. end
  410. ),
  411. case ExpectedValue of
  412. {allow, IsSuperuser} ->
  413. ?assertMatch(
  414. ?EXCEPTION_ALLOW(IsSuperuser),
  415. emqx_access_control:authenticate(?CREDENTIALS)
  416. );
  417. deny ->
  418. ?assertMatch(
  419. ?EXCEPTION_DENY,
  420. emqx_access_control:authenticate(?CREDENTIALS)
  421. )
  422. end.
  423. %%------------------------------------------------------------------------------
  424. %% Helpers
  425. %%------------------------------------------------------------------------------
  426. raw_http_auth_config() ->
  427. #{
  428. <<"mechanism">> => <<"password_based">>,
  429. <<"enable">> => <<"true">>,
  430. <<"backend">> => <<"http">>,
  431. <<"method">> => <<"get">>,
  432. <<"url">> => <<"http://127.0.0.1:32333/auth">>,
  433. <<"body">> => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD},
  434. <<"headers">> => #{<<"X-Test-Header">> => <<"Test Value">>}
  435. }.
  436. samples() ->
  437. [
  438. %% simple get request
  439. #{
  440. handler => fun(Req0, State) ->
  441. #{
  442. username := <<"plain">>,
  443. password := <<"plain">>
  444. } = cowboy_req:match_qs([username, password], Req0),
  445. Req = cowboy_req:reply(
  446. 200,
  447. #{<<"content-type">> => <<"application/json">>},
  448. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  449. Req0
  450. ),
  451. {ok, Req, State}
  452. end,
  453. config_params => #{},
  454. result => {ok, #{is_superuser => false, client_attrs => #{}}}
  455. },
  456. %% get request with json body response
  457. #{
  458. handler => fun(Req0, State) ->
  459. Req = cowboy_req:reply(
  460. 200,
  461. #{<<"content-type">> => <<"application/json">>},
  462. emqx_utils_json:encode(#{
  463. result => allow,
  464. is_superuser => true,
  465. client_attrs => #{
  466. fid => <<"n11">>,
  467. <<"#_bad_key">> => <<"v">>
  468. }
  469. }),
  470. Req0
  471. ),
  472. {ok, Req, State}
  473. end,
  474. config_params => #{},
  475. result => {ok, #{is_superuser => true, client_attrs => #{<<"fid">> => <<"n11">>}}}
  476. },
  477. %% get request with url-form-encoded body response
  478. #{
  479. handler => fun(Req0, State) ->
  480. Req = cowboy_req:reply(
  481. 200,
  482. #{
  483. <<"content-type">> =>
  484. <<"application/x-www-form-urlencoded">>
  485. },
  486. <<"is_superuser=true&result=allow">>,
  487. Req0
  488. ),
  489. {ok, Req, State}
  490. end,
  491. config_params => #{},
  492. result => {ok, #{is_superuser => true, client_attrs => #{}}}
  493. },
  494. %% get request with response of unknown encoding
  495. #{
  496. handler => fun(Req0, State) ->
  497. Req = cowboy_req:reply(
  498. 200,
  499. #{
  500. <<"content-type">> =>
  501. <<"test/plain">>
  502. },
  503. <<"is_superuser=true">>,
  504. Req0
  505. ),
  506. {ok, Req, State}
  507. end,
  508. config_params => #{},
  509. %% only one chain, ignore by authn http and deny by default
  510. result => {error, not_authorized}
  511. },
  512. %% simple post request, application/json
  513. #{
  514. handler => fun(Req0, State) ->
  515. {ok, RawBody, Req1} = cowboy_req:read_body(Req0),
  516. #{
  517. <<"username">> := <<"plain">>,
  518. <<"password">> := <<"plain">>
  519. } = emqx_utils_json:decode(RawBody, [return_maps]),
  520. Req = cowboy_req:reply(
  521. 200,
  522. #{<<"content-type">> => <<"application/json">>},
  523. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  524. Req1
  525. ),
  526. {ok, Req, State}
  527. end,
  528. config_params => #{
  529. <<"method">> => <<"post">>,
  530. <<"headers">> => #{<<"content-type">> => <<"application/json">>}
  531. },
  532. result => {ok, #{is_superuser => false, client_attrs => #{}}}
  533. },
  534. %% simple post request, application/x-www-form-urlencoded
  535. #{
  536. handler => fun(Req0, State) ->
  537. {ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0),
  538. #{
  539. <<"username">> := <<"plain">>,
  540. <<"password">> := <<"plain">>
  541. } = maps:from_list(PostVars),
  542. Req = cowboy_req:reply(
  543. 200,
  544. #{<<"content-type">> => <<"application/json">>},
  545. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  546. Req1
  547. ),
  548. {ok, Req, State}
  549. end,
  550. config_params => #{
  551. <<"method">> => <<"post">>,
  552. <<"headers">> => #{
  553. <<"content-type">> =>
  554. <<"application/x-www-form-urlencoded">>
  555. }
  556. },
  557. result => {ok, #{is_superuser => false, client_attrs => #{}}}
  558. },
  559. %% simple post request for placeholders, application/json
  560. #{
  561. handler => fun(Req0, State) ->
  562. {ok, RawBody, Req1} = cowboy_req:read_body(Req0),
  563. #{
  564. <<"username">> := <<"plain">>,
  565. <<"password">> := <<"plain">>,
  566. <<"clientid">> := <<"clienta">>,
  567. <<"peerhost">> := <<"127.0.0.1">>,
  568. <<"cert_subject">> := <<"cert_subject_data">>,
  569. <<"cert_common_name">> := <<"cert_common_name_data">>,
  570. <<"the_group">> := <<"g1">>
  571. } = emqx_utils_json:decode(RawBody, [return_maps]),
  572. Req = cowboy_req:reply(
  573. 200,
  574. #{<<"content-type">> => <<"application/json">>},
  575. emqx_utils_json:encode(#{result => allow, is_superuser => false}),
  576. Req1
  577. ),
  578. {ok, Req, State}
  579. end,
  580. config_params => #{
  581. <<"method">> => <<"post">>,
  582. <<"headers">> => #{<<"content-type">> => <<"application/json">>},
  583. <<"body">> => #{
  584. <<"clientid">> => ?PH_CLIENTID,
  585. <<"username">> => ?PH_USERNAME,
  586. <<"password">> => ?PH_PASSWORD,
  587. <<"peerhost">> => ?PH_PEERHOST,
  588. <<"cert_subject">> => ?PH_CERT_SUBJECT,
  589. <<"cert_common_name">> => ?PH_CERT_CN_NAME,
  590. <<"the_group">> => <<"${client_attrs.group}">>
  591. }
  592. },
  593. result => {ok, #{is_superuser => false, client_attrs => #{}}}
  594. },
  595. %% custom headers
  596. #{
  597. handler => fun(Req0, State) ->
  598. <<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0),
  599. Req = cowboy_req:reply(200, Req0),
  600. {ok, Req, State}
  601. end,
  602. config_params => #{},
  603. %% only one chain, ignore by authn http and deny by default
  604. result => {error, not_authorized}
  605. },
  606. %% 204 code
  607. #{
  608. handler => fun(Req0, State) ->
  609. Req = cowboy_req:reply(204, Req0),
  610. {ok, Req, State}
  611. end,
  612. config_params => #{},
  613. result => {ok, #{is_superuser => false}}
  614. },
  615. %% 400 code
  616. #{
  617. handler => fun(Req0, State) ->
  618. Req = cowboy_req:reply(400, Req0),
  619. {ok, Req, State}
  620. end,
  621. config_params => #{},
  622. %% only one chain, ignore by authn http and deny by default
  623. result => {error, not_authorized}
  624. },
  625. %% 500 code
  626. #{
  627. handler => fun(Req0, State) ->
  628. Req = cowboy_req:reply(500, Req0),
  629. {ok, Req, State}
  630. end,
  631. config_params => #{},
  632. %% only one chain, ignore by authn http and deny by default
  633. result => {error, not_authorized}
  634. },
  635. %% Handling error
  636. #{
  637. handler => fun(Req0, State) ->
  638. error(woops),
  639. {ok, Req0, State}
  640. end,
  641. config_params => #{},
  642. %% only one chain, ignore by authn http and deny by default
  643. result => {error, not_authorized}
  644. }
  645. ].
  646. start_apps(Apps) ->
  647. lists:foreach(fun application:ensure_all_started/1, Apps).
  648. stop_apps(Apps) ->
  649. lists:foreach(fun application:stop/1, Apps).
  650. uri_encode(T) ->
  651. emqx_http_lib:uri_encode(to_list(T)).
  652. to_list(A) when is_atom(A) ->
  653. atom_to_list(A);
  654. to_list(N) when is_integer(N) ->
  655. integer_to_list(N);
  656. to_list(B) when is_binary(B) ->
  657. binary_to_list(B);
  658. to_list(L) when is_list(L) ->
  659. L.