diff --git a/CHANGES b/CHANGES
index 4f41c39fd6..78e019efd7 100644
--- a/CHANGES
+++ b/CHANGES
@@ -71,6 +71,18 @@ Bridging
ast_bridge_snapshot_update structure as it's data. It contains
the last snapshot and the new one.
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 16.2.0 to Asterisk 16.3.0 ----------
+------------------------------------------------------------------------------
+
+ARI
+------------------
+ * Application event filtering is now supported. An application can now specify
+ an "allowed" and/or "disallowed" list(s) of event types. Only those types
+ indicated in the "allowed" list are sent to the application. Conversely, any
+ types defined in the "disallowed" list are not sent to the application. Note
+ that if a type is specified in both lists "disallowed" takes precedence.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 16.1.0 to Asterisk 16.2.0 ------------
------------------------------------------------------------------------------
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index b0829ab736..c40e901e5f 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -147,6 +147,17 @@ struct stasis_app;
*/
const char *stasis_app_name(const struct stasis_app *app);
+/*!
+ * \brief Return the JSON representation of a Stasis application.
+ * \since 16.3.0
+ *
+ * \param app The application.
+ *
+ * \return JSON representation of app with given name.
+ * \return \c NULL on error.
+ */
+struct ast_json *stasis_app_object_to_json(struct stasis_app *app);
+
/*!
* \brief Return the JSON representation of a Stasis application.
*
@@ -958,6 +969,41 @@ struct ast_cli_args;
*/
void stasis_app_to_cli(const struct stasis_app *app, struct ast_cli_args *a);
+/*!
+ * \brief Convert and add the app's event type filter(s) to the given json object.
+ *
+ * \param app The application
+ * \param json The json object to add the filter data to
+ *
+ * \return The given json object
+ */
+struct ast_json *stasis_app_event_filter_to_json(struct stasis_app *app, struct ast_json *json);
+
+/*!
+ * \brief Set the application's event type filter
+ *
+ * \param app The application
+ * \param filter The allowed and/or disallowed event filter
+ *
+ * \return 0 if successfully set
+ */
+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter);
+
+/*!
+ * \brief Check if the given event should be filtered.
+ *
+ * Attempts first to find the event in the application's disallowed events list.
+ * If found then the event won't be sent to the remote. If not found in the
+ * disallowed list then a search is done to see if it can be found in the allowed
+ * list. If found the event message is sent, otherwise it is not sent.
+ *
+ * \param app_name The application name
+ * \param event The event to check
+ *
+ * \return True if allowed, false otherwise
+ */
+int stasis_app_event_allowed(const char *app_name, struct ast_json *event);
+
/*! @} */
#endif /* _ASTERISK_STASIS_APP_H */
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index a06a1f5e59..44d9d7727c 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -6534,6 +6534,8 @@ int ast_ari_validate_application(struct ast_json *json)
int has_channel_ids = 0;
int has_device_names = 0;
int has_endpoint_ids = 0;
+ int has_events_allowed = 0;
+ int has_events_disallowed = 0;
int has_name = 0;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
@@ -6581,6 +6583,28 @@ int ast_ari_validate_application(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("events_allowed", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_events_allowed = 1;
+ prop_is_valid = ast_ari_validate_list(
+ ast_json_object_iter_value(iter),
+ ast_ari_validate_object);
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Application field events_allowed failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("events_disallowed", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_events_disallowed = 1;
+ prop_is_valid = ast_ari_validate_list(
+ ast_json_object_iter_value(iter),
+ ast_ari_validate_object);
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Application field events_disallowed failed validation\n");
+ res = 0;
+ }
+ } else
if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_name = 1;
@@ -6619,6 +6643,16 @@ int ast_ari_validate_application(struct ast_json *json)
res = 0;
}
+ if (!has_events_allowed) {
+ ast_log(LOG_ERROR, "ARI Application missing required field events_allowed\n");
+ res = 0;
+ }
+
+ if (!has_events_disallowed) {
+ ast_log(LOG_ERROR, "ARI Application missing required field events_disallowed\n");
+ res = 0;
+ }
+
if (!has_name) {
ast_log(LOG_ERROR, "ARI Application missing required field name\n");
res = 0;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index ab0b2f60b0..1ee74f4865 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1830,6 +1830,8 @@ ari_validator ast_ari_validate_application_fn(void);
* - channel_ids: List[string] (required)
* - device_names: List[string] (required)
* - endpoint_ids: List[string] (required)
+ * - events_allowed: List[object] (required)
+ * - events_disallowed: List[object] (required)
* - name: string (required)
*/
diff --git a/res/ari/resource_applications.c b/res/ari/resource_applications.c
index fc93b28e22..4a97401cf3 100644
--- a/res/ari/resource_applications.c
+++ b/res/ari/resource_applications.c
@@ -168,3 +168,23 @@ void ast_ari_applications_unsubscribe(struct ast_variable *headers,
"Error processing request");
}
}
+
+void ast_ari_applications_filter(struct ast_variable *headers,
+ struct ast_ari_applications_filter_args *args,
+ struct ast_ari_response *response)
+{
+ struct stasis_app *app = stasis_app_get_by_name(args->application_name);
+
+ if (!app) {
+ ast_ari_response_error(response, 404, "Not Found", "Application not found");
+ return;
+ }
+
+ if (stasis_app_event_filter_set(app, args->filter)) {
+ ast_ari_response_error(response, 400, "Bad Request", "Invalid format definition");
+ } else {
+ ast_ari_response_ok(response, stasis_app_object_to_json(app));
+ }
+
+ ao2_ref(app, -1);
+}
diff --git a/res/ari/resource_applications.h b/res/ari/resource_applications.h
index be62e9d5fd..23a9b9bd0d 100644
--- a/res/ari/resource_applications.h
+++ b/res/ari/resource_applications.h
@@ -127,5 +127,33 @@ int ast_ari_applications_unsubscribe_parse_body(
* \param[out] response HTTP response
*/
void ast_ari_applications_unsubscribe(struct ast_variable *headers, struct ast_ari_applications_unsubscribe_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_applications_filter() */
+struct ast_ari_applications_filter_args {
+ /*! Application's name */
+ const char *application_name;
+ /*! Specify which event types to allow/disallow */
+ struct ast_json *filter;
+};
+/*!
+ * \brief Body parsing function for /applications/{applicationName}/eventFilter.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_applications_filter_parse_body(
+ struct ast_json *body,
+ struct ast_ari_applications_filter_args *args);
+
+/*!
+ * \brief Filter application events types.
+ *
+ * Allowed and/or disallowed event type filtering can be done. The body (parameter) should specify a JSON key/value object that describes the type of event filtering needed. One, or both of the following keys can be designated:
"allowed" - Specifies an allowed list of event types
"disallowed" - Specifies a disallowed list of event types
Further, each of those key's value should be a JSON array that holds zero, or more JSON key/value objects. Each of these objects must contain the following key with an associated value:
"type" - The type name of the event to filter
The value must be the string name (case sensitive) of the event type that needs filtering. For example:
{ "allowed": [ { "type": "StasisStart" }, { "type": "StasisEnd" } ] }
As this specifies only an allowed list, then only those two event type messages are sent to the application. No other event messages are sent.
The following rules apply:
* If the body is empty, both the allowed and disallowed filters are set empty.
* If both list types are given then both are set to their respective values (note, specifying an empty array for a given type sets that type to empty).
* If only one list type is given then only that type is set. The other type is not updated.
* An empty "allowed" list means all events are allowed.
* An empty "disallowed" list means no events are disallowed.
* Disallowed events take precedence over allowed events if the event type is specified in both lists.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_applications_filter(struct ast_variable *headers, struct ast_ari_applications_filter_args *args, struct ast_ari_response *response);
#endif /* _ASTERISK_RESOURCE_APPLICATIONS_H */
diff --git a/res/ari/resource_events.c b/res/ari/resource_events.c
index 5983f7b531..d935da15f5 100644
--- a/res/ari/resource_events.c
+++ b/res/ari/resource_events.c
@@ -114,7 +114,7 @@ static void stasis_app_message_handler(
"Queued '%s' message for Stasis app '%s'; websocket is not ready\n",
msg_type,
msg_application);
- } else {
+ } else if (stasis_app_event_allowed(app_name, message)) {
if (stasis_app_get_debug_by_name(app_name)) {
char *str = ast_json_dump_string_format(message, ast_ari_json_format());
diff --git a/res/res_ari_applications.c b/res/res_ari_applications.c
index fd8a448cef..4d1443ec36 100644
--- a/res/res_ari_applications.c
+++ b/res/res_ari_applications.c
@@ -459,6 +459,74 @@ fin: __attribute__((unused))
ast_free(args.event_source);
return;
}
+int ast_ari_applications_filter_parse_body(
+ struct ast_json *body,
+ struct ast_ari_applications_filter_args *args)
+{
+ /* Parse query parameters out of it */
+ return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /applications/{applicationName}/eventFilter.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_applications_filter_cb(
+ struct ast_tcptls_session_instance *ser,
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+ struct ast_ari_applications_filter_args args = {};
+ struct ast_variable *i;
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "applicationName") == 0) {
+ args.application_name = (i->value);
+ } else
+ {}
+ }
+ args.filter = body;
+ ast_ari_applications_filter(headers, &args, response);
+#if defined(AST_DEVMODE)
+ code = response->response_code;
+
+ switch (code) {
+ case 0: /* Implementation is still a stub, or the code wasn't set */
+ is_valid = response->message == NULL;
+ break;
+ case 500: /* Internal Server Error */
+ case 501: /* Not Implemented */
+ case 400: /* Bad request. */
+ case 404: /* Application does not exist. */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_application(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /applications/{applicationName}/eventFilter\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /applications/{applicationName}/eventFilter\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ return;
+}
/*! \brief REST handler for /api-docs/applications.json */
static struct stasis_rest_handlers applications_applicationName_subscription = {
@@ -471,14 +539,23 @@ static struct stasis_rest_handlers applications_applicationName_subscription = {
.children = { }
};
/*! \brief REST handler for /api-docs/applications.json */
+static struct stasis_rest_handlers applications_applicationName_eventFilter = {
+ .path_segment = "eventFilter",
+ .callbacks = {
+ [AST_HTTP_PUT] = ast_ari_applications_filter_cb,
+ },
+ .num_children = 0,
+ .children = { }
+};
+/*! \brief REST handler for /api-docs/applications.json */
static struct stasis_rest_handlers applications_applicationName = {
.path_segment = "applicationName",
.is_wildcard = 1,
.callbacks = {
[AST_HTTP_GET] = ast_ari_applications_get_cb,
},
- .num_children = 1,
- .children = { &applications_applicationName_subscription, }
+ .num_children = 2,
+ .children = { &applications_applicationName_subscription,&applications_applicationName_eventFilter, }
};
/*! \brief REST handler for /api-docs/applications.json */
static struct stasis_rest_handlers applications = {
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 86723a1d12..4da78a6dc0 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1689,13 +1689,14 @@ static struct ast_json *app_event_sources_to_json(
return json;
}
-static struct ast_json *stasis_app_object_to_json(struct stasis_app *app)
+struct ast_json *stasis_app_object_to_json(struct stasis_app *app)
{
if (!app) {
return NULL;
}
- return app_event_sources_to_json(app, app_to_json(app));
+ return stasis_app_event_filter_to_json(
+ app, app_event_sources_to_json(app, app_to_json(app)));
}
struct ast_json *stasis_app_to_json(const char *app_name)
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 9317e087da..0f0923ae97 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -65,6 +65,10 @@ struct stasis_app {
enum stasis_app_subscription_model subscription_model;
/*! Whether or not someone wants to see debug messages about this app */
int debug;
+ /*! An array of allowed events types for this application */
+ struct ast_json *events_allowed;
+ /*! An array of disallowed events types for this application */
+ struct ast_json *events_disallowed;
/*! Name of the Stasis application */
char name[];
};
@@ -303,6 +307,12 @@ static void app_dtor(void *obj)
app->forwards = NULL;
ao2_cleanup(app->data);
app->data = NULL;
+
+ ast_json_unref(app->events_allowed);
+ app->events_allowed = NULL;
+ ast_json_unref(app->events_disallowed);
+ app->events_disallowed = NULL;
+
}
static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message)
@@ -1603,3 +1613,119 @@ void stasis_app_unregister_event_sources(void)
stasis_app_unregister_event_source(&bridge_event_source);
stasis_app_unregister_event_source(&channel_event_source);
}
+
+struct ast_json *stasis_app_event_filter_to_json(struct stasis_app *app, struct ast_json *json)
+{
+ if (!app || !json) {
+ return json;
+ }
+
+ ast_json_object_set(json, "events_allowed", app->events_allowed ?
+ ast_json_ref(app->events_allowed) : ast_json_array_create());
+ ast_json_object_set(json, "events_disallowed", app->events_disallowed ?
+ ast_json_ref(app->events_disallowed) : ast_json_array_create());
+
+ return json;
+}
+
+static int app_event_filter_set(struct stasis_app *app, struct ast_json **member,
+ struct ast_json *filter, const char *filter_type)
+{
+ if (filter && ast_json_typeof(filter) == AST_JSON_OBJECT) {
+ if (!ast_json_object_size(filter)) {
+ /* If no filters are specified then reset this filter type */
+ filter = NULL;
+ } else {
+ /* Otherwise try to get the filter array for this type */
+ filter = ast_json_object_get(filter, filter_type);
+ if (!filter) {
+ /* A filter type exists, but not this one, so don't update */
+ return 0;
+ }
+ }
+ }
+
+ /* At this point the filter object should be an array */
+ if (filter && ast_json_typeof(filter) != AST_JSON_ARRAY) {
+ ast_log(LOG_ERROR, "Invalid json type event filter - app: %s, filter: %s\n",
+ app->name, filter_type);
+ return -1;
+ }
+
+ if (filter) {
+ /* Confirm that at least the type names are specified */
+ struct ast_json *obj;
+ int i;
+
+ for (i = 0; i < ast_json_array_size(filter) &&
+ (obj = ast_json_array_get(filter, i)); ++i) {
+
+ if (ast_strlen_zero(ast_json_object_string_get(obj, "type"))) {
+ ast_log(LOG_ERROR, "Filter event must have a type - app: %s, "
+ "filter: %s\n", app->name, filter_type);
+ return -1;
+ }
+ }
+ }
+
+ ao2_lock(app);
+ ast_json_unref(*member);
+ *member = filter ? ast_json_ref(filter) : NULL;
+ ao2_unlock(app);
+
+ return 0;
+}
+
+static int app_events_allowed_set(struct stasis_app *app, struct ast_json *filter)
+{
+ return app_event_filter_set(app, &app->events_allowed, filter, "allowed");
+}
+
+static int app_events_disallowed_set(struct stasis_app *app, struct ast_json *filter)
+{
+ return app_event_filter_set(app, &app->events_disallowed, filter, "disallowed");
+}
+
+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter)
+{
+ return app_events_disallowed_set(app, filter) || app_events_allowed_set(app, filter);
+}
+
+static int app_event_filter_matched(struct ast_json *array, struct ast_json *event, int empty)
+{
+ struct ast_json *obj;
+ int i;
+
+ if (!array || !ast_json_array_size(array)) {
+ return empty;
+ }
+
+ for (i = 0; i < ast_json_array_size(array) &&
+ (obj = ast_json_array_get(array, i)); ++i) {
+
+ if (ast_strings_equal(ast_json_object_string_get(obj, "type"),
+ ast_json_object_string_get(event, "type"))) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int stasis_app_event_allowed(const char *app_name, struct ast_json *event)
+{
+ struct stasis_app *app = stasis_app_get_by_name(app_name);
+ int res;
+
+ if (!app) {
+ return 0;
+ }
+
+ ao2_lock(app);
+ res = !app_event_filter_matched(app->events_disallowed, event, 0) &&
+ app_event_filter_matched(app->events_allowed, event, 1);
+ ao2_unlock(app);
+ ao2_ref(app, -1);
+
+ return res;
+}
diff --git a/rest-api/api-docs/applications.json b/rest-api/api-docs/applications.json
index cdd69c4178..09c5cd5c91 100644
--- a/rest-api/api-docs/applications.json
+++ b/rest-api/api-docs/applications.json
@@ -134,6 +134,47 @@
]
}
]
+ },
+ {
+ "path": "/applications/{applicationName}/eventFilter",
+ "description": "Stasis application",
+ "operations": [
+ {
+ "httpMethod": "PUT",
+ "summary": "Filter application events types.",
+ "notes": "Allowed and/or disallowed event type filtering can be done. The body (parameter) should specify a JSON key/value object that describes the type of event filtering needed. One, or both of the following keys can be designated:
\"allowed\" - Specifies an allowed list of event types
\"disallowed\" - Specifies a disallowed list of event types
Further, each of those key's value should be a JSON array that holds zero, or more JSON key/value objects. Each of these objects must contain the following key with an associated value:
\"type\" - The type name of the event to filter
The value must be the string name (case sensitive) of the event type that needs filtering. For example:
{ \"allowed\": [ { \"type\": \"StasisStart\" }, { \"type\": \"StasisEnd\" } ] }
As this specifies only an allowed list, then only those two event type messages are sent to the application. No other event messages are sent.
The following rules apply:
* If the body is empty, both the allowed and disallowed filters are set empty.
* If both list types are given then both are set to their respective values (note, specifying an empty array for a given type sets that type to empty).
* If only one list type is given then only that type is set. The other type is not updated.
* An empty \"allowed\" list means all events are allowed.
* An empty \"disallowed\" list means no events are disallowed.
* Disallowed events take precedence over allowed events if the event type is specified in both lists.",
+ "nickname": "filter",
+ "responseClass": "Application",
+ "parameters": [
+ {
+ "name": "applicationName",
+ "description": "Application's name",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ },
+ {
+ "name": "filter",
+ "description": "Specify which event types to allow/disallow",
+ "paramType": "body",
+ "required": false,
+ "dataType": "object",
+ "allowMultiple": false
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 400,
+ "reason": "Bad request."
+ },
+ {
+ "code": 404,
+ "reason": "Application does not exist."
+ }
+ ]
+ }
+ ]
}
],
"models": {
@@ -165,6 +206,16 @@
"type": "List[string]",
"description": "Names of the devices subscribed to.",
"required": true
+ },
+ "events_allowed": {
+ "type": "List[object]",
+ "description": "Event types sent to the application.",
+ "required": true
+ },
+ "events_disallowed": {
+ "type": "List[object]",
+ "description": "Event types not sent to the application.",
+ "required": true
}
}
}