commit c2f960e563837f6f9e56954cf4dd9f35489b2d13 Author: Sukhbir Singh sukhbir@torproject.org Date: Mon Jun 29 13:56:26 2015 -0400
Add in-band XMPP registration support (#12097) --- projects/instantbird/config | 6 +- .../instantbird/xmpp-inband-registration.patch | 278 ++++++++++++++++++++ projects/instantbird/xmppRegister.js | 127 +++++++++ projects/instantbird/xmppRegister.xul | 25 ++ 4 files changed, 435 insertions(+), 1 deletion(-)
diff --git a/projects/instantbird/config b/projects/instantbird/config index 656cdb4..63348dc 100644 --- a/projects/instantbird/config +++ b/projects/instantbird/config @@ -79,6 +79,9 @@ input_files: - filename: cert_override.txt - filename: ctcp-time.patch - filename: ctcp-ping.patch + - filename: xmpp-inband-registration.patch + - filename: xmppRegister.js + - filename: xmppRegister.xul - filename: xmpp-domain.patch - filename: xmpp-resource.patch - filename: xmpp-onion-js.patch @@ -100,10 +103,11 @@ input_files: - filename: branding/default.ico - filename: branding/instantbird.ico - filename: branding/name.patch - - filename: branding/osx.patch - filename: branding/instantbird.icns - filename: branding/credits.patch - filename: branding/about.png + - filename: branding/osx.patch + enable: '[% c("var/osx") %] - filename: fix-mingw-build.patch enable: '[% c("var/windows") %]' - filename: f2e7cea9bc6a-bug-1150967.patch diff --git a/projects/instantbird/xmpp-inband-registration.patch b/projects/instantbird/xmpp-inband-registration.patch new file mode 100644 index 0000000..73c838c --- /dev/null +++ b/projects/instantbird/xmpp-inband-registration.patch @@ -0,0 +1,278 @@ +diff --git a/chat/locales/en-US/xmpp.properties b/chat/locales/en-US/xmpp.properties +--- a/chat/locales/en-US/xmpp.properties ++++ b/chat/locales/en-US/xmpp.properties +@@ -8,16 +8,19 @@ + # (These will be displayed in account.connection.progress from + # accounts.properties, which adds … at the end, so do not include + # periods at the end of these messages.) + connection.initializingStream=Initializing stream + connection.initializingEncryption=Initializing encryption + connection.authenticating=Authenticating + connection.gettingResource=Getting resource + connection.downloadingRoster=Downloading contact list ++connection.registering=Registering new account with server ++connection.gettingRegistration=Getting registration form ++connection.onRegistrationSuccess=Account registration successful + + # LOCALIZATION NOTE (connection.error.*) + # These will show in the account manager if an error occurs during the + # connection attempt. + connection.error.invalidUsername=Invalid username (your username should contain an '@' character) + connection.error.failedToCreateASocket=Failed to create a socket (Are you offline?) + connection.error.serverClosedConnection=The server closed the connection + connection.error.resetByPeer=Connection reset by peer +@@ -28,16 +31,18 @@ connection.error.startTLSRequired=The se + connection.error.startTLSNotSupported=The server doesn't support encryption but your configuration requires it + connection.error.failedToStartTLS=Failed to start encryption + connection.error.noAuthMec=No authentication mechanism offered by the server + connection.error.noCompatibleAuthMec=None of the authentication mechanisms offered by the server are supported + connection.error.notSendingPasswordInClear=The server only supports authentication by sending the password in cleartext + connection.error.authenticationFailure=Authentication failure + connection.error.notAuthorized=Not authorized (Did you enter the wrong password?) + connection.error.failedToGetAResource=Failed to get a resource ++connection.error.noRegistrationSupport=The server does not support in-band registration ++connection.error.registrationCancel=Registration canceled + + + # LOCALIZATION NOTE (conversation.error.notDelivered): + # This is displayed in a conversation as an error message when a message + # the user has sent wasn't delivered. + # %S is replaced by the text of the message that wasn't delivered. + conversation.error.notDelivered=This message could not be delivered: %S + # This is displayed in a conversation as an error message when joining a MUC +diff --git a/chat/protocols/xmpp/xmpp-session.jsm b/chat/protocols/xmpp/xmpp-session.jsm +--- a/chat/protocols/xmpp/xmpp-session.jsm ++++ b/chat/protocols/xmpp/xmpp-session.jsm +@@ -6,16 +6,18 @@ const EXPORTED_SYMBOLS = ["XMPPSession", + + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + + Cu.import("resource:///modules/imXPCOMUtils.jsm"); + Cu.import("resource:///modules/socket.jsm"); + Cu.import("resource:///modules/xmpp-xml.jsm"); + Cu.import("resource:///modules/xmpp-authmechs.jsm"); + ++const registerWindow = "chrome://instantbird/content/xmppRegister.xul"; ++ + XPCOMUtils.defineLazyGetter(this, "_", function() + l10nHelper("chrome://chat/locale/xmpp.properties") + ); + + // Workaround because a lazy getter can't be exported. + XPCOMUtils.defineLazyGetter(this, "_defaultResource", function() + l10nHelper("chrome://branding/locale/brand.properties")("brandShortName") + ); +@@ -63,16 +65,17 @@ XMPPSession.prototype = { + __proto__: Socket, + connectTimeout: 60, + readWriteTimeout: 300, + sendPing: function() { + this.sendStanza(Stanza.iq("get", null, null, + Stanza.node("ping", Stanza.NS.ping)), + this.cancelDisconnectTimer, this); + }, ++ nodes: {}, + _lastReceiveTime: 0, + _lastSendTime: 0, + checkPingTimer(aJustSentSomething = false) { + // Don't start a ping timer if we're not fully connected yet. + if (this.onXmppStanza != this.stanzaListeners.accountListening) + return; + let now = Date.now(); + if (aJustSentSomething) +@@ -265,28 +268,95 @@ XMPPSession.prototype = { + _("connection.error.startTLSNotSupported")); + return; + } + + // If we aren't starting TLS, jump to the auth step. + this.onXmppStanza = this.stanzaListeners.startAuth; + this.onXmppStanza(aStanza); + }, ++ onRegisterResponse: function(aStanza) { ++ let error = this._account.parseError(aStanza); ++ if (error) { ++ this.onError(null, aStanza.getElement(["error"]).innerText); ++ return; ++ } ++ if (aStanza.attributes["type"] == "result") { ++ this._account.reportConnecting(_("connection.onRegistrationSuccess")); ++ this._account.prefs.setBoolPref("register", false); ++ this._account.connect(); ++ } ++ return; ++ }, ++ startRegister: function(aStanza) { ++ // Some servers do not support in-band registration. In that case, ++ // complain and quit the registration process. ++ let error = this._account.parseError(aStanza); ++ if (error) { ++ this.onError(null, _("connection.error.noRegistrationSupport")); ++ return; ++ } ++ ++ this._account.reportConnecting(_("connection.gettingRegistration")); ++ let registerStanza = aStanza.getChildrenByNS(Stanza.NS.register)[0]; ++ // If we get registration data, show the form, else quit. ++ if (registerStanza.getElement(["x"])) { ++ registerStanza.wrappedJSObject = registerStanza; ++ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"] ++ .getService(Ci.nsIWindowWatcher); ++ let win = ww.openWindow(null, registerWindow, "", ++ "centerscreen,chrome,modal,minimizable=no", registerStanza); ++ } else { ++ this.onError(null, _("connection.error.noRegistrationSupport")); ++ } ++ ++ // If the user cancelled the form, we should stop the registration. ++ if (this.nodes["cancel"]) ++ this.onError(null, _("connection.error.registrationCancel")); ++ ++ let xml = '<?xml version="1.0"?>'; ++ let fieldNodes = []; ++ for (let key in this.nodes) { ++ let node = Stanza.node("field", null, {"var": key}); ++ let childNode = Stanza.node("value"); ++ childNode.addText(this.nodes[key]); ++ node.addChild(childNode); ++ fieldNodes.push(node); ++ } ++ let registerResponse = Stanza.iq("set", null, this._domain, ++ Stanza.node("query", Stanza.NS.register, null, ++ Stanza.node("x", Stanza.NS.xdata, ++ {"type": "submit"}, fieldNodes))); ++ this.sendStanza(registerResponse); ++ this.onXmppStanza = this.stanzaListeners.onRegisterResponse; ++ }, + startTLS: function(aStanza) { + if (aStanza.localName != "proceed") { + this._networkError(_("connection.error.failedToStartTLS")); + return; + } + + this.startTLS(); + this._encrypted = true; + this.startStream(); + this.onXmppStanza = this.stanzaListeners.startAuth; + }, + startAuth: function(aStanza) { ++ // If the user has requested for a new account, we try to perform ++ // in-band registration first (if the server supports it) and then ++ // we authenticate. ++ if (this._account.getBool("register")) { ++ this._account.reportConnecting(_("connection.registering")); ++ let register = Stanza.iq("get", null, null, ++ Stanza.node("query", Stanza.NS.register)); ++ this.sendStanza(register); ++ this.onXmppStanza = this.stanzaListeners.startRegister; ++ return; ++ } ++ + if (aStanza.localName != "features") { + this.ERROR("Unexpected stanza " + aStanza.localName + ", expected 'features'"); + this._networkError(_("connection.error.incorrectResponse")); + return; + } + + let mechs = aStanza.getElement(["mechanisms"]); + if (!mechs) { +diff --git a/im/content/accountWizard.js b/im/content/accountWizard.js +--- a/im/content/accountWizard.js ++++ b/im/content/accountWizard.js +@@ -106,16 +106,20 @@ var accountWizard = { + }, + + showUsernamePage: function aw_showUsernamePage() { + let proto = this.proto.id; + if ("userNameBoxes" in this && this.userNameProto == proto) { + this.checkUsername(); + return; + } ++ ++ if (this.proto.id == "prpl-jabber") { ++ document.getElementById("registerXMPP").hidden = false; ++ } + + let bundle = document.getElementById("accountsBundle"); + let usernameInfo; + let emptyText = this.proto.usernameEmptyText; + if (emptyText) { + usernameInfo = + bundle.getFormattedString("accountUsernameInfoWithDescription", + [emptyText, this.proto.name]); +@@ -412,16 +416,18 @@ var accountWizard = { + createAccount: function aw_createAccount() { + let acc = Services.accounts.createAccount(this.username, this.proto.id); + if (!this.proto.noPassword && this.password) + acc.password = this.password; + if (this.alias) + acc.alias = this.alias; + //FIXME: newMailNotification + ++ acc.setBool("register", document.getElementById("registerXMPP").checked); ++ + for (let i = 0; i < this.prefs.length; ++i) { + let option = this.prefs[i]; + let opt = option.opt; + switch(opt.type) { + case opt.typeBool: + acc.setBool(option.name, option.value); + break; + case opt.typeInt: +diff --git a/im/content/accountWizard.xul b/im/content/accountWizard.xul +--- a/im/content/accountWizard.xul ++++ b/im/content/accountWizard.xul +@@ -60,16 +60,17 @@ + onpageshow="accountWizard.showUsernamePage();" + onpagehide="accountWizard.hideUsernamePage();" + onpagerewound="return accountWizard.rewindFromUsernamePage();"> + <description id="usernameInfo"/> + <separator/> + <vbox id="userNameBox"/> + <separator/> + <description id="duplicateAccount" hidden="true">&accountUsernameDuplicate.label;</description> ++ <checkbox id="registerXMPP" label="®isterXMPP.label;" hidden="true" /> + </wizardpage> + + <wizardpage id="accountpassword" pageid="accountpassword" next="accountadvanced" + label="&accountPasswordTitle.label;"> + <description>&accountPasswordInfo.label;</description> + <separator/> + <hbox id="passwordBox" align="baseline"> + <label value="&accountPasswordField.label;" control="password" id="passwordLabel"/> +diff --git a/im/content/jar.mn b/im/content/jar.mn +--- a/im/content/jar.mn ++++ b/im/content/jar.mn +@@ -56,16 +56,18 @@ instantbird.jar: + content/instantbird/proxies.css + content/instantbird/proxy.xml + * content/instantbird/tabbrowser.xml + content/instantbird/tabbrowser.css + content/instantbird/utilities.js + * content/instantbird/viewlog.xul + content/instantbird/viewlog.js + content/instantbird/viewlog.css ++* content/instantbird/xmppRegister.xul ++ content/instantbird/xmppRegister.js + #ifdef XP_MACOSX + * content/instantbird/hiddenWindow.xul + content/instantbird/menus-mac.xul + content/instantbird/macgestures.js + * content/instantbird/jsConsoleOverlay.xul + * content/instantbird/softwareUpdateOverlay.xul + #elifdef XP_WIN + content/instantbird/menus-win.xul +diff --git a/im/locales/en-US/chrome/instantbird/accountWizard.dtd b/im/locales/en-US/chrome/instantbird/accountWizard.dtd +--- a/im/locales/en-US/chrome/instantbird/accountWizard.dtd ++++ b/im/locales/en-US/chrome/instantbird/accountWizard.dtd +@@ -26,8 +26,10 @@ + <!ENTITY accountAliasInfo.label "This will only be displayed in your conversations when you talk, remote buddies won't see it."> + <!ENTITY accountProxySettings.caption "Proxy Settings"> + <!ENTITY accountProxySettings.change.label "Change…"> + <!ENTITY accountProxySettings.change.accessKey "C"> + + <!ENTITY accountSummaryTitle.label "Summary"> + <!ENTITY accountSummaryInfo.label "A summary of the information you entered is displayed below. Please check it before the account is created."> + <!ENTITY accountSummary.connectAutomatically.label "Connect this account automatically."> ++ ++<!ENTITY registerXMPP.label "Create this new account on the server"> diff --git a/projects/instantbird/xmppRegister.js b/projects/instantbird/xmppRegister.js new file mode 100644 index 0000000..052fa6c --- /dev/null +++ b/projects/instantbird/xmppRegister.js @@ -0,0 +1,127 @@ +const { interfaces: Ci, utils: Cu, classes: Cc } = Components; + +Cu.import("resource:///modules/imXPCOMUtils.jsm"); +Cu.import("resource:///modules/xmpp-session.jsm"); + +XPCOMUtils.defineLazyGetter(this, "_", function() + l10nHelper("chrome://branding/locale/brand.properties") +); + +let registerAccount = { + createElement: function(aType, aID, aValue) { + let element = document.createElement(aType); + if (aID) + element.setAttribute("id", aID); + if (aValue) + element.setAttribute("value", aValue); + return element; + }, + + createRow: function() { + let row = document.createElement("row"); + row.setAttribute("align", "baseline"); + return row; + }, + + onLoad: function() { + document.documentElement.getButton("accept").disabled = true; + + this.rows = document.getElementById("register-rows"); + + this.nodes = XMPPSession.prototype.nodes; + this.registerStanza = window.arguments[0].wrappedJSObject; + this.dataStanza = this.registerStanza.getElement(["x"]); + + let instructions = this.dataStanza.getElement(["instructions"]); + if (instructions) { + let instructionRow = this.createRow(); + let instructionLabel = this.createElement("label", null, instructions.innerText); + instructionRow.appendChild(instructionLabel); + this.rows.appendChild(instructionRow); + } + + let title = this.dataStanza.getElement(["title"]); + if (title) + document.title = title.innerText; + else + document.title = _("brandShortName"); + + for each (let ele in this.dataStanza.getElements(["field"])) { + let fieldType = ele.attributes["type"]; + switch (fieldType) { + + case "text-single": + case "text-private": + + let textRow = this.createRow(); + let textLabel = this.createElement("label", null, ele.attributes["label"]); + + let textBox = this.createElement("textbox", ele.attributes["var"], + ele.getElement(["value"]) ? ele.getElement(["value"]).innerText : ""); + if (fieldType == "text-private") + textBox.setAttribute("type", "password"); + if (ele.getElement(["required"])) + textBox.setAttribute("oninput", "onInput(this)"); + + textRow.appendChild(textLabel); + textRow.appendChild(textBox); + this.rows.appendChild(textRow); + break; + + case "fixed": + + let fixedRow = this.createRow(); + let fixedLabel = this.createElement("label", null, ele.getElement(["value"]).innerText); + fixedRow.appendChild(fixedLabel); + this.rows.appendChild(fixedRow); + break; + } + } + + // Some forms have an OCR field. In that case, show the OCR image + // and provide input for the same. + let ocr = this.dataStanza.getElements(["field"]).find(e => e.attributes["var"] == "ocr"); + if (ocr) { + let ocrRow = this.createRow(); + let ocrImage = this.createElement("image"); + ocrImage.setAttribute("src", "data:image/png;base64," + this.registerStanza.getElement(["data"]).innerText); + + let ocrLabel = this.createElement("label", null, ocr.attributes["label"]); + let ocrInput = this.createElement("textbox", ocr.attributes["var"], null); + + ocrRow.appendChild(ocrLabel); + ocrRow.appendChild(ocrInput); + this.rows.appendChild(ocrRow); + + let ocrBox = document.createElement("hbox"); + ocrBox.setAttribute("align", "center"); + ocrBox.appendChild(ocrImage); + + let ocrImageRow = this.createRow(); + ocrImageRow.appendChild(ocrBox); + this.rows.appendChild(ocrImageRow); + } + }, + + onSave: function() { + for each (let elements in this.dataStanza.getElements(["field"])) { + if (elements.attributes["var"] != undefined) { + let variable = elements.attributes["var"]; + if (document.getElementById(variable)) + this.nodes[variable] = document.getElementById(variable).value; + else + this.nodes[variable] = elements.getElement(["value"]).innerText; + } + } + delete this.nodes["cancel"]; + }, + + onCancel: function() { + // The form was cancelled so we quit the registration. + this.nodes["cancel"] = true; + }, +}; + +function onInput(e) { + document.documentElement.getButton("accept").disabled = !e.value; +} diff --git a/projects/instantbird/xmppRegister.xul b/projects/instantbird/xmppRegister.xul new file mode 100644 index 0000000..c21bc96 --- /dev/null +++ b/projects/instantbird/xmppRegister.xul @@ -0,0 +1,25 @@ +<?xml version="1.0" ?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css" ?> + +<dialog + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="registerDialog" + onload="registerAccount.onLoad()" + buttons="accept,cancel" + ondialogaccept="return registerAccount.onSave()" + ondialogcancel="registerAccount.onCancel()"> + + <script type="application/javascript" src="chrome://instantbird/content/xmppRegister.js" /> + + <grid flex="1"> + + <columns> + <column flex="1" /> + <column flex="1" /> + </columns> + + <rows id="register-rows" /> + + </grid> + +</dialog>
tor-commits@lists.torproject.org