Bug 795959 - Typing '@gmail.com' as part of the username shouldn't be required when setting up a gtalk account, r=clokep,a=Standard8
authorFlorian Qu?ze <florian@queze.net>
Tue, 02 Oct 2012 15:34:40 +0200
changeset 13457 583b589da03e19dc13d005dac50ab945bf6fc726
parent 13456 526e2bab1310ec32ce796fa6ef10688d7267d07a
child 13458 f0fc137009027780bc9e001b4de25799384af314
push id701
push userbugzilla@standard8.plus.com
push dateMon, 08 Oct 2012 19:06:59 +0000
treeherdercomm-beta@c9696e33af8e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersclokep, Standard8
bugs795959
Bug 795959 - Typing '@gmail.com' as part of the username shouldn't be required when setting up a gtalk account, r=clokep,a=Standard8
chat/protocols/gtalk/gtalk.js
chat/protocols/xmpp/xmpp-session.jsm
chat/protocols/xmpp/xmpp.jsm
--- a/chat/protocols/gtalk/gtalk.js
+++ b/chat/protocols/gtalk/gtalk.js
@@ -3,43 +3,83 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 Cu.import("resource:///modules/jsProtoHelper.jsm");
 Cu.import("resource:///modules/xmpp.jsm");
 Cu.import("resource:///modules/xmpp-session.jsm");
+Cu.import("resource:///modules/xmpp-xml.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "_", function()
   l10nHelper("chrome://chat/locale/xmpp.properties")
 );
 
+// PlainFullBindAuth is an authentication mechanism that works like
+// the standard PLAIN mechanism but adds a client-uses-full-bind-result
+// attribute to the auth stanza to tell the Google Talk servers that we
+// support their JID Domain Discovery extension.
+// See https://developers.google.com/talk/jep_extensions/jid_domain_change
+function PlainFullBindAuth(username, password, domain) {
+  this._key = btoa("\0"+ username + "\0" + password);
+}
+PlainFullBindAuth.prototype = {
+  next: function(aStanza) {
+    let attrs = {
+      mechanism: "PLAIN",
+      "xmlns:ga": "http://www.google.com/talk/protocol/auth",
+      "ga:client-uses-full-bind-result": "true"
+    };
+    return {
+      done: true,
+      send: Stanza.node("auth", Stanza.NS.sasl, attrs, this._key)
+    };
+  }
+};
+
 function GTalkAccount(aProtoInstance, aImAccount) {
   this._init(aProtoInstance, aImAccount);
 }
 GTalkAccount.prototype = {
   __proto__: XMPPAccountPrototype,
   connect: function() {
     this._jid = this._parseJID(this.name);
+    // The XMPP spec says that the node part of a JID is optional, but
+    // in the case of Google Talk if the username typed by the user
+    // doesn't contain an @, we prefer assuming that it's the domain
+    // part that's been omitted.
+    if (!this._jid.node) {
+      // If the domain part was omitted, swap the node and domain parts,
+      // use 'gmail.com' as the default domain, and tell the Google
+      // Talk server that we will use the full bind result.
+      this._jid.node = this._jid.domain;
+      this._jid.domain = "gmail.com";
+      this.authMechanisms = {PLAIN: PlainFullBindAuth};
+    }
 
     // For the resource, if the user has edited the option to a non
     // empty value, use that.
     if (this.prefs.prefHasUserValue("resource")) {
       let resource = this.getString("resource");
       if (resource)
         this._jid.resource = resource;
     }
     // Otherwise, if the username doesn't contain a resource, use the
     // value of the resource option (it will be the default value).
     // If we set an empty resource, XMPPSession will fallback to
     // XMPPDefaultResource (set to brandShortName).
     if (!this._jid.resource)
       this._jid.resource = this.getString("resource");
 
+    let jid = this._jid.node + "@" + this._jid.domain;
+    if (this._jid.resource)
+      jid += "/" + this._jid.resource;
+    this._jid.jid = jid;
+
     this._connection =
       new XMPPSession("talk.google.com", 443,
                       "require_tls", this._jid,
                       this.imAccount.password, this);
   }
 };
 
 function GTalkProtocol() {
--- a/chat/protocols/xmpp/xmpp-session.jsm
+++ b/chat/protocols/xmpp/xmpp-session.jsm
@@ -273,24 +273,25 @@ XMPPSession.prototype = {
         this._networkError(_("connection.error.noAuthMec"));
         return;
       }
 
       // Select the auth mechanism we will use. PLAIN will be treated
       // a bit differently as we want to avoid it over an unencrypted
       // connection, except if the user has explicly allowed that
       // behavior.
+      let authMechanisms = this._account.authMechanisms || XMPPAuthMechanisms;
       let selectedMech = "";
       let canUsePlain = false;
       mechs = mechs.getChildren("mechanism");
       for each (let m in mechs) {
         let mech = m.innerText;
         if (mech == "PLAIN" && !this._encrypted)
           canUsePlain = true;
-        else if (XMPPAuthMechanisms.hasOwnProperty(mech)) {
+        else if (authMechanisms.hasOwnProperty(mech)) {
           selectedMech = mech;
           break;
         }
       }
       if (!selectedMech && canUsePlain) {
         if (this._connectionSecurity == "allow_unencrypted_plain_auth")
           selectedMech = "PLAIN";
         else {
@@ -299,19 +300,19 @@ XMPPSession.prototype = {
           return;
         }
       }
       if (!selectedMech) {
         this.onError(Ci.prplIAccount.ERROR_AUTHENTICATION_IMPOSSIBLE,
                      _("connection.error.noCompatibleAuthMec"));
         return;
       }
-      this._auth = new XMPPAuthMechanisms[selectedMech](this._jid.node,
-                                                        this._password,
-                                                        this._domain);
+      this._auth = new authMechanisms[selectedMech](this._jid.node,
+                                                    this._password,
+                                                    this._domain);
 
       this._account.reportConnecting(_("connection.authenticating"));
       this.onXmppStanza = this.stanzaListeners.authDialog;
       this.onXmppStanza(null); // the first auth step doesn't read anything
     },
     authDialog: function(aStanza) {
       if (aStanza && aStanza.localName == "failure") {
         let errorMsg = "authenticationFailure";
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -563,16 +563,17 @@ function XMPPAccountBuddy(aAccount, aBud
 XMPPAccountBuddy.prototype = XMPPAccountBuddyPrototype;
 
 /* Helper class for account */
 const XMPPAccountPrototype = {
   __proto__: GenericAccountPrototype,
 
   _jid: null, // parsed Jabber ID: node, domain, resource
   _connection: null, // XMPP Connection
+  authMechanisms: null, // hook to let prpls tweak the list of auth mechanisms
 
   _init: function(aProtoInstance, aImAccount) {
     GenericAccountPrototype._init.call(this, aProtoInstance, aImAccount);
 
     /* Ongoing conversations */
     this._conv = {};
     this._buddies = {};
     this._mucs = {};