Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add c2s support #30

Closed
wants to merge 6 commits into from

4 participants

@hpychan

Here is my c2s support for xmpp-server.

I used my forked version of vows to test.

hpychan/vows

I added the -i features in vows to support different lib, so I can see my code coverage.

The test command is
vows -i lib test/*

As a result, I updated test_jid.js as well.

Please tell me your thought.

hpychan

@astro
Owner

You know we're in the unfortunate situation of having two forks implementing c2s server. I'm currently tending towards the @superfeedr branch, just because Julien had me look into it earlier. Remember: release early, release often, and talk. Refusing contributions really gives me a bad conscience.

Nevertheless I want to give you some feedback. It could be nitpicking, but I don't want to hand your pull request down without technical reasons.

  • Your commits are huge and have non-descriptive messages. git provides nice means of representing development in a comprehensible way. (Julien's aren't much better: b9b6eba)
  • Roster isn't spelled Roaster
  • IqExtender looks useful. What is it for? :-) On the other hand, I don't like custom code performing magic for just specific parts.
  • Your tests are appreciated! Maybe we could merge them independently?
  • mechanisms belong to SASL and I'd like to have that kind of distinguishably separate from the XMPP stuff. We shall distinguish between client and server cases here too.
  • Specific queries like roster requests should not be handled by the library but the full-fledged XMPP server that will use the code one day.
  • Julien has implemented SASL PLAINTEXT for the server side as well. The other (digest) mechanisms that don't pass passwords in clear text are more interesting though.
  • TLS support is also quite important. We're having trouble with that.
  • XmppServer is named a bit unfortunate when it is c2s-specific. I think even the s2s-specific Router I added before is a misnomer.
  • Your code looks good, though style differs from the rest. I know that's a highly opinionated topic: I myself still have to convince emacs not to use tabs.
  • Would you mind explaining Logitech Connect? We've already got support for Facebook authentication, so I totally don't oppose such things.
@hpychan

Hi astro,

In my design, I want to allow anyone to extend code easily. I am not a believer on comments, but a strong believer on TDD. As a result, I got my code fullly coverage, yet might not have a good description. :)

Let me address your comments.

1) I will add more descriptive message
2) Rename to Roster should be a easy changes
3) The intention of IqExtender is to allow other developers to add more extension for IQ, for example Roaster, IQ Query Action Protocol, and etc
4) I am happy you like the tests. I can migrate the tests to you once you decide which c2s branch you want to merge into your code.
5) My design of mechanisms also have your same intention mind. I would like to allow the C2S server to only support SASL mechanisms they want. You can write a Digest SASL mechanism as well.
5) Roster can be removed as long as no iq extension is specified in constructor of XmppServer (C2SServer after change)
6) PLAINTEXT or Digest mechanism should be support by the library for marshalling purpose, the actual validation should be extensible by the mechansim, which can be a web service/database query.
7) I look into the Connection classes, I thought TLS has already support. I need to test it
8) I will rename it to C2SServer?
9) You like the code in tabs or spaces? I can follow your standard.

10) Logitech Connect is a iq extension that I want to work with. I don't think it should be part of the node-xmpp.

@superfeedr

Hey Hpychan, I think our c2s branch is now functional and works fine. However, one thing that it lacks is tests. I was hoping that maybe we could re-use the ones you used for your own branch.
I am not very familiar with vowsjs, but maybe you could point me to where I can start? Thanks!

@astro
Owner

Julien's branch has been merged now. Please help me maintaining it. :-)

@astro astro closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
112 examples/c2s.js
@@ -1,119 +1,39 @@
var xmpp = require('../lib/node-xmpp');
-var SoftwareVersion = require('./c2s_mods/software_version');
-/*
- * TODO
- * - support for Presence module
- * - Support for TLS () : https://groups.google.com/group/nodejs/browse_thread/thread/1e8e6501493d63b8#
- * - Admin tools
- * - Component interface
- * - Plugins for 'well-known' services (Roster, PubSub, PEP)
- * - Logging
- * - Shapers
- * - In-band registration
- * - mods :
- * - presence
- * - offline
- * - announce
- * - caps
- * - muc
- * - roster
- * - PEP
- */
+/* This is a very basic C2S example. One of the key design decisions of node-xmpp is to keep it very lightweight */
+/* If you need a full blown server check out https://github.com/superfeedr/xmpp-server */
// Sets up the server.
var c2s = new xmpp.C2S({
port: 5222,
- domain: 'localhost',
- tls: {
- keyPath: './examples/localhost-key.pem',
- certPath: './examples/localhost-cert.pem'
- }
+ domain: 'localhost'//,
+ // tls: {
+ // keyPath: './examples/localhost-key.pem',
+ // certPath: './examples/localhost-cert.pem'
+ // }
+
});
// Allows the developer to authenticate users against anything they want.
-c2s.on("authenticate", function(jid, password, client) {
- if(password == "password") {
- client.emit("auth-success", jid);
- }
- else {
- client.emit("auth-fail", jid);
- }
+c2s.on("authenticate", function(jid, password, client, cb) {
+ cb(true); // cb(false);
+});
+
+// Allows the developer to register the jid against anything they want
+c2s.on("register", function(jid, password, client, cb) {
+ cb(true); // cb(false, {code: "406", type: "modify", text: "not-acceptable"});
});
// On Connect event. When a client connects.
c2s.on("connect", function(client) {
// That's the way you add mods to a given server.
- SoftwareVersion.name = "Node XMPP server example";
- SoftwareVersion.version = "0.0.0.1";
- SoftwareVersion.os = "Mac OS X 10.7 Lion";
- client.addMixin(SoftwareVersion.mod);
});
// On Disconnect event. When a client disconnects
c2s.on("disconnect", function(client) {
-
});
// Most imoortant pieces of code : that is where you can configure your XMPP server to support only what you care about/need.
c2s.on("stanza", function(stanza, client) {
- var query, vCard;
- // We should provide a bunch of "plugins" for the functionalities below.
-
- // No roster support in this server!
- if (stanza.is('iq') && (session = stanza.getChild('query', 'jabber:iq:roster'))) {
- stanza.attrs.type = "error";
- stanza.attrs.to = stanza.attrs.from;
- delete stanza.attrs.from;
- client.send(stanza);
- }
- // No private support on this server
- else if (stanza.is('iq') && (query = stanza.getChild('query', "jabber:iq:private"))) {
- stanza.attrs.type = "error";
- stanza.attrs.to = stanza.attrs.from;
- delete stanza.attrs.from;
- client.send(stanza);
- }
- // No vCard support on this server.
- else if (stanza.is('iq') && (vCard = stanza.getChild('vCard', "vcard-temp"))) {
- stanza.attrs.type = "error";
- stanza.attrs.to = stanza.attrs.from;
- delete stanza.attrs.from;
- client.send(stanza);
- }
- // No DiscoInfo on this server.
- else if (stanza.is('iq') && (query = stanza.getChild('query', "http://jabber.org/protocol/disco#info"))) {
- stanza.attrs.type = "error";
- stanza.attrs.to = stanza.attrs.from;
- delete stanza.attrs.from;
- client.send(stanza);
- }
- // No Version support on this server.
- else {
-
- }
-})
-
-
-// You can also decide to rewrite many things, like for example the way you route stanzas.
-// This will allow for clustering for your node-xmpp server, using redis's PubSub feature.
-// To run this example in its full "power", just run node exmaple c2s.js from 2 different machines, as long as they share the redis server, they should be able to communicate!
-// var sys = require("sys");
-// var redis = require("redis-node");
-// var redispub = redis.createClient();
-// var redissub = redis.createClient();
-//
-// xmpp.C2S.prototype.route = function(stanza) {
-// var self = this;
-// if(stanza.attrs && stanza.attrs.to) {
-// var toJid = new xmpp.JID(stanza.attrs.to);
-// redispub.publish(toJid.bare().toString(), stanza.toString());
-// }
-// }
-// xmpp.C2S.prototype.registerRoute = function(jid, client) {
-// redissub.subscribeTo(jid.bare().toString(), function(channel, stanza, pattern) {
-// client.send(stanza);
-// });
-// return true;
-// }
+});
View
46 examples/c2s_mods/software_version.js
@@ -1,46 +0,0 @@
-var xmpp = require('node-xmpp');
-
-// // XEP-0092: Software Version
-exports.name = "mod_version";
-
-exports.default = {
- name: "node-xmpp Server",
- version: "1.3.3.7",
- os: "earth OS"
-};
-
-function SoftwareVersionMixin(client) {
- client.on('stanza', function(stanza) {
- if (stanza.is('iq') && (query = stanza.getChild('query', "jabber:iq:version"))) {
- stanza.attrs.type = "result";
- stanza.attrs.to = stanza.attrs.from;
- delete stanza.attrs.from;
-
- // Actual version attributes
- if(typeof(exports.name) === "undefined") {
- query.c("name").t(exports.default.name);
- }
- else {
- query.c("name").t(exports.name);
- }
-
- if(typeof(exports.version) === "undefined") {
- query.c("version").t(exports.default.version);
- }
- else {
- query.c("version").t(exports.version);
- }
-
- if(typeof(exports.os) === "undefined") {
- query.c("os").t(exports.default.os);
- }
- else {
- query.c("os").t(exports.os);
- }
- client.send(stanza);
- }
- });
-}
-
-exports.mod = SoftwareVersionMixin;
-
View
13 examples/localhost-cert.pem
@@ -1,13 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICATCCAWoCCQDnhGmqEMOAnTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
-VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
-cyBQdHkgTHRkMB4XDTExMDgyNjIwMDkxM1oXDTExMDkyNTIwMDkxM1owRTELMAkG
-A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
-IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA26k/
-+Nb9PgjAIMlnMCnZuTfuXsKi5AG8ZWlD/iBrXmS+bDnlclsVoUExVz/Z8ZCJfUcY
-COHmVVwNiOtRBuSwfpQ9G0RLOKSmxRhd8v7pmV20poqcw7jtk8T4YlbB2a/TafgT
-pxLKN12C8p+NODQTQSjg0XPnLdk+xwEna7/m4zcCAwEAATANBgkqhkiG9w0BAQUF
-AAOBgQCE0tU6WZ0byq+kLPnR3aZlbh+Zu/8fWrnhqhx2I8zs15v8yLq34D3AplOl
-kahYzqOhJnr52xJ8tLf6TeZxRVk6Nl2B94Y76abcD8p1TwWOlOBd8Omt6XnUxvp0
-QEFNdDLh8H4PSj5LxAF+GwceoTaB3typYMw64aM/jYJisNxcXg==
------END CERTIFICATE-----
View
11 examples/localhost-csr.pem
@@ -1,11 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh
-MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
-AQUAA4GNADCBiQKBgQDbqT/41v0+CMAgyWcwKdm5N+5ewqLkAbxlaUP+IGteZL5s
-OeVyWxWhQTFXP9nxkIl9RxgI4eZVXA2I61EG5LB+lD0bREs4pKbFGF3y/umZXbSm
-ipzDuO2TxPhiVsHZr9Np+BOnEso3XYLyn404NBNBKODRc+ct2T7HASdrv+bjNwID
-AQABoAAwDQYJKoZIhvcNAQEFBQADgYEAMpd7dWOruYc5sNVQ1NX5qJd9LnYcq0wq
-FjoNSjza+aT/ZC/D8vQXRwGpwnZJzFjo0EqF+jMsDMQ75GeDCqxMYsZ9oX9fcip/
-h0h5wUKfmzoKytDcUdNg0P3Jjag0rxNVYnZ7Z//bt/zOX4gfbmIOG1ChkA3UT1XB
-RxaRrC0yPA8=
------END CERTIFICATE REQUEST-----
View
15 examples/localhost-key.pem
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXQIBAAKBgQDbqT/41v0+CMAgyWcwKdm5N+5ewqLkAbxlaUP+IGteZL5sOeVy
-WxWhQTFXP9nxkIl9RxgI4eZVXA2I61EG5LB+lD0bREs4pKbFGF3y/umZXbSmipzD
-uO2TxPhiVsHZr9Np+BOnEso3XYLyn404NBNBKODRc+ct2T7HASdrv+bjNwIDAQAB
-AoGALCmC6wyOLYKk3fKgBFblpw9PpU2MYjrXHaxkNxtdesTWY/vf3XXii2EIAnqb
-3q0odz9r3Z1NKawwLG7jA3fG48yfVfgOB89RKNpaeaFI+tgmtKEGSpV0nlXBfbz5
-ijjre0oax0fIyKuLudKVusXM6x2+sRcSELBUfTyt7Z7/ZUECQQDuekx13a5i1mlL
-ty5+plkmIxzZHIrwemAoAyyRgNQ9B79gv11tVC9LPtgSKLUZvpInp9N9M/PYawjb
-/sEsToZTAkEA680EIdF8oWMVySrPJyXy8b+b4mfnawyyZrb38AMske1QVK+AJ2cZ
-3LYWizmp5I58v3BZZwSF9sda9JoWqbmLDQJAcSgUycSzb3Cfq+6yoCPcn0z/7kEt
-6CeZIsNGzY/cpUzc0VJcSXktq72ZFxy7ugiEH07x+jHBncpD/Odnz9c+wwJBAK6s
-vLbgnFZhNC5FTBdQAVwb+LoLBl9ClJWKTLi7YTToe22AZIrL0ViyepjAKp5o/vhB
-nbi2nRZmL3aq/sbT77ECQQDVIDfGlfLzHSEg67wyiKY9MgdrxxCfCQWXVMpuK846
-Ruy0IgjINq+90Cc5rPFeTnrsMm0iJ/ps1+OH2+ybQxea
------END RSA PRIVATE KEY-----
View
79 lib/xmpp/c2s.js
@@ -25,7 +25,7 @@ function C2S(options) {
// And now start listening to connections on the port provided as an option.
net.createServer(function (inStream) {
self.acceptConnection(inStream, options);
- }).listen(options.port, options.host);
+ }).listen(options.port);
// Load TLS key material
if (options.tls)
@@ -52,7 +52,7 @@ C2S.prototype.acceptConnection = function(socket, options) {
this.emit("connect", client);
socket.addListener('error', function() { });
client.addListener('error', function() { });
-}
+};
/**
* C2S Router */
@@ -72,7 +72,7 @@ C2S.prototype.route = function(stanza) {
else {
// Huh? Who is it for? and why did it end up here?
}
-}
+};
/**
* Registers a route (jid => specific client connection)
@@ -83,7 +83,7 @@ C2S.prototype.registerRoute = function(jid, client) {
this.sessions[jid.bare().toString()] = {};
this.sessions[jid.bare().toString()][jid.resource] = client;
return true;
-}
+};
/**
* Unregisters a route (jid => specific client connection)
@@ -95,7 +95,7 @@ C2S.prototype.unregisterRoute = function(jid, client) {
delete this.sessions[jid.bare().toString()][jid.resource];
}
return true;
-}
+};
function C2SClient(socket, c2s) {
@@ -117,18 +117,6 @@ function C2SClient(socket, c2s) {
self.onRawStanza(stanza);
});
- this.addListener('auth-success', function(jid) {
- self.jid = jid;
- self.authentified = true;
- self.stopParser();
- self.send(new ltx.Element("success", { xmlns: NS_XMPP_SASL }));
- self.startParser();
- });
-
- this.addListener('auth-fail', function(jid) {
- self.send(new ltx.Element("failure", { xmlns: NS_XMPP_SASL }));
- });
-
this.addListener('close', function() {
// We need to remove this user's connection, if authed:
if (self.jid) {
@@ -140,6 +128,7 @@ function C2SClient(socket, c2s) {
return self;
};
sys.inherits(C2SClient, Connection.Connection);
+exports.C2SClient = C2SClient;
C2SClient.prototype.startStream = function(streamAttrs) {
var attrs = {};
@@ -169,14 +158,14 @@ C2SClient.prototype.startStream = function(streamAttrs) {
this.send(s.substr(0, s.indexOf(' </stream:stream>')));
this.sendFeatures();
-}
+};
C2SClient.prototype.sendFeatures = function() {
var features = new ltx.Element('stream:features');
if(!this.authentified) {
// TLS
if(this.c2s.options.tls && !this.socket.encrypted) {
- features.c("starttls", {xmlns: NS_XMPP_TLS}).c("required")
+ features.c("starttls", {xmlns: NS_XMPP_TLS}).c("required");
}
this.mechanisms = sasl.availableMechanisms();
@@ -190,7 +179,7 @@ C2SClient.prototype.sendFeatures = function() {
features.c("session", {xmlns: 'urn:ietf:params:xml:ns:xmpp-session'});
}
this.send(features);
-}
+};
C2SClient.prototype.onRawStanza = function(stanza) {
var bind, session;
@@ -222,16 +211,33 @@ C2SClient.prototype.onRawStanza = function(stanza) {
stanza.attrs.to != this.jid.bare().toString()) {
this.c2s.router.send(stanza);
}
+ else if(stanza.is('iq') &&
+ (inband = stanza.getChild('query', 'jabber:iq:register'))) {
+ this.onRegistration(stanza);
+ }
else {
// Let's trigger self for Mixins
this.emit('stanza', stanza, this);
// Then send it to the user code, just in case the user wants to interract with it.
this.c2s.emit('stanza', stanza, this);
}
-}
+};
C2SClient.prototype.authenticate = function(username, password) {
- this.c2s.emit('authenticate', new JID(username, this.c2s.options.domain), password, this);
+ var self = this;
+ var jid = new JID(username, this.c2s.options.domain);
+ this.c2s.emit('authenticate', jid, password, this, function(authenticated) {
+ if(authenticated) {
+ self.jid = jid;
+ self.authentified = true;
+ self.stopParser();
+ self.send(new ltx.Element("success", { xmlns: NS_XMPP_SASL }));
+ self.startParser();
+ }
+ else {
+ self.send(new ltx.Element("failure", { xmlns: NS_XMPP_SA0SL }));
+ }
+ });
};
C2SClient.prototype.onAuth = function(stanza) {
@@ -247,9 +253,32 @@ C2SClient.prototype.onAuth = function(stanza) {
}
};
+C2SClient.prototype.onRegistration = function(stanza) {
+ var self = this;
+ register = stanza.getChild('query', 'jabber:iq:register');
+ if(stanza.attrs.type === 'get') {
+ stanza.attrs.type = "result";
+ register.c("instructions").t("Choose a username and password for use with this service. ");
+ register.c("username");
+ register.c("password");
+ self.send(stanza);
+ }
+ else if(stanza.attrs.type === 'set') {
+ self.c2s.emit('register', new JID(register.getChild('username', 'jabber:iq:register').getText(), this.c2s.options.domain), register.getChild('password', 'jabber:iq:register').getText(), self, function(registered, reason) {
+ if(registered) {
+ self.send(new ltx.Element("iq", {type: "result", id: stanza.attrs.id}));
+ } else {
+ stanza.attrs.type = "error";
+ stanza.c("error", {code: reason.code, type: reason.type}).c(reason.text, {xmlns: "urn:ietf:params:xml:ns:xmpp-stanzas"});
+ self.send(stanza);
+ }
+ });
+ }
+};
+
C2SClient.prototype.onBind = function(stanza) {
var self = this;
- var bind = stanza.getChild('bind', 'urn:ietf:params:xml:ns:xmpp-bind')
+ var bind = stanza.getChild('bind', 'urn:ietf:params:xml:ns:xmpp-bind');
if(resource = bind.getChild("resource", 'urn:ietf:params:xml:ns:xmpp-bind')) {
self.jid.setResource(resource.getText());
}
@@ -259,12 +288,12 @@ C2SClient.prototype.onBind = function(stanza) {
if(self.c2s.registerRoute(self.jid, self)) {
self.send(new ltx.Element("iq", {type:"result", id: stanza.attrs.id}).c("bind", { xmlns: "urn:ietf:params:xml:ns:xmpp-bind"}).c("jid").t(self.jid.toString()));
}
-}
+};
C2SClient.prototype.onSession = function(stanza) {
var self = this;
self.send(new ltx.Element("iq", {type:"result", id: stanza.attrs.id}).c("session", { xmlns: "urn:ietf:params:xml:ns:xmpp-session"}));
-}
+};
function generateId() {
View
4 lib/xmpp/sasl.js
@@ -41,7 +41,7 @@ function Plain() {
var params = auth.split("\x00");
this.username = params[1];
client.authenticate(this.username, params[2]);
- }
+ };
}
sys.inherits(Plain, Mechanism);
@@ -175,7 +175,7 @@ function DigestMD5() {
return false;
}
this.response = dict.response;
- return true
+ return true;
};
View
6 package.json
@@ -5,7 +5,13 @@
,"author": "Stephan Maka"
,"dependencies": {"node-expat": ">=1.3.1"
,"ltx": ">= 0.0.4"
+ ,"node-stringprep" : ">=0.0.5"
}
+ ,"devDependencies" : { "v8-profiler" : ">=0.0.3"
+ ,"vows": ">= 0.5.9"
+ ,"nodemock": ">= 0.2.12"
+ ,"horaa": ">= 0.1.1alpha"
+ }
,"repositories": [{"type": "git"
,"path": "git://github.com/astro/node-xmpp.git"
}]
View
577 test/test_c2s.js
@@ -0,0 +1,577 @@
+
+var sys = require("sys");
+var horaa = require('horaa');
+var ltx = require('ltx');
+
+var nodemock = require("nodemock");
+var xmpp = require("xmpp");
+
+var vows = require('vows'),
+assert = require('assert');
+
+var C2S = require('xmpp/c2s').C2S;
+
+var NS_JABBER_IQ_ROSTER = 'jabber:iq:roster';
+var NS_XMPP_SASL = "urn:ietf:params:xml:ns:xmpp-sasl";
+var NS_XMPP_BIND = "urn:ietf:params:xml:ns:xmpp-bind";
+var NS_XMPP_SESSION = "urn:ietf:params:xml:ns:xmpp-session";
+var NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams";
+var NS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
+
+function sizeOf(obj) {
+
+ var size = 0, key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) size++;
+ }
+ return size;
+}
+
+function xmlToObject( featuresStr ) {
+
+ sys.puts( "server sent:");
+ sys.puts( featuresStr );
+ sys.puts( "\n\n");
+
+ try {
+ return ltx.parse(featuresStr);
+ } catch (e) {
+ assert.isTrue( false );
+ }
+}
+
+function streamFromClient(c2sClient, clientStream) {
+
+ sys.puts( "server retrieved:");
+ sys.puts( clientStream);
+ sys.puts( "\n\n");
+
+ c2sClient.onData(clientStream);
+}
+
+function c2sClientInitiatesStream(domain, client)
+{
+ var clientStream = "";
+ clientStream += "<?xml version='1.0'?>";
+ clientStream += "<stream:stream ";
+ clientStream += "to='"+ domain + "' ";
+ clientStream += "xmlns='jabber:client' ";
+ clientStream += "xmlns:stream='http://etherx.jabber.org/streams' ";
+ clientStream += "version='1.0' >";
+ streamFromClient(client, clientStream);
+}
+
+function readFeatures( socket ) {
+
+ // server send back a stream
+ var pattern = "";
+ pattern += '<stream:stream ';
+ pattern += 'xmlns:stream="http://etherx.jabber.org/streams" ';
+ pattern += 'xmlns="jabber:client" ';
+ pattern += 'version="1.0" ';
+ pattern += 'id="([a-zA-Z_0-9]*)" ';
+ pattern += 'from="([a-zA-Z\.]*)"';
+ pattern += '>(.*)';
+ var regEx = new RegExp(pattern,'g');
+ //sys.puts( 'pattern : ' + pattern );
+ //sys.puts( 'stream : ' + socket.streamResult );
+ var strings = regEx.exec( socket.streamResult );
+ socket.streamResult = '';
+ //sys.puts( strings );
+ assert.isNotNull( strings );
+ var featuresStr = strings[3];
+ return xmlToObject( featuresStr );
+}
+
+function readError( socket ) {
+
+ // server send back a stream
+ var pattern = "";
+ pattern += '(.*)</stream:stream>';
+ var regEx = new RegExp(pattern,'g');
+ var strings = regEx.exec( socket.streamResult );
+ socket.streamResult = '';
+ assert.isNotNull( strings );
+ var errorStr = strings[1];
+ return xmlToObject( errorStr );
+}
+
+
+function encode64(decoded) {
+ return (new Buffer(decoded, 'utf8')).toString('base64');
+}
+
+function getPlainAuth( params) {
+ var decodedString = params.authzid + '\0' + params.authcid + '\0' + params.password;
+ return encode64( decodedString );
+}
+
+function getPlainAuthByUserName( username ) {
+ return getPlainAuth( {
+ 'authzid' : username + '@myharmony.com',
+ 'authcid' : username,
+ 'password' : '1234'
+ } );
+}
+
+function clientSendEmptyAuth(client, mechanismType ) {
+
+ var clientStream = "";
+ clientStream += "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'";
+ clientStream += " mechanism='"+mechanismType+"'/>";
+ streamFromClient(client, clientStream);
+}
+
+function clientSendAuth(client, mechanismType, encodedResponse ) {
+ var clientStream = "";
+ clientStream += "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'";
+ clientStream += " mechanism='"+mechanismType+"'>" + encodedResponse +"</auth>";
+ streamFromClient(client, clientStream);
+}
+
+function clientSendResponse(client, encodedResponse ) {
+ var clientStream = "";
+ clientStream += "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'";
+ clientStream += " >" + encodedResponse +"</response>";
+ streamFromClient(client, clientStream);
+}
+
+function clientSendAbort(client, encodedResponse ) {
+ var clientStream = "";
+ clientStream += "<abort xmlns='urn:ietf:params:xml:ns:xmpp-sasl'";
+ clientStream += " >" + encodedResponse +"</abort>";
+ streamFromClient(client, clientStream);
+}
+
+function verifyChallengeTag(socket, challenge)
+{
+ var challengeTag = xmlToObject( socket.streamResult );
+ socket.streamResult = '';
+ assert.equal (challengeTag.is('challenge', NS_XMPP_SASL), true );
+ assert.equal (challengeTag.getText(), challenge );
+}
+
+function verifySuccessTag(socket ) {
+ var success = xmlToObject( socket.streamResult );
+ socket.streamResult = '';
+ assert.equal ( success.is('success', NS_XMPP_SASL), true);
+}
+
+function verifyAuthError(socket, errortype)
+{
+ var failureTag = getError( socket );
+ assert.equal (failureTag.is('failure', NS_XMPP_SASL), true );
+ var message = failureTag.getChild(errortype, NS_XMPP_SASL);
+ assert.equal ('<'+errortype+'/>',message.toString() );
+}
+
+function xmppAuthSucceed( domain, client, socket ) {
+ // 6.5.9: Server informs client of successful authentication:
+ verifySuccessTag(socket);
+
+ /*
+ // 6.5.10: Client initiates a new stream to server
+ c2sClientInitiatesStream( domain, client );
+ // 6.5.11: Server responds by sending a stream header to client :
+ var features = readFeatures( socket );
+ assert.equal ( true, features.is('features') );
+ var bind = features.getChild('bind', NS_XMPP_BIND);
+ assert.isNotNull ( bind );
+ var session = features.getChild('bind', NS_XMPP_SESSION);
+ assert.isNotNull ( session );*/
+}
+
+
+function clientSendIqBind(client, resourceId, iqtype) {
+
+ if ( !iqtype ) {
+ iqtype = 'set';
+ }
+ var clientStream = "";
+ clientStream += "<iq type='"+iqtype+"' id='bind_1'>";
+ clientStream += "<bind xmlns='"+NS_XMPP_BIND+"'>";
+ clientStream += "<resource>"+resourceId+"</resource>";
+ clientStream += "</bind>";
+ clientStream += "</iq>";
+ streamFromClient(client, clientStream);
+}
+
+function clientSendEmptyIqBind(client) {
+
+ var clientStream = "";
+ clientStream += "<iq type='set' id='bind_1'>";
+ clientStream += "<bind xmlns='"+NS_XMPP_BIND+"'/>";
+ clientStream += "</iq>";
+ streamFromClient(client, clientStream);
+}
+
+function verifyIqBindResult(socket, userName, domain, resourceId, currentJid){
+ var iqTag = xmlToObject( socket.streamResult );
+ socket.streamResult = '';
+ assert.equal ( iqTag.is('iq'), true);
+ assert.equal ( iqTag.attrs['type'], 'result');
+ var bindTag = iqTag.getChild('bind', NS_XMPP_BIND);
+ var jidTag = bindTag.getChild('jid', NS_XMPP_BIND);
+ var jid = new xmpp.JID( jidTag.getText() );
+ assert.equal ( jid.user, userName);
+ assert.equal ( jid.domain, domain);
+ assert.equal ( jid.resource, resourceId );
+ assert.equal ( currentJid.user, userName);
+ assert.equal ( currentJid.domain, domain);
+ assert.equal ( currentJid.resource, resourceId );
+}
+
+var api = {
+ createC2S: function(domain){
+ return function() {
+ var port = 5222;
+ var router_port = 5269;
+
+ var mockedServerSocket = nodemock.mock("listen").takes( port );
+ var mockedRouterServerSocket = nodemock.mock("listen").takes( router_port, "::" );
+
+ var counter = 0;
+ var response = null;
+ var router_response = null;
+ var httpsHoraa = horaa('net');
+ httpsHoraa.hijack('createServer', function( cb ) {
+ counter++;
+ if ( counter == 1 ) {
+ response = cb;
+ return mockedServerSocket;
+ } else {
+ router_response = cb;
+ return mockedRouterServerSocket;
+ }
+ } );
+
+
+ var server = new C2S( { 'domain' : domain
+ , "port" : port
+ } );
+
+ return { 'server' : server, 'domain' : domain, 'acceptctrl' : response };
+ };
+ },
+ createC2SClient: function(domain){
+ return function(params) {
+
+ var server = params.server;
+
+ server.addListener( "connect", function( c2sclient ) {
+ params.client = c2sclient;
+ });
+ // arrange
+ //sys.puts( server.clients );
+
+ //sys.puts("createIncomingClient\n\n");
+ var mockSocket = {};
+ mockSocket.addListener = function( e, cb ){};
+ mockSocket.writable = true;
+ mockSocket.streamResult = '';
+ mockSocket.setKeepAlive = function( a, b ){};
+ mockSocket.write = function(data) { this.streamResult += data.toString();};
+ mockSocket.end = function() {};
+ assert.equal( sizeOf(server.sessions), 0 );
+ // act
+ params.acceptctrl(mockSocket);
+ // assert
+ assert.equal( sizeOf(server.sessions), 0 );
+
+ params['socket'] = mockSocket;
+ return params;
+ };
+ },
+ c2sServerStartStream : function(){
+ return function( params ){
+ //sys.puts("serverStartStream\n\n");
+
+ // 6.5.1 Client initiates stream to server:
+ var client = params.client;
+ var socket = params.socket;
+ c2sClientInitiatesStream( params.server.options.domain, client );
+ // 6.5.2 Server responds with a stream tag sent to client:
+ // and 6.5.3 Server informs client of available authentication mechanisms
+ var features = readFeatures( socket );
+ assert.equal ( true, features.is('features') );
+ var mechanisms = features.getChild('mechanisms', NS_XMPP_SASL);
+ assert.isNotNull ( mechanisms);
+ var mechanismChildren = mechanisms.getChildren('mechanism', NS_XMPP_SASL);
+ assert.equal ("PLAIN", mechanismChildren[0].getText() );
+ return params;
+ };
+ },
+ fastSASLNegotiation: function (userName) {
+ return function( params ) {
+
+ var client = params.client;
+ var socket = params.socket;
+ var domain = params.server.options.domain;
+ params.server.addListener('authenticate', function ( jid, password, client, cb ) {
+ cb(true);
+ } );
+
+ clientSendAuth(client, 'PLAIN', getPlainAuthByUserName(userName));
+
+
+ // 6.5.9 - 6.5.11
+ xmppAuthSucceed( domain, client, socket );
+ params.userName = userName;
+
+ return params;
+ };
+ },
+ slowSASLNegotiation: function (userName) {
+ return function( params ) {
+ var client = params.client;
+ var socket = params.socket;
+ var domain = params.server.options.domain;
+
+ params.server.addListener('authenticate', function ( jid, password, client, cb ) {
+ cb(true);
+ } );
+
+ // 6.5.4
+ clientSendEmptyAuth(client, 'PLAIN');
+ // 6.5.5 Server sends a [BASE64] encoded challenge to client:
+ verifyChallengeTag(socket, "");
+
+ // 6.5.6: Client sends a [BASE64] encoded response to the challenge:
+ clientSendResponse( client, getPlainAuthByUserName(userName) );
+
+ // 6.5.9 - 6.5.11
+ xmppAuthSucceed( domain, client, socket );
+ params.userName = userName;
+ return params;
+ };
+ },
+ bindUser: function (resourceId) {
+ return function ( params ) {
+ var client = params.client;
+ var socket = params.socket;
+
+ clientSendIqBind( client, resourceId );
+
+ verifyIqBindResult(socket, params.userName, params.domain, resourceId, client.jid);
+ return params;
+ };
+ }
+};
+//Create a Test Suite
+vows.describe('C2S server ').addBatch({
+ 'a c2s server created': {
+ topic : api.createC2S('github.com') ,
+ 'c2s client connected': {
+ topic : api.createC2SClient( ) ,
+ "c2s client close" : function( params ) {
+ var server = params.server;
+ var client = params.client;
+ assert.isNotNull( client);
+ client.emit( 'close', 'client error');
+
+ }
+ },
+ 'a c2s server created': {
+ topic : api.createC2S('github.com') ,
+ 'c2s client connected': {
+ topic : api.createC2SClient( ) ,
+ "retrieve stream from client" : {
+ topic : api.c2sServerStartStream(),
+ "testing" : function( params )
+ {
+
+ }
+ }
+ }
+ },
+ 'SASL negotiation (PLAIN) - shortcut': {
+ topic : api.createC2S('github.com') ,
+ 'c2s client connected': {
+ topic : api.createC2SClient( ) ,
+ 'client initiates a stream, and server responds with a stream tag sent to client along with auth features (6.5.1 to 6.5.3)': {
+ topic: api.c2sServerStartStream(),
+ "empty auth retrieved (correct encoding) , server sends encoded challenge": api.fastSASLNegotiation()
+ }
+ }
+ },
+ 'SASL negotiation (PLAIN)': {
+ topic : api.createC2S('github.com') ,
+ 'c2s client connected': {
+ topic : api.createC2SClient( ) ,
+ 'client initiates a stream, and server responds with a stream tag sent to client along with auth features (6.5.1 to 6.5.3)': {
+ topic: api.c2sServerStartStream(),
+ // TODO : c2s doesn't support slow sasl negotiation
+ "empty auth retrieved (correct encoding) , server sends encoded challenge": api.slowSASLNegotiation()
+ }
+ }
+ }
+}
+
+ /*
+ 'a user goes online and offline': {
+ topic : api.createC2S('github.xom') ,
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "client has been auth and online" : {
+ topic : api.jidOnline( 'hpychan', 'github.xom') ,
+ "remove client" : function( params ) {
+ var username = 'hpychan';
+ var server = params.server;
+ for (var streamId in server.clients) {
+ var client = server.clients[streamId];
+ client.emit( 'end', client);
+ //server.on
+ /*JidOffline( client );
+ }
+ assert.equal( server.getJids().length, 0 );
+ // get jids with correct usernames
+ assert.equal( server.getIncomingClients(username).length, 0 );
+ }
+
+ }
+ }
+ },
+ 'a server goes to prebind state with valid jid': {
+ topic : api.createC2S('github.xom') , fastSslowSASLNegotiationASLNegotiation
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "prebind for jid name" : function( params ) {
+ var username = 'hpychan';
+ var domain = 'github.xom';
+ var server = params.server;
+ for (var streamId in server.clients) {
+ var client = server.clients[streamId];
+ var jid = new xmpp.JID( username, domain );
+ var prebind = { 'jid' : jid };
+
+ client.emit( 'preBind', prebind);
+ var pattern = 'generatedId-(0-9)*';
+ var regEx = new RegExp(pattern,'g');
+ assert.isTrue( regEx.test( prebind.resource ));
+ }
+ }
+ }
+ },
+ 'a server goes to prebind state without valid jid': {
+ topic : api.createC2S('github.xom') ,
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "prebind for jid name" : function( params ) {
+ var username = 'hpychan';
+ var domain = 'github.xom';
+ var server = params.server;
+ for (var streamId in server.clients) {
+ var client = server.clients[streamId];
+ var jid = new xmpp.JID( username, domain );
+ var prebind = {};
+
+ client.emit( 'preBind', prebind);
+
+ assert.equal ( prebind.error, 'bad-request' );
+ }
+ }
+ }
+ },
+ 'a server has error after client connected': {
+ topic : api.createC2S('github.xom') ,
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "error happened" : function( params ) {
+ var server = params.server;
+ for (var streamId in server.clients) {
+ var client = server.clients[streamId];
+
+ client.emit( 'error', 'client error');
+ }
+ }
+ }
+ },
+ 'a server has error after client connected and sent a message': {
+ topic : api.createC2S('github.xom') ,
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "error happened after last message sent" : function( params ) {
+ var server = params.server;
+ for (var streamId in server.clients) {
+ var client = server.clients[streamId];
+ client.lastMessage = 'hello world';
+ client.emit( 'error', 'client error');
+ }
+ }
+ }
+ },authe
+ 'a user goes online, server send a message to the user': {
+ topic : api.createC2S('github.xom') ,
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "client has been auth and online" : {
+ topic : api.jidOnline( 'hpychan', 'github.xom') ,
+ "send message to user" : {
+ topic: function( params ) {
+ var username = 'hpychan';
+ var server = params.server;
+
+ server.sendMessage( username, "hello", this.callback );
+ },
+ "callback from server" : function( statusCode, statusDescription) {
+ assert.equal( statusCode, 200 );
+ assert.equal( statusDescription, "OK" );
+ }
+ }
+ }
+ }
+ },
+ 'server send a message to offline user': {
+ topic : api.createC2S('github.xom') ,
+ "send message to user" : {
+ topic: function( params ) {
+ var username = 'hpychan';
+ var server = params.server;
+
+ server.sendMessage( username, "hello", this.callback );
+ },
+ "callback from server" : function( statusCode, statusDescription) {
+ assert.equal( statusCode, 400 );
+ assert.equal( statusDescription, "Not Found" );
+ }
+ }
+ },
+ 'a user goes online, server send a iq message to the user': {
+ topic : api.createC2S('github.xom') ,
+ 'adding new client': {
+ topic : api.createC2SClient( ) ,
+ "client has been auth and online" : {
+ topic : api.jidOnline( 'hpychan', 'github.xom') ,
+ "send iq message to user" : {
+
+ topic: function( params ) {
+ var username = 'hpychan';
+ var server = params.server;
+
+ server.sendIq( username, NS_JABBER_IQ_ROSTER, "query", {}, this.callback );
+ },
+ "callback from server" : function( params, statusCode, statusDescription, details) {
+ assert.equal( statusCode, 400 );
+ assert.equal( statusDescription, "Not Found" );
+ }
+ }
+ }
+ }
+ },
+ 'server send a iq message to offline user': {
+ topic : api.createC2S('github.xom') ,
+ "send iq message to offline user" : {
+ topic: function( params ) {
+ var username = 'hpychan';
+ var server = params.server;
+
+ server.sendIq( username, NS_JABBER_IQ_ROSTER, "query", {}, this.callback );
+ },
+ "callback from server" : function( params, statusCode, statusDescription, details) {
+ assert.equal( statusCode, 400 );
+ assert.equal( statusDescription, "Not Found" );
+ }
+ }
+ }
+ */
+}).export( module, {'error': false} ); // export to suite
View
4 test/test_jid.js
@@ -1,6 +1,6 @@
var vows = require('vows'),
assert = require('assert'),
-xmpp = require('./../lib/xmpp');
+xmpp = require('xmpp');
vows.describe('JID').addBatch({
@@ -97,4 +97,4 @@ vows.describe('JID').addBatch({
}
}
-}).run();
+}).export( module );
Something went wrong with that request. Please try again.