Commit 762b4c9c39 for asterisk.org
commit 762b4c9c39b28fa5564112b0f484012298469a6c
Author: George Joseph <gjoseph@sangoma.com>
Date: Fri Jun 12 14:23:22 2026 -0600
res_ari: Ensure read-only users are properly authorized via REST Over WebSocket.
The REST over WebSocket path now properly prevents non-GET methods from
being executed on inbound WebSockets.
* The query parameters from the original incoming GET request that caused the
upgrade to WebSocket are now passed to all REST requests that come from the
client. This ensures that if the client authenticated with a read-only
userid using the "api_key" query_string parameter, REST requests coming
in over the WebSocket will only be able to execute GETs on resources.
The HTTP headers were already passed to the REST requests so if the
client had authenticated via an "Authorization" it was properly handled.
* New tests have been added to test_ari.c to check that read-only users
are properly denied access to resources using non-GET methods. Several
memory leaks were also squashed.
Resolves: #GHSA-wcvv-g26m-wx5c
diff --git a/res/ari/ari_websocket_requests.c b/res/ari/ari_websocket_requests.c
index 61d1ca897f..018603a2ab 100644
--- a/res/ari/ari_websocket_requests.c
+++ b/res/ari/ari_websocket_requests.c
@@ -71,7 +71,7 @@ static void request_destroy(struct rest_request_msg *request)
})
static struct rest_request_msg *parse_rest_request_msg(
- const char *remote_addr, struct ast_json *request_msg,
+ const char *remote_addr, struct ast_json *request_msg, struct ast_variable *get_params,
struct ast_ari_response *response, int debug_app)
{
struct rest_request_msg *request = NULL;
@@ -154,6 +154,25 @@ static struct rest_request_msg *parse_rest_request_msg(
remote_addr, request, request_msg);
}
+ /*
+ * If there were get_params on the original HTTP GET request that created
+ * the websocket, we need to append any api_key parameter to the request->query_strings
+ * to properly authenticate and authorize the request.
+ */
+ if (get_params) {
+ struct ast_variable *v;
+ ast_trace(-1, "%s: Websocket query params:\n", remote_addr);
+ for (v = get_params; v; v = v->next) {
+ if (ast_strings_equal(v->name, "api_key")) {
+ struct ast_variable *api_key = ast_variable_new(v->name, v->value, __FILE__);
+ if (api_key) {
+ ast_variable_list_append_hint(&request->query_strings, NULL, api_key);
+ }
+ break;
+ }
+ }
+ }
+
request->body = ast_json_null();
body = ast_strdup(ast_json_string_get(
@@ -259,7 +278,8 @@ static void send_rest_response(
}
int ari_websocket_process_request(struct ari_ws_session *ari_ws_session,
- const char *remote_addr, struct ast_variable *upgrade_headers,
+ const char *remote_addr, struct ast_variable *get_params,
+ struct ast_variable *upgrade_headers,
const char *app_name, struct ast_json *request_msg)
{
int debug_app = stasis_app_get_debug_by_name(app_name);
@@ -281,7 +301,7 @@ int ari_websocket_process_request(struct ari_ws_session *ari_ws_session,
}
request = SCOPE_CALL_WITH_RESULT(-1, struct rest_request_msg *,
- parse_rest_request_msg, remote_addr, request_msg, &response, debug_app);
+ parse_rest_request_msg, remote_addr, request_msg, get_params, &response, debug_app);
if (!request || response.response_code != 200) {
SCOPE_CALL(-1, send_rest_response, ari_ws_session,
diff --git a/res/ari/ari_websockets.c b/res/ari/ari_websockets.c
index 945c43a324..13d998317a 100644
--- a/res/ari/ari_websockets.c
+++ b/res/ari/ari_websockets.c
@@ -826,6 +826,11 @@ static void websocket_established_cb(struct ast_websocket *ast_ws_session,
for (v = upgrade_headers; v; v = v->next) {
ast_trace(3, "--> %s: %s\n", v->name, v->value);
}
+
+ ast_trace(2, "%s: Websocket query params:\n", remote_addr);
+ for (v = get_params; v; v = v->next) {
+ ast_trace(3, "--> %s: %s\n", v->name, v->value);
+ }
}
/*
@@ -849,7 +854,7 @@ static void websocket_established_cb(struct ast_websocket *ast_ws_session,
ari_ws_session->connected = 1;
ast_trace(-1, "%s: Waiting for messages\n", remote_addr);
while ((msg = session_read(ari_ws_session))) {
- ari_websocket_process_request(ari_ws_session, remote_addr,
+ ari_websocket_process_request(ari_ws_session, remote_addr, get_params,
upgrade_headers, ari_ws_session->app_name, msg);
ast_json_unref(msg);
}
@@ -1042,7 +1047,7 @@ static void *outbound_session_handler_thread(void *obj)
*/
while ((msg = session_read(session))) {
ari_websocket_process_request(session, session->remote_addr,
- upgrade_headers, session->app_name, msg);
+ NULL, upgrade_headers, session->app_name, msg);
ast_json_unref(msg);
}
diff --git a/res/ari/ari_websockets.h b/res/ari/ari_websockets.h
index a08277b50a..53f8e5b931 100644
--- a/res/ari/ari_websockets.h
+++ b/res/ari/ari_websockets.h
@@ -99,7 +99,8 @@ void ari_websocket_send_event(struct ari_ws_session *ari_ws_session,
* \retval 0 on success, -1 on failure
*/
int ari_websocket_process_request(struct ari_ws_session *ast_ws_session,
- const char *remote_addr, struct ast_variable *upgrade_headers,
+ const char *remote_addr, struct ast_variable *get_params,
+ struct ast_variable *upgrade_headers,
const char *app_name, struct ast_json *msg);
/*!
diff --git a/res/res_ari.c b/res/res_ari.c
index 2740771bee..a8fa1cae9d 100644
--- a/res/res_ari.c
+++ b/res/res_ari.c
@@ -441,33 +441,75 @@ static void handle_options(struct stasis_rest_handlers *handler,
}
}
-/*!
- * \brief Authenticate a <code>?api_key=userid:password</code>
- *
- * \param api_key API key query parameter
- * \return User object for the authenticated user.
- * \retval NULL if authentication failed.
+/*
+ * This function is actually copied from http.c. Didn't see a need to make
+ * that function public just for this one use case.
*/
-static struct ari_conf_user *authenticate_api_key(const char *api_key)
+static struct ast_http_auth *create_http_auth(const char *userid, const char *password)
{
- RAII_VAR(char *, copy, NULL, ast_free);
- char *username;
- char *password;
+ struct ast_http_auth *auth;
+ size_t userid_len;
+ size_t password_len;
- password = copy = ast_strdup(api_key);
- if (!copy) {
+ if (!userid || !password) {
+ ast_log(LOG_ERROR, "Invalid userid/password\n");
return NULL;
}
- username = strsep(&password, ":");
- if (!password) {
- ast_log(LOG_WARNING, "Invalid api_key\n");
+ userid_len = strlen(userid) + 1;
+ password_len = strlen(password) + 1;
+
+ /* Allocate enough room to store everything in one memory block */
+ auth = ao2_alloc_options(sizeof(*auth) + userid_len + password_len, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!auth) {
return NULL;
}
- return ari_conf_validate_user(username, password);
+ /* Put the userid right after the struct */
+ auth->userid = (char *)(auth + 1);
+ strcpy(auth->userid, userid);
+
+ /* Put the password right after the userid */
+ auth->password = auth->userid + userid_len;
+ strcpy(auth->password, password);
+
+ return auth;
}
+/*!
+ * \brief Parse an api_key parameter from the query parameters into an ast_http_auth structure.
+ *
+ * \param get_params query string parameters
+ * \return ast_http_user object for the api_key.
+ * \retval NULL if api_key wasn't found or is had no password.
+ */
+static struct ast_http_auth *parse_api_keys(struct ast_variable *get_params)
+{
+ struct ast_variable *v;
+
+ for (v = get_params; v; v = v->next) {
+ if (ast_strings_equal(v->name, "api_key")) {
+ if (!ast_strlen_zero(v->value)) {
+ char *password = ast_strdupa(v->value);
+ char *username = strsep(&password, ":");
+ if (!ast_strlen_zero(password)) {
+ return create_http_auth(username, password);
+ }
+ }
+ break;
+ }
+ }
+ return NULL;
+}
+
+#if AST_DEVMODE
+static void test_ari_user_destructor(void *obj)
+{
+ struct ari_conf_user *ari_user = obj;
+ ast_string_field_free_memory(ari_user);
+}
+#endif
+
/*!
* \brief Authenticate an HTTP request.
*
@@ -477,26 +519,68 @@ static struct ari_conf_user *authenticate_api_key(const char *api_key)
* \retval NULL if authentication failed.
*/
static struct ari_conf_user *authenticate_user(struct ast_variable *get_params,
- struct ast_variable *headers)
+ struct ast_variable *headers, enum ast_ari_invoke_source source)
{
RAII_VAR(struct ast_http_auth *, http_auth, NULL, ao2_cleanup);
- struct ast_variable *v;
+ struct ari_conf_user *ari_user = NULL;
+ SCOPE_ENTER(3, "\n");
/* HTTP Basic authentication */
http_auth = ast_http_get_auth(headers);
- if (http_auth) {
- return ari_conf_validate_user(http_auth->userid,
- http_auth->password);
+ if (!http_auth) {
+ /* ?api_key authentication */
+ http_auth = parse_api_keys(get_params);
+ }
+ if (!http_auth) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "No credentials found in HTTP headers or api_key\n");
}
- /* ?api_key authentication */
- for (v = get_params; v; v = v->next) {
- if (strcasecmp("api_key", v->name) == 0) {
- return authenticate_api_key(v->value);
+ if (source != ARI_INVOKE_SOURCE_TEST) {
+ ast_trace(-1, "Validating user %s\n", http_auth->userid);
+ ari_user = ari_conf_validate_user(http_auth->userid, http_auth->password);
+ if (!ari_user) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "User %s failed to validate\n", http_auth->userid);
}
+ SCOPE_EXIT_RTN_VALUE(ari_user, "User %s validated. Read only: %s\n",
+ http_auth->userid, AST_YESNO(ari_user->read_only));
}
+#if AST_DEVMODE
+ /*
+ * ARI_INVOKE_SOURCE_TEST should only be set by the tests in test_ari.c but
+ * as a safety precaution, only check against the test usernames and passwords
+ * if we're in DEVMODE.
+ */
+ else {
+ int readonly = 0;
+ if (ast_strings_equal(http_auth->userid, "aritest")) {
+ if (!ast_strings_equal(http_auth->password, "aritestpw")) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "User %s failed to validate\n", http_auth->userid);
+ }
+ } else if (ast_strings_equal(http_auth->userid, "aritestro")) {
+ if (!ast_strings_equal(http_auth->password, "aritestropw")) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "User %s failed to validate\n", http_auth->userid);
+ }
+ readonly = 1;
+ }
+
+ ari_user = ao2_alloc(sizeof(*ari_user), test_ari_user_destructor);
+ if (!ari_user) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "alloc failed\n");
+ }
+ if (ast_string_field_init(ari_user, 256) != 0) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "alloc failed\n");
+ }
+ ast_string_field_set(ari_user, password, http_auth->password);
+ ari_user->read_only = readonly;
+
+ SCOPE_EXIT_RTN_VALUE(ari_user, "User %s validated. Read only: %s\n",
+ http_auth->userid, AST_YESNO(ari_user->read_only));
+ }
+
+#else
return NULL;
+#endif
}
static void remove_trailing_slash(const char *uri,
@@ -553,29 +637,32 @@ enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *se
response->response_code, response->response_text);
}
- user = authenticate_user(get_params, headers);
-
- if (!user && source == ARI_INVOKE_SOURCE_REST) {
- /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response
- * message is used by an origin server to challenge the
- * authorization of a user agent. This response MUST include a
- * WWW-Authenticate header field containing at least one
- * challenge applicable to the requested resource.
- */
+ user = authenticate_user(get_params, headers, source);
+ if (!user) {
ast_ari_response_error(response, 401, "Unauthorized", "Authentication required");
-
- /* Section 1.2:
- * realm = "realm" "=" realm-value
- * realm-value = quoted-string
- * Section 2:
- * challenge = "Basic" realm
- */
- ast_str_append(&response->headers, 0,
- "WWW-Authenticate: Basic realm=\"%s\"\r\n",
- general->auth_realm);
+ if (source == ARI_INVOKE_SOURCE_REST) {
+ /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response
+ * message is used by an origin server to challenge the
+ * authorization of a user agent. This response MUST include a
+ * WWW-Authenticate header field containing at least one
+ * challenge applicable to the requested resource.
+ */
+
+ /* Section 1.2:
+ * realm = "realm" "=" realm-value
+ * realm-value = quoted-string
+ * Section 2:
+ * challenge = "Basic" realm
+ */
+ ast_str_append(&response->headers, 0,
+ "WWW-Authenticate: Basic realm=\"%s\"\r\n",
+ general->auth_realm);
+ }
SCOPE_EXIT_RTN_VALUE(ARI_INVOKE_RESULT_ERROR_CONTINUE, "Response: %d : %s\n",
response->response_code, response->response_text);
- } else if (user && user->acl && !ast_acl_list_is_empty(user->acl) &&
+ }
+
+ if (source == ARI_INVOKE_SOURCE_REST && ser && user->acl && !ast_acl_list_is_empty(user->acl) &&
ast_apply_acl(user->acl, &ser->remote_address, "ARI User ACL") == AST_SENSE_DENY) {
ast_ari_response_error(response, 403, "Forbidden", "Access denied by ACL");
SCOPE_EXIT_RTN_VALUE(ARI_INVOKE_RESULT_ERROR_CONTINUE, "Response: %d : %s\n",
@@ -584,7 +671,7 @@ enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *se
ast_ari_response_error(response, 503, "Service Unavailable", "Asterisk not booted");
SCOPE_EXIT_RTN_VALUE(ARI_INVOKE_RESULT_ERROR_CLOSE, "Response: %d : %s\n",
response->response_code, response->response_text);
- } else if (user && user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) {
+ } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) {
ast_ari_response_error(response, 403, "Forbidden", "Write access denied");
SCOPE_EXIT_RTN_VALUE(ARI_INVOKE_RESULT_ERROR_CONTINUE, "Response: %d : %s\n",
response->response_code, response->response_text);
diff --git a/tests/test_ari.c b/tests/test_ari.c
index 29bbbc1014..a9ed928b57 100644
--- a/tests/test_ari.c
+++ b/tests/test_ari.c
@@ -309,8 +309,9 @@ AST_TEST_DEFINE(invoke_get)
RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
- struct ast_variable *get_params = NULL;
- struct ast_variable *headers = NULL;
+ struct ast_variable *tail = NULL;
+ RAII_VAR(struct ast_variable *, get_params, NULL, ast_variables_destroy);
+ RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy);
switch (cmd) {
case TEST_INIT:
@@ -325,21 +326,24 @@ AST_TEST_DEFINE(invoke_get)
fixture = setup_invocation_test();
response = response_alloc();
- get_params = ast_variable_new("get1", "get-one", __FILE__);
- ast_assert(get_params != NULL);
- get_params->next = ast_variable_new("get2", "get-two", __FILE__);
- ast_assert(get_params->next != NULL);
-
- headers = ast_variable_new("head1", "head-one", __FILE__);
- ast_assert(headers != NULL);
- headers->next = ast_variable_new("head2", "head-two", __FILE__);
- ast_assert(headers->next != NULL);
-
- expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}",
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("get1", "get-one", __FILE__));
+ ast_assert(tail != NULL);
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("get2", "get-two", __FILE__));
+ ast_assert(tail != NULL);
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("api_key", "aritestro:aritestropw", __FILE__));
+ ast_assert(tail != NULL);
+
+ tail = ast_variable_list_append_hint(&headers, NULL, ast_variable_new("head1", "head-one", __FILE__));
+ ast_assert(tail != NULL);
+ tail = ast_variable_list_append_hint(&headers, NULL, ast_variable_new("head2", "head-two", __FILE__));
+ ast_assert(tail != NULL);
+
+ expected = ast_json_pack("{s: s, s: {s: s, s: s, s: s}, s: {s: s, s: s}, s: {}}",
"name", "foo_get",
"get_params",
"get1", "get-one",
"get2", "get-two",
+ "api_key", "aritestro:aritestropw",
"headers",
"head1", "head-one",
"head2", "head-two",
@@ -360,7 +364,8 @@ AST_TEST_DEFINE(invoke_wildcard)
RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
- struct ast_variable *get_params = NULL;
+ struct ast_variable *tail = NULL;
+ RAII_VAR(struct ast_variable *, get_params, NULL, ast_variables_destroy);
struct ast_variable *headers = NULL;
switch (cmd) {
@@ -376,9 +381,13 @@ AST_TEST_DEFINE(invoke_wildcard)
fixture = setup_invocation_test();
response = response_alloc();
- expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}",
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("api_key", "aritestro:aritestropw", __FILE__));
+ ast_assert(tail != NULL);
+
+ expected = ast_json_pack("{s: s, s: {s: s}, s: {}, s: {s: s}}",
"name", "bam_get",
"get_params",
+ "api_key", "aritestro:aritestropw",
"headers",
"path_vars",
"bam", "foshizzle");
@@ -393,30 +402,25 @@ AST_TEST_DEFINE(invoke_wildcard)
return AST_TEST_PASS;
}
-AST_TEST_DEFINE(invoke_delete)
+static enum ast_test_result_state invoke_delete_common(struct ast_test_info *info, enum ast_test_command cmd,
+ struct ast_test *test, const char *creds, int expect_pass, int expect_rc)
{
RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
- struct ast_variable *get_params = NULL;
+ struct ast_variable *tail = NULL;
+ RAII_VAR(struct ast_variable *, get_params, NULL, ast_variables_destroy);
struct ast_variable *headers = NULL;
- switch (cmd) {
- case TEST_INIT:
- info->name = __func__;
- info->category = "/res/ari/";
- info->summary = "Test DELETE of an HTTP resource.";
- info->description = "Test ARI binding logic.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
fixture = setup_invocation_test();
response = response_alloc();
- expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}",
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("api_key", creds, __FILE__));
+ ast_assert(tail != NULL);
+
+ expected = ast_json_pack("{s: s, s: {s: s}, s: {}, s: {s: s}}",
"name", "bang_delete",
"get_params",
+ "api_key", creds,
"headers",
"path_vars",
"bam", "foshizzle");
@@ -424,20 +428,53 @@ AST_TEST_DEFINE(invoke_delete)
ast_ari_invoke(NULL, ARI_INVOKE_SOURCE_TEST, NULL, "foo/foshizzle/bang", AST_HTTP_DELETE, get_params, headers,
ast_json_null(), response);
- ast_test_validate(test, 1 == invocation_count);
- ast_test_validate(test, 204 == response->response_code);
- ast_test_validate(test, ast_json_equal(expected, response->message));
+ ast_test_validate(test, expect_pass == invocation_count);
+ ast_test_validate(test, expect_rc == response->response_code);
+ ast_test_validate(test, ast_json_equal(expected, response->message) == expect_pass);
return AST_TEST_PASS;
}
+AST_TEST_DEFINE(invoke_delete)
+{
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = "/res/ari/";
+ info->summary = "Test DELETE of an HTTP resource.";
+ info->description = "Test ARI binding logic.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return invoke_delete_common(info, cmd, test, "aritest:aritestpw", 1, 204);
+}
+
+AST_TEST_DEFINE(invoke_delete_forbidden)
+{
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = "/res/ari/";
+ info->summary = "Test forbidden DELETE of an HTTP resource.";
+ info->description = "Test ARI binding logic.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return invoke_delete_common(info, cmd, test, "aritestro:aritestropw", 0, 403);
+}
+
AST_TEST_DEFINE(invoke_post)
{
RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
- struct ast_variable *get_params = NULL;
- struct ast_variable *headers = NULL;
+ struct ast_variable *tail = NULL;
+ RAII_VAR(struct ast_variable *, get_params, NULL, ast_variables_destroy);
+ RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy);
switch (cmd) {
case TEST_INIT:
@@ -452,21 +489,24 @@ AST_TEST_DEFINE(invoke_post)
fixture = setup_invocation_test();
response = response_alloc();
- get_params = ast_variable_new("get1", "get-one", __FILE__);
- ast_assert(get_params != NULL);
- get_params->next = ast_variable_new("get2", "get-two", __FILE__);
- ast_assert(get_params->next != NULL);
-
- headers = ast_variable_new("head1", "head-one", __FILE__);
- ast_assert(headers != NULL);
- headers->next = ast_variable_new("head2", "head-two", __FILE__);
- ast_assert(headers->next != NULL);
-
- expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}",
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("get1", "get-one", __FILE__));
+ ast_assert(tail != NULL);
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("get2", "get-two", __FILE__));
+ ast_assert(tail != NULL);
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("api_key", "aritest:aritestpw", __FILE__));
+ ast_assert(tail != NULL);
+
+ tail = ast_variable_list_append_hint(&headers, NULL, ast_variable_new("head1", "head-one", __FILE__));
+ ast_assert(tail != NULL);
+ tail = ast_variable_list_append_hint(&headers, NULL, ast_variable_new("head2", "head-two", __FILE__));
+ ast_assert(tail != NULL);
+
+ expected = ast_json_pack("{s: s, s: {s: s, s: s, s: s}, s: {s: s, s: s}, s: {}}",
"name", "bar_post",
"get_params",
"get1", "get-one",
"get2", "get-two",
+ "api_key", "aritest:aritestpw",
"headers",
"head1", "head-one",
"head2", "head-two",
@@ -486,7 +526,8 @@ AST_TEST_DEFINE(invoke_bad_post)
{
RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
- struct ast_variable *get_params = NULL;
+ struct ast_variable *tail = NULL;
+ RAII_VAR(struct ast_variable *, get_params, NULL, ast_variables_destroy);
struct ast_variable *headers = NULL;
switch (cmd) {
@@ -502,6 +543,10 @@ AST_TEST_DEFINE(invoke_bad_post)
fixture = setup_invocation_test();
response = response_alloc();
+
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("api_key", "aritest:aritestpw", __FILE__));
+ ast_assert(tail != NULL);
+
ast_ari_invoke(NULL, ARI_INVOKE_SOURCE_TEST, NULL, "foo", AST_HTTP_POST, get_params, headers,
ast_json_null(), response);
@@ -511,13 +556,43 @@ AST_TEST_DEFINE(invoke_bad_post)
return AST_TEST_PASS;
}
-AST_TEST_DEFINE(invoke_not_found)
+AST_TEST_DEFINE(invoke_no_user)
{
RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
struct ast_variable *get_params = NULL;
struct ast_variable *headers = NULL;
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = "/res/ari/";
+ info->summary = "Test POST on a resource that doesn't support it.";
+ info->description = "Test ARI binding logic.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ fixture = setup_invocation_test();
+ response = response_alloc();
+ ast_ari_invoke(NULL, ARI_INVOKE_SOURCE_TEST, NULL, "foo", AST_HTTP_POST, get_params, headers,
+ ast_json_null(), response);
+
+ ast_test_validate(test, 0 == invocation_count);
+ ast_test_validate(test, 401 == response->response_code);
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(invoke_not_found)
+{
+ RAII_VAR(void *, fixture, NULL, tear_down_invocation_test);
+ RAII_VAR(struct ast_ari_response *, response, NULL, response_free);
+ struct ast_variable *tail = NULL;
+ RAII_VAR(struct ast_variable *, get_params, NULL, ast_variables_destroy);
+ struct ast_variable *headers = NULL;
+
switch (cmd) {
case TEST_INIT:
info->name = __func__;
@@ -531,6 +606,10 @@ AST_TEST_DEFINE(invoke_not_found)
fixture = setup_invocation_test();
response = response_alloc();
+
+ tail = ast_variable_list_append_hint(&get_params, NULL, ast_variable_new("api_key", "aritest:aritestpw", __FILE__));
+ ast_assert(tail != NULL);
+
ast_ari_invoke(NULL, ARI_INVOKE_SOURCE_TEST, NULL, "foo/fizzle/i-am-not-a-resource", AST_HTTP_GET, get_params, headers,
ast_json_null(), response);
@@ -549,8 +628,10 @@ static int unload_module(void)
AST_TEST_UNREGISTER(invoke_get);
AST_TEST_UNREGISTER(invoke_wildcard);
AST_TEST_UNREGISTER(invoke_delete);
+ AST_TEST_UNREGISTER(invoke_delete_forbidden);
AST_TEST_UNREGISTER(invoke_post);
AST_TEST_UNREGISTER(invoke_bad_post);
+ AST_TEST_UNREGISTER(invoke_no_user);
AST_TEST_UNREGISTER(invoke_not_found);
return 0;
}
@@ -564,8 +645,10 @@ static int load_module(void)
AST_TEST_REGISTER(invoke_get);
AST_TEST_REGISTER(invoke_wildcard);
AST_TEST_REGISTER(invoke_delete);
+ AST_TEST_REGISTER(invoke_delete_forbidden);
AST_TEST_REGISTER(invoke_post);
AST_TEST_REGISTER(invoke_bad_post);
+ AST_TEST_REGISTER(invoke_no_user);
AST_TEST_REGISTER(invoke_not_found);
return AST_MODULE_LOAD_SUCCESS;
}