<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/6157">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve
George Joseph: Looks good to me, approved
Jenkins2: Approved for Submit
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_xmpp: Google OAuth 2.0 protocol support for XMPP / Motif<br><br>Add ability to use tokens instead of passwords according to Google OAuth 2.0<br>protocol.<br><br>ASTERISK-27169<br>Reported by: Andrey Egorov<br>Tested by: Andrey Egorov<br><br>Change-Id: I07f7052a502457ab55010a4d3686653b60f4c8db<br>---<br>M CHANGES<br>M configs/samples/xmpp.conf.sample<br>M res/res_xmpp.c<br>3 files changed, 115 insertions(+), 13 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/CHANGES b/CHANGES<br>index caf0e5d..0b440f5 100644<br>--- a/CHANGES<br>+++ b/CHANGES<br>@@ -51,6 +51,12 @@<br> * New dialplan function PJSIP_DTMF_MODE added to get or change the DTMF mode<br> of a channel on a per-call basis.<br> <br>+res_xmpp<br>+-----------------<br>+ * OAuth 2.0 authentication is now supported when contacting Google. Follow the<br>+ instructions in xmpp.conf.sample to retrieve and configure the necessary<br>+ tokens.<br>+<br> app_queue<br> ------------------<br> * Add priority to callers in AMI QueueStatus response.<br>diff --git a/configs/samples/xmpp.conf.sample b/configs/samples/xmpp.conf.sample<br>index dad0f79..e3a4be1 100644<br>--- a/configs/samples/xmpp.conf.sample<br>+++ b/configs/samples/xmpp.conf.sample<br>@@ -18,6 +18,29 @@<br> ;pubsub_node=pubsub.astjab.org ; Node to use for publishing events via PubSub<br> ;username=asterisk@astjab.org/asterisk ; Username with optional resource.<br> ;secret=blah ; Password<br>+;refresh_token=TOKEN_VALUE ; Refresh token issued by Google OAuth 2.0 protocol.<br>+ ; `secret` must NOT be set if you use OAuth.<br>+ ; See https://developers.google.com/identity/protocols/OAuth2WebServer<br>+ ; for more details.<br>+ ; For test reasons you can obtain one on the page<br>+ ; https://developers.google.com/oauthplayground/<br>+ ; 1. Click on Settings icon, check "Use your own OAuth credentials"<br>+ ; and enter your Client ID and Client Secret (see below).<br>+ ; 2. Input the scope https://www.googleapis.com/auth/googletalk<br>+ ; and push "Authorize APIs" button.<br>+ ; 3. Approve permissions.<br>+ ; 4. On section "Step 2" push "Exchange authorization code for tokens"<br>+ ; and get your Refresh token.<br>+;oauth_clientid=OAUTH_CLIENT_ID_VALUE ; The application's client id to authorize using Google OAuth 2.0 protocol.<br>+;oauth_secret=OAUTH_SECRET_VALUE ; The application's client secret to authorize using Google OAuth 2.0 protocol.<br>+ ; 1. Create new Project on the page:<br>+ ; https://console.cloud.google.com/apis/credentials/oauthclient<br>+ ; 2. Create new Application ID on the same page with type Web-application.<br>+ ; In section "Allowed URI redirections" put the path to the corresponding<br>+ ; script on your site or https://developers.google.com/oauthplayground<br>+ ; if you would like to obtain refresh_token from users by hand<br>+ ; (for example, for test reasons).<br>+ ; 3. Client ID and Client Secret will be shown and available on the same page.<br> ;priority=1 ; Resource priority<br> ;port=5222 ; Port to use defaults to 5222<br> ;usetls=yes ; Use tls or not<br>diff --git a/res/res_xmpp.c b/res/res_xmpp.c<br>index 1f53210..09af0b9 100644<br>--- a/res/res_xmpp.c<br>+++ b/res/res_xmpp.c<br>@@ -61,6 +61,7 @@<br> #include "asterisk/manager.h"<br> #include "asterisk/cli.h"<br> #include "asterisk/config_options.h"<br>+#include "asterisk/json.h"<br> <br> /*** DOCUMENTATION<br> <application name="JabberSend" language="en_US" module="res_xmpp"><br>@@ -323,6 +324,15 @@<br> <configOption name="secret"><br> <synopsis>XMPP password</synopsis><br> </configOption><br>+ <configOption name="refresh_token"><br>+ <synopsis>Google OAuth 2.0 refresh token</synopsis><br>+ </configOption><br>+ <configOption name="oauth_clientid"><br>+ <synopsis>Google OAuth 2.0 application's client id</synopsis><br>+ </configOption><br>+ <configOption name="oauth_secret"><br>+ <synopsis>Google OAuth 2.0 application's secret</synopsis><br>+ </configOption><br> <configOption name="serverhost"><br> <synopsis>Route to server, e.g. talk.google.com</synopsis><br> </configOption><br>@@ -461,6 +471,9 @@<br> AST_STRING_FIELD(name); /*!< Name of the client connection */<br> AST_STRING_FIELD(user); /*!< Username to use for authentication */<br> AST_STRING_FIELD(password); /*!< Password to use for authentication */<br>+ AST_STRING_FIELD(refresh_token); /*!< Refresh token to use for OAuth authentication */<br>+ AST_STRING_FIELD(oauth_clientid); /*!< Client ID to use for OAuth authentication */<br>+ AST_STRING_FIELD(oauth_secret); /*!< Secret to use for OAuth authentication */<br> AST_STRING_FIELD(server); /*!< Server hostname */<br> AST_STRING_FIELD(statusmsg); /*!< Status message for presence */<br> AST_STRING_FIELD(pubsubnode); /*!< Pubsub node */<br>@@ -529,6 +542,7 @@<br> static ast_mutex_t messagelock;<br> <br> static int xmpp_client_config_post_apply(void *obj, void *arg, int flags);<br>+static int fetch_access_token(struct ast_xmpp_client_config *cfg);<br> <br> /*! \brief Destructor function for configuration */<br> static void ast_xmpp_client_config_destructor(void *obj)<br>@@ -761,11 +775,15 @@<br> if (ast_strlen_zero(clientcfg->user)) {<br> ast_log(LOG_ERROR, "No user specified on client '%s'\n", clientcfg->name);<br> return -1;<br>- } else if (ast_strlen_zero(clientcfg->password)) {<br>- ast_log(LOG_ERROR, "No password specified on client '%s'\n", clientcfg->name);<br>+ } else if (ast_strlen_zero(clientcfg->password) && ast_strlen_zero(clientcfg->refresh_token)) {<br>+ ast_log(LOG_ERROR, "No password or refresh_token specified on client '%s'\n", clientcfg->name);<br> return -1;<br> } else if (ast_strlen_zero(clientcfg->server)) {<br> ast_log(LOG_ERROR, "No server specified on client '%s'\n", clientcfg->name);<br>+ return -1;<br>+ } else if (!ast_strlen_zero(clientcfg->refresh_token) &&<br>+ (ast_strlen_zero(clientcfg->oauth_clientid) || ast_strlen_zero(clientcfg->oauth_secret))) {<br>+ ast_log(LOG_ERROR, "No oauth_clientid or oauth_secret specified, so client '%s' can't be used\n", clientcfg->name);<br> return -1;<br> }<br> <br>@@ -778,6 +796,9 @@<br> /* If any configuration options are changing that would require reconnecting set the bit so we will do so if possible */<br> if (strcmp(clientcfg->user, oldclientcfg->user) ||<br> strcmp(clientcfg->password, oldclientcfg->password) ||<br>+ strcmp(clientcfg->refresh_token, oldclientcfg->refresh_token) ||<br>+ strcmp(clientcfg->oauth_clientid, oldclientcfg->oauth_clientid) ||<br>+ strcmp(clientcfg->oauth_secret, oldclientcfg->oauth_secret) ||<br> strcmp(clientcfg->server, oldclientcfg->server) ||<br> (clientcfg->port != oldclientcfg->port) ||<br> (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT) != ast_test_flag(&oldclientcfg->flags, XMPP_COMPONENT)) ||<br>@@ -2786,7 +2807,13 @@<br> }<br> <br> iks_insert_attrib(auth, "xmlns", IKS_NS_XMPP_SASL);<br>- iks_insert_attrib(auth, "mechanism", "PLAIN");<br>+ if (!ast_strlen_zero(cfg->refresh_token)) {<br>+ iks_insert_attrib(auth, "mechanism", "X-OAUTH2");<br>+ iks_insert_attrib(auth, "auth:service", "oauth2");<br>+ iks_insert_attrib(auth, "xmlns:auth", "http://www.google.com/talk/protocol/auth");<br>+ } else {<br>+ iks_insert_attrib(auth, "mechanism", "PLAIN");<br>+ }<br> <br> if (strchr(client->jid->user, '/')) {<br> char *user = ast_strdupa(client->jid->user);<br>@@ -3285,28 +3312,28 @@<br> {<br> iks *iq, *ping;<br> int res;<br>- <br>+<br> ast_debug(2, "JABBER: Sending Keep-Alive Ping for client '%s'\n", client->name);<br> <br> if (!(iq = iks_new("iq")) || !(ping = iks_new("ping"))) {<br> iks_delete(iq);<br> return -1;<br> }<br>- <br>+<br> iks_insert_attrib(iq, "type", "get");<br> iks_insert_attrib(iq, "to", to);<br> iks_insert_attrib(iq, "from", from);<br>- <br>+<br> ast_xmpp_client_lock(client);<br> iks_insert_attrib(iq, "id", client->mid);<br> ast_xmpp_increment_mid(client->mid);<br> ast_xmpp_client_unlock(client);<br>- <br>+<br> iks_insert_attrib(ping, "xmlns", "urn:xmpp:ping");<br> iks_insert_node(iq, ping);<br>- <br>+<br> res = ast_xmpp_client_send(client, iq);<br>- <br>+<br> iks_delete(ping);<br> iks_delete(iq);<br> <br>@@ -3627,6 +3654,13 @@<br> return -1;<br> }<br> <br>+ if (!ast_strlen_zero(clientcfg->refresh_token)) {<br>+ ast_debug(2, "Obtaining OAuth access token for client '%s'\n", client->name);<br>+ if (fetch_access_token(clientcfg)) {<br>+ return -1;<br>+ }<br>+ }<br>+<br> ast_xmpp_client_disconnect(client);<br> <br> client->timeout = 50;<br>@@ -3643,7 +3677,7 @@<br> <br> /* Set socket timeout options */<br> setsockopt(iks_fd(client->parser), SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval));<br>- <br>+<br> if (res == IKS_NET_NOCONN) {<br> ast_log(LOG_ERROR, "No XMPP connection available when trying to connect client '%s'\n", client->name);<br> return -1;<br>@@ -3728,7 +3762,7 @@<br> /* Log the message here, because iksemel's logHook is<br> unaccessible */<br> xmpp_log_hook(client, buf, len, 1);<br>- <br>+<br> if(buf[0] == ' ') {<br> ast_debug(1, "JABBER: Detected Google Keep Alive. "<br> "Sending out Ping request for client '%s'\n", client->name);<br>@@ -3867,6 +3901,42 @@<br> <br> /* All buddies are unlinked from the configuration buddies container, always */<br> return 1;<br>+}<br>+<br>+static int fetch_access_token(struct ast_xmpp_client_config *cfg)<br>+{<br>+ RAII_VAR(char *, cmd, NULL, ast_free);<br>+ char cBuf[1024] = "";<br>+ const char *url = "https://www.googleapis.com/oauth2/v3/token";<br>+ struct ast_json_error error;<br>+ RAII_VAR(struct ast_json *, jobj, NULL, ast_json_unref);<br>+<br>+ ast_asprintf(&cmd, "CURL(%s,client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token)",<br>+ url, cfg->oauth_clientid, cfg->oauth_secret, cfg->refresh_token);<br>+<br>+ ast_debug(2, "Performing OAuth 2.0 authentication for client '%s' using command: %s\n",<br>+ cfg->name, cmd);<br>+<br>+ if (!ast_func_read(NULL, cmd, cBuf, sizeof(cBuf) - 1)) {<br>+ ast_log(LOG_ERROR, "CURL is unavailable. This is required for OAuth 2.0 authentication of XMPP client '%s'. Please ensure it is loaded.\n",<br>+ cfg->name);<br>+ return -1;<br>+ }<br>+<br>+ ast_debug(2, "OAuth 2.0 authentication for client '%s' returned: %s\n", cfg->name, cBuf);<br>+<br>+ jobj = ast_json_load_string(cBuf, &error);<br>+ if (jobj) {<br>+ const char *token = ast_json_string_get(ast_json_object_get(jobj, "access_token"));<br>+ if (token) {<br>+ ast_string_field_set(cfg, password, token);<br>+ return 0;<br>+ }<br>+ }<br>+<br>+ ast_log(LOG_ERROR, "An error occurred while performing OAuth 2.0 authentication for client '%s': %s\n", cfg->name, cBuf);<br>+<br>+ return -1;<br> }<br> <br> static int xmpp_client_config_post_apply(void *obj, void *arg, int flags)<br>@@ -4622,8 +4692,8 @@<br> * Module loading including tests for configuration or dependencies.<br> * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,<br> * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails<br>- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the <br>- * configuration file or other non-critical problem return <br>+ * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the<br>+ * configuration file or other non-critical problem return<br> * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.<br> */<br> static int load_module(void)<br>@@ -4641,6 +4711,9 @@<br> <br> aco_option_register(&cfg_info, "username", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, user));<br> aco_option_register(&cfg_info, "secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, password));<br>+ aco_option_register(&cfg_info, "refresh_token", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, refresh_token));<br>+ aco_option_register(&cfg_info, "oauth_clientid", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, oauth_clientid));<br>+ aco_option_register(&cfg_info, "oauth_secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, oauth_secret));<br> aco_option_register(&cfg_info, "serverhost", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, server));<br> aco_option_register(&cfg_info, "statusmessage", ACO_EXACT, client_options, "Online and Available", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, statusmsg));<br> aco_option_register(&cfg_info, "pubsub_node", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, pubsubnode));<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/6157">change 6157</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/6157"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 14 </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I07f7052a502457ab55010a4d3686653b60f4c8db </div>
<div style="display:none"> Gerrit-Change-Number: 6157 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: Andrey <andr06@gmail.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>