diff options
author | dequis <dx@dxzone.com.ar> | 2016-08-30 17:40:19 -0300 |
---|---|---|
committer | dequis <dx@dxzone.com.ar> | 2016-08-30 17:40:19 -0300 |
commit | 67ea361f9f8b2a197e8e52055e4d779a36a876f5 (patch) | |
tree | 91ac2400191960fecc133aa7653ff7eb9da33d18 | |
parent | 2dc394c7b0d7dfec1e0a7f553d3510c5efa98086 (diff) |
hipchat: Add basic support for personal oauth tokens
Fixes trac ticket 1265 - see the comments on that ticket for reasons on
why personal tokens and not the usual oauth flow. TL;DR hipchat doesn't
allow third party apps to own oauth client secrets. Instead, those are
generated when the "addon" is "installed" which requires a flow that is
either impossible or too awkward to use in bitlbee.
So, after giving up on the right way and telling the users to manage
tokens the ugly way, what's left to do is easy, just a few tweaks in the
sasl blob and short-circuit most of the actual oauth stuff. I didn't
even bother changing the service struct from google. It's not used.
This also updates the gtalk SASL X-OAUTH2 code to use GStrings instead of
juggling with malloc/strlen/strcpy, and simplifies a bit the way
GStrings are used in the equivalent SASL PLAIN code.
-rw-r--r-- | protocols/jabber/jabber.c | 10 | ||||
-rw-r--r-- | protocols/jabber/jabber.h | 1 | ||||
-rw-r--r-- | protocols/jabber/sasl.c | 83 |
3 files changed, 60 insertions, 34 deletions
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 11a90ff4..36f56fb1 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -81,11 +81,11 @@ static void jabber_init(account_t *acc) s = set_add(&acc->set, "server", NULL, set_eval_account, acc); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK; + set_add(&acc->set, "oauth", "false", set_eval_oauth, acc); + if (strcmp(acc->prpl->name, "hipchat") == 0) { set_setstr(&acc->set, "server", "chat.hipchat.com"); } else { - set_add(&acc->set, "oauth", "false", set_eval_oauth, acc); - /* this reuses set_eval_oauth, which clears the password */ set_add(&acc->set, "anonymous", "false", set_eval_oauth, acc); } @@ -396,7 +396,11 @@ static int jabber_buddy_msg(struct im_connection *ic, char *who, char *message, if (g_strcasecmp(who, JABBER_OAUTH_HANDLE) == 0 && !(jd->flags & OPT_LOGGED_IN) && jd->fd == -1) { - if (sasl_oauth2_get_refresh_token(ic, message)) { + + if (jd->flags & JFLAG_HIPCHAT) { + sasl_oauth2_got_token(ic, message, NULL, NULL); + return 1; + } else if (sasl_oauth2_get_refresh_token(ic, message)) { return 1; } else { imcb_error(ic, "OAuth failure"); diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index d76ee08f..db43f205 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -339,6 +339,7 @@ gboolean sasl_supported(struct im_connection *ic); void sasl_oauth2_init(struct im_connection *ic); int sasl_oauth2_get_refresh_token(struct im_connection *ic, const char *msg); int sasl_oauth2_refresh(struct im_connection *ic, const char *refresh_token); +void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error); extern const struct oauth2_service oauth2_service_google; diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c index 7778af1f..77a2cee0 100644 --- a/protocols/jabber/sasl.c +++ b/protocols/jabber/sasl.c @@ -38,13 +38,16 @@ const struct oauth2_service oauth2_service_google = "6C-Zgf7Tr7gEQTPlBhMUgo7R", }; +/* """"""""""""""""""""""""""""""oauth"""""""""""""""""""""""""""""" */ +#define HIPCHAT_SO_CALLED_OAUTH_URL "https://hipchat.com/account/api" + xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply; char *s; - int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0; + int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_hipchat_oauth = 0; int want_oauth = FALSE, want_hipchat = FALSE, want_anonymous = FALSE; GString *mechs; @@ -79,6 +82,8 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) sup_anonymous = 1; } else if (c->text && g_strcasecmp(c->text, "X-OAUTH2") == 0) { sup_gtalk = 1; + } else if (c->text && g_strcasecmp(c->text, "X-HIPCHAT-OAUTH2") == 0) { + sup_hipchat_oauth = 1; } if (c->text) { @@ -89,7 +94,7 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) } if (!want_oauth && !sup_plain && !sup_digest) { - if (sup_gtalk) { + if (sup_gtalk || sup_hipchat_oauth) { imcb_error(ic, "This server requires OAuth " "(supported schemes:%s)", mechs->str); } else { @@ -109,22 +114,36 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) xt_add_attr(reply, "xmlns", XMLNS_HIPCHAT); } - if (sup_gtalk && want_oauth) { - int len; + if ((sup_gtalk || sup_hipchat_oauth) && want_oauth) { + GString *gs; + + gs = g_string_sized_new(128); + + g_string_append_c(gs, '\0'); + + if (sup_gtalk) { + /* X-OAUTH2 is not *the* standard OAuth2 SASL/XMPP implementation. + It's currently used by GTalk and vaguely documented on + http://code.google.com/apis/cloudprint/docs/rawxmpp.html */ + xt_add_attr(reply, "mechanism", "X-OAUTH2"); - /* X-OAUTH2 is, not *the* standard OAuth2 SASL/XMPP implementation. - It's currently used by GTalk and vaguely documented on - http://code.google.com/apis/cloudprint/docs/rawxmpp.html . */ - xt_add_attr(reply, "mechanism", "X-OAUTH2"); + g_string_append(gs, jd->username); + g_string_append_c(gs, '\0'); + g_string_append(gs, jd->oauth2_access_token); + } else if (sup_hipchat_oauth) { + /* Hipchat's variant, not standard either, is documented here: + https://docs.atlassian.com/hipchat.xmpp/latest/xmpp/auth.html */ + xt_add_attr(reply, "mechanism", "oauth2"); + + g_string_append(gs, jd->oauth2_access_token); + g_string_append_c(gs, '\0'); + g_string_append(gs, set_getstr(&ic->acc->set, "resource")); + } - len = strlen(jd->username) + strlen(jd->oauth2_access_token) + 2; - s = g_malloc(len + 1); - s[0] = 0; - strcpy(s + 1, jd->username); - strcpy(s + 2 + strlen(jd->username), jd->oauth2_access_token); - reply->text = base64_encode((unsigned char *) s, len); + reply->text = base64_encode((unsigned char *) gs->str, gs->len); reply->text_len = strlen(reply->text); - g_free(s); + g_string_free(gs, TRUE); + } else if (want_oauth) { imcb_error(ic, "OAuth requested, but not supported by server"); imc_logout(ic, FALSE); @@ -148,7 +167,6 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) /* The rest will be done later, when we receive a <challenge/>. */ } else if (sup_plain) { - int len; GString *gs; char *username; @@ -173,12 +191,9 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data) g_string_append(gs, set_getstr(&ic->acc->set, "resource")); } - len = gs->len; - s = g_string_free(gs, FALSE); - - reply->text = base64_encode((unsigned char *) s, len); + reply->text = base64_encode((unsigned char *) gs->str, gs->len); reply->text_len = strlen(reply->text); - g_free(s); + g_string_free(gs, TRUE); } if (reply && !jabber_write_packet(ic, reply)) { @@ -427,20 +442,29 @@ gboolean sasl_supported(struct im_connection *ic) void sasl_oauth2_init(struct im_connection *ic) { struct jabber_data *jd = ic->proto_data; - char *msg, *url; imcb_log(ic, "Starting OAuth authentication"); /* Temporary contact, just used to receive the OAuth response. */ imcb_add_buddy(ic, JABBER_OAUTH_HANDLE, NULL); - url = oauth2_url(jd->oauth2_service); - msg = g_strdup_printf("Open this URL in your browser to authenticate: %s", url); - imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, msg, 0, 0); + + if (jd->flags & JFLAG_HIPCHAT) { + imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, + "Open this URL and generate a token with 'View Group' and 'Send Message' scopes: " + HIPCHAT_SO_CALLED_OAUTH_URL, 0, 0); + } else { + char *msg, *url; + + url = oauth2_url(jd->oauth2_service); + msg = g_strdup_printf("Open this URL in your browser to authenticate: %s", url); + imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, msg, 0, 0); + + g_free(msg); + g_free(url); + } imcb_buddy_msg(ic, JABBER_OAUTH_HANDLE, "Respond to this message with the returned " "authorization token.", 0, 0); - g_free(msg); - g_free(url); } static gboolean sasl_oauth2_remove_contact(gpointer data, gint fd, b_input_condition cond) @@ -453,9 +477,6 @@ static gboolean sasl_oauth2_remove_contact(gpointer data, gint fd, b_input_condi return FALSE; } -static void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, - const char *error); - int sasl_oauth2_get_refresh_token(struct im_connection *ic, const char *msg) { struct jabber_data *jd = ic->proto_data; @@ -485,7 +506,7 @@ int sasl_oauth2_refresh(struct im_connection *ic, const char *refresh_token) refresh_token, sasl_oauth2_got_token, ic); } -static void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error) +void sasl_oauth2_got_token(gpointer data, const char *access_token, const char *refresh_token, const char *error) { struct im_connection *ic = data; struct jabber_data *jd; |