[Asterisk-code-review] ARI event type filtering (asterisk[master])
Kevin Harwell
asteriskteam at digium.com
Fri Feb 8 14:51:13 CST 2019
Kevin Harwell has uploaded this change for review. ( https://gerrit.asterisk.org/10982
Change subject: ARI event type filtering
......................................................................
ARI event type filtering
Event type filtering is now enabled, and configurable per application. An app is
now able to specify which events are sent to the application by configuring an
allowed and/or disallowed list(s). This can be done by issuing the following:
PUT /applications/{applicationName}/eventFilter
And then enumerating the allowed/disallowed event types as a body parameter.
ASTERISK-28106
Change-Id: I9671ba1fcdb3b6c830b553d4c5365aed5d588d5b
---
M CHANGES
M include/asterisk/stasis_app.h
M res/ari/ari_model_validators.c
M res/ari/ari_model_validators.h
M res/ari/resource_applications.c
M res/ari/resource_applications.h
M res/res_ari_applications.c
M res/res_stasis.c
M res/stasis/app.c
M rest-api/api-docs/applications.json
10 files changed, 400 insertions(+), 4 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/82/10982/1
diff --git a/CHANGES b/CHANGES
index 4f41c39..78e019e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -72,6 +72,18 @@
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 b0829ab..e2b34a4 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -150,6 +150,16 @@
/*!
* \brief Return the JSON representation of a Stasis application.
*
+ * \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.
+ *
* \param app_name Name of the application.
*
* \return JSON representation of app with given name.
@@ -958,6 +968,41 @@
*/
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 a06a1f5..cf3ec27 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -6530,13 +6530,26 @@
{
int res = 1;
struct ast_json_iter *iter;
+ int has_allowed_events = 0;
int has_bridge_ids = 0;
int has_channel_ids = 0;
int has_device_names = 0;
+ int has_disallowed_events = 0;
int has_endpoint_ids = 0;
int has_name = 0;
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("allowed_events", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_allowed_events = 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 allowed_events failed validation\n");
+ res = 0;
+ }
+ } else
if (strcmp("bridge_ids", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_bridge_ids = 1;
@@ -6570,6 +6583,17 @@
res = 0;
}
} else
+ if (strcmp("disallowed_events", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_disallowed_events = 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 disallowed_events failed validation\n");
+ res = 0;
+ }
+ } else
if (strcmp("endpoint_ids", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_endpoint_ids = 1;
@@ -6599,6 +6623,11 @@
}
}
+ if (!has_allowed_events) {
+ ast_log(LOG_ERROR, "ARI Application missing required field allowed_events\n");
+ res = 0;
+ }
+
if (!has_bridge_ids) {
ast_log(LOG_ERROR, "ARI Application missing required field bridge_ids\n");
res = 0;
@@ -6614,6 +6643,11 @@
res = 0;
}
+ if (!has_disallowed_events) {
+ ast_log(LOG_ERROR, "ARI Application missing required field disallowed_events\n");
+ res = 0;
+ }
+
if (!has_endpoint_ids) {
ast_log(LOG_ERROR, "ARI Application missing required field endpoint_ids\n");
res = 0;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index ab0b2f6..988c4f0 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1826,9 +1826,11 @@
* - endpoint: Endpoint
* - message: TextMessage (required)
* Application
+ * - allowed_events: List[object] (required)
* - bridge_ids: List[string] (required)
* - channel_ids: List[string] (required)
* - device_names: List[string] (required)
+ * - disallowed_events: List[object] (required)
* - endpoint_ids: List[string] (required)
* - name: string (required)
*/
diff --git a/res/ari/resource_applications.c b/res/ari/resource_applications.c
index fc93b28..4a97401 100644
--- a/res/ari/resource_applications.c
+++ b/res/ari/resource_applications.c
@@ -168,3 +168,23 @@
"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 be62e9d..23a9b9b 100644
--- a/res/ari/resource_applications.h
+++ b/res/ari/resource_applications.h
@@ -127,5 +127,33 @@
* \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:<br /><br />"allowed" - Specifies an allowed list of event types<br />"disallowed" - Specifies a disallowed list of event types<br /><br />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:<br /><br />"type" - The type name of the event to filter<br /><br />The value must be the string name (case sensitive) of the event type that needs filtering. For example:<br /><br />{ "allowed": [ { "type": "StasisStart" }, { "type": "StasisEnd" } ] }<br /><br />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.<br /><br />The following rules apply:<br /><br />* If the body is empty, both the allowed and disallowed filters are set empty.<br />* 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).<br />* If only one list type is given then only that type is set. The other type is not updated.<br />* An empty "allowed" list means all events are allowed.<br />* An empty "disallowed" list means no events are disallowed.<br />* 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/res_ari_applications.c b/res/res_ari_applications.c
index fd8a448..4d1443e 100644
--- a/res/res_ari_applications.c
+++ b/res/res_ari_applications.c
@@ -459,6 +459,74 @@
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 @@
.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 86723a1..4da78a6 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1689,13 +1689,14 @@
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 9317e08..91aa2c1 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -65,6 +65,10 @@
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 *allowed;
+ /*! An array of disallowed events types for this application */
+ struct ast_json *disallowed;
/*! Name of the Stasis application */
char name[];
};
@@ -303,6 +307,12 @@
app->forwards = NULL;
ao2_cleanup(app->data);
app->data = NULL;
+
+ ast_json_unref(app->allowed);
+ app->allowed = NULL;
+ ast_json_unref(app->disallowed);
+ app->disallowed = NULL;
+
}
static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message)
@@ -1603,3 +1613,119 @@
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, "allowed_events", app->allowed ?
+ ast_json_ref(app->allowed) : ast_json_array_create());
+ ast_json_object_set(json, "disallowed_events", app->disallowed ?
+ ast_json_ref(app->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_allowed_set(struct stasis_app *app, struct ast_json *filter)
+{
+ return app_event_filter_set(app, &app->allowed, filter, "allowed");
+}
+
+static int app_disallowed_set(struct stasis_app *app, struct ast_json *filter)
+{
+ return app_event_filter_set(app, &app->disallowed, filter, "disallowed");
+}
+
+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter)
+{
+ return app_disallowed_set(app, filter) || app_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_strcmp_s(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->disallowed, event, 0) &&
+ app_event_filter_matched(app->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 cdd69c4..3fa8c77 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:<br /><br />\"allowed\" - Specifies an allowed list of event types<br />\"disallowed\" - Specifies a disallowed list of event types<br /><br />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:<br /><br />\"type\" - The type name of the event to filter<br /><br />The value must be the string name (case sensitive) of the event type that needs filtering. For example:<br /><br />{ \"allowed\": [ { \"type\": \"StasisStart\" }, { \"type\": \"StasisEnd\" } ] }<br /><br />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.<br /><br />The following rules apply:<br /><br />* If the body is empty, both the allowed and disallowed filters are set empty.<br />* 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).<br />* If only one list type is given then only that type is set. The other type is not updated.<br />* An empty \"allowed\" list means all events are allowed.<br />* An empty \"disallowed\" list means no events are disallowed.<br />* 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
+ },
+ "allowed_events": {
+ "type": "List[object]",
+ "description": "Event types sent to the application.",
+ "required": true
+ },
+ "disallowed_events": {
+ "type": "List[object]",
+ "description": "Event types not sent to the application.",
+ "required": true
}
}
}
--
To view, visit https://gerrit.asterisk.org/10982
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: I9671ba1fcdb3b6c830b553d4c5365aed5d588d5b
Gerrit-Change-Number: 10982
Gerrit-PatchSet: 1
Gerrit-Owner: Kevin Harwell <kharwell at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190208/7832d698/attachment-0001.html>
More information about the asterisk-code-review
mailing list