Bug 975144 - Updating RTC identity tests, r=jib

This commit is contained in:
Martin Thomson 2015-02-22 10:57:20 +13:00
parent 19a1f64280
commit 42e602a493
6 changed files with 261 additions and 253 deletions

View File

@ -6,44 +6,33 @@
</head>
<body>
<script class="testbody" type="application/javascript">
"use strict";
var Cu = SpecialPowers.Cu;
var rtcid = Cu.import("resource://gre/modules/media/IdpProxy.jsm");
var IdpProxy = rtcid.IdpProxy;
'use strict';
// here we call the identity provider directly
function getIdentityAssertion(fingerprints, callback) {
var idp;
function handleFailure() {
ok(false, "Failed to talk to IdP");
callback();
}
function handleResponse(response) {
var wrappedResponse = SpecialPowers.wrap(response);
is(wrappedResponse.type, "SUCCESS",
"IdP provided successful response");
var assertion = btoa(JSON.stringify(wrappedResponse.message));
idp.close();
callback(assertion);
}
idp = new IdpProxy("example.com", "idp.html");
idp.start(handleFailure);
idp.send({
type: "SIGN",
message: JSON.stringify({ fingerprint: fingerprints })
}, handleResponse);
function getIdentityAssertion(fpArray) {
var Cu = SpecialPowers.Cu;
var rtcid = Cu.import('resource://gre/modules/media/IdpSandbox.jsm');
var sandbox = new rtcid.IdpSandbox('example.com', 'idp.js');
return sandbox.start()
.then(idp => SpecialPowers.wrap(idp)
.generateAssertion(JSON.stringify({ fingerprint: fpArray }),
'https://example.com'))
.then(assertion => {
assertion = SpecialPowers.wrap(assertion);
var assertionString = btoa(JSON.stringify(assertion));
sandbox.stop();
return assertionString;
});
}
// this takes the real fingerprints and makes more
// This takes a real fingerprint and makes some extra bad ones.
function makeFingerprints(algo, digest) {
var fingerprints = [];
fingerprints.push({ algorithm: algo, digest: digest });
for (var i = 0; i < 3; ++i) {
fingerprints.push({
algorithm: algo,
digest: digest.replace(/:./g, ":" + i.toString(16))
digest: digest.replace(/:./g, ':' + i.toString(16))
});
}
return fingerprints;
@ -53,65 +42,71 @@ var fingerprintRegex = /^a=fingerprint:(\S+) (\S+)/m;
var identityRegex = /^a=identity:(\S+)/m;
function fingerprintSdp(fingerprints) {
return fingerprints.map(fp => "a=fInGeRpRiNt:" + fp.algorithm +
" " + fp.digest + "\n").join("");
return fingerprints.map(fp => 'a=fInGeRpRiNt:' + fp.algorithm +
' ' + fp.digest + '\n').join('');
}
// Firefox only uses a single fingerprint
// that doesn't mean we can't have SDP that describes two
// Firefox only uses a single fingerprint.
// That doesn't mean we have it create SDP that describes two.
// This function synthesizes that SDP and tries to set it.
function testMultipleFingerprints() {
// this one fails setRemoteDescription if the identity is not good
var pcStrict = new mozRTCPeerConnection({ peerIdentity: 'someone@example.com'});
// this one will be manually tweaked to have two fingerprints
var pcDouble = new mozRTCPeerConnection({});
function finished(result, message) {
ok(result, message);
pcStrict.close();
pcDouble.close();
SimpleTest.finish();
}
var offer, match, fingerprints;
navigator.mozGetUserMedia({ audio: true, fake: true }, function(stream) {
ok(stream, "Got fake stream");
pcDouble.addStream(stream);
var fail = msg =>
(e => ok(false, 'error in ' + msg + ': ' +
(e.message ? (e.message + '\n' + e.stack) : e)));
pcDouble.createOffer(function(offer) {
ok(offer, "Got offer");
var fp = offer.sdp.match(fingerprintRegex);
if (!fp) {
finished(false, "No fingerprint in offer SDP");
return;
navigator.mediaDevices.getUserMedia({ audio: true, fake: true })
.then(stream => {
ok(stream, 'Got fake stream');
pcDouble.addStream(stream);
return pcDouble.createOffer();
})
.then(o => {
offer = o;
ok(offer, 'Got offer');
match = offer.sdp.match(fingerprintRegex);
if (!match) {
throw new Error('No fingerprint in offer SDP');
}
fingerprints = makeFingerprints(match[1], match[2]);
return getIdentityAssertion(fingerprints);
})
.then(assertion => {
ok(assertion, 'Should have assertion');
var fingerprints = makeFingerprints(fp[1], fp[2]);
getIdentityAssertion(fingerprints, function(assertion) {
ok(assertion, "Should have assertion");
var sdp = offer.sdp.slice(0, fp.index) +
"a=identity:" + assertion + "\n" +
var sdp = offer.sdp.slice(0, match.index) +
'a=identity:' + assertion + '\n' +
fingerprintSdp(fingerprints.slice(1)) +
offer.sdp.slice(fp.index);
offer.sdp.slice(match.index);
var desc = new mozRTCSessionDescription({ type: "offer", sdp: sdp });
pcStrict.setRemoteDescription(desc, function() {
finished(true, "session description was OK");
}, function(err) {
finished(false, "error setting the session description: " + err);
});
});
}, function(err) {
finished(false, "error creating an offer: " + err);
var desc = new mozRTCSessionDescription({ type: 'offer', sdp: sdp });
return pcStrict.setRemoteDescription(desc);
})
.then(() => {
ok(true, 'Modified fingerprints were accepted');
}, error => {
var e = SpecialPowers.wrap(error);
ok(false, 'error in test: ' +
(e.message ? (e.message + '\n' + e.stack) : e));
})
.then(() => {
pcStrict.close();
pcDouble.close();
SimpleTest.finish();
});
}, function(err) {
finished(false, "error getting stream: " + err);
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set" : [ [ "dom.messageChannel.enabled", true ],
[ "media.peerconnection.identity.enabled", true ] ]
set: [ [ 'dom.messageChannel.enabled', true ],
[ 'media.peerconnection.identity.enabled', true ] ]
}, testMultipleFingerprints);
</script>
</body>

View File

@ -17,7 +17,7 @@ function checkIdentity(assertion, identity) {
// about how the IdP actually works (not good in general, but OK here)
var assertion = JSON.parse(atob(assertion)).assertion;
var user = JSON.parse(assertion).username;
is(user, identity, "id should be '" + identity + "' is '" + user + "'");
is(user, identity, 'id should be "' + identity + '" is "' + user + '"');
}
var test;
@ -26,79 +26,69 @@ function theTest() {
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.chain.removeAfter('PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE');
test.chain.append([
function GET_IDENTITY_ASSERTION_FAILS_WITHOUT_PROVIDER(test) {
return new Promise(resolve => {
test.pcLocal._pc.onidpassertionerror = function(e) {
ok(e, "getIdentityAssertion must fail without provider");
resolve();
};
test.pcLocal._pc.getIdentityAssertion();
});
function GET_IDENTITY_ASSERTION_FAILS_WITHOUT_PROVIDER(t) {
t.pcLocal._pc.onidpassertionerror = e => {
ok(e, 'getIdentityAssertion should fail without provider');
t.next();
};
t.pcLocal._pc.getIdentityAssertion();
},
function GET_IDENTITY_ASSERTION_FIRES_EVENTUALLY_AND_SUBSEQUENTLY(test) {
return new Promise(resolve => {
var fired = 0;
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html');
test.pcLocal._pc.onidentityresult = function(e) {
fired++;
if (fired == 1) {
ok(true, "identityresult fired");
checkIdentity(e.assertion, 'someone@example.com');
} else if (fired == 2) {
ok(true, "identityresult fired 2x");
checkIdentity(e.assertion, 'someone@example.com');
resolve();
}
};
test.pcLocal._pc.onidpassertionerror = function(e) {
ok(false, "error event fired");
resolve();
};
test.pcLocal._pc.getIdentityAssertion();
test.pcLocal._pc.getIdentityAssertion();
});
function GET_IDENTITY_ASSERTION_FIRES_EVENTUALLY_AND_SUBSEQUENTLY(t) {
var fired = 0;
t.setIdentityProvider(t.pcLocal, 'example.com', 'idp.js');
t.pcLocal._pc.onidentityresult = e => {
fired++;
if (fired == 1) {
ok(true, 'identityresult fired once');
checkIdentity(e.assertion, 'someone@example.com');
} else if (fired == 2) {
ok(true, 'identityresult fired twice');
checkIdentity(e.assertion, 'someone@example.com');
t.next();
}
};
t.pcLocal._pc.onidpassertionerror = e => {
ok(false, '2x should not fire error event');
t.next();
};
t.pcLocal._pc.getIdentityAssertion();
t.pcLocal._pc.getIdentityAssertion();
},
function GET_IDENTITY_ASSERTION_FAILS(test) {
return new Promise(resolve => {
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html#error');
test.pcLocal._pc.onidentityresult = function(e) {
ok(false, "Should not get an identity result");
resolve();
};
test.pcLocal._pc.onidpassertionerror = function(err) {
ok(err, "Got error event from getIdentityAssertion");
resolve();
};
test.pcLocal._pc.getIdentityAssertion();
});
function GET_IDENTITY_ASSERTION_FAILS(t) {
t.setIdentityProvider(t.pcLocal, 'example.com', 'idp.js#fail');
t.pcLocal._pc.onidentityresult = e => {
ok(false, '#fail should not get an identity result');
t.next();
};
t.pcLocal._pc.onidpassertionerror = e => {
ok(e, '#fail should fire error event');
t.next();
};
t.pcLocal._pc.getIdentityAssertion();
},
function GET_IDENTITY_ASSERTION_IDP_NOT_READY(test) {
return new Promise(resolve => {
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html#error:ready');
test.pcLocal._pc.onidentityresult = function(e) {
ok(false, "Should not get an identity result");
resolve();
};
test.pcLocal._pc.onidpassertionerror = function(e) {
ok(e, "Got error callback from getIdentityAssertion");
resolve();
};
test.pcLocal._pc.getIdentityAssertion();
});
function GET_IDENTITY_ASSERTION_IDP_NOT_READY(t) {
t.setIdentityProvider(t.pcLocal, 'example.com', 'idp.js#not_ready');
t.pcLocal._pc.onidentityresult = e => {
ok(false, '#not_ready should not get an identity result');
t.next();
};
t.pcLocal._pc.onidpassertionerror = e => {
ok(e, '#not_ready should fire error event');
t.next();
};
t.pcLocal._pc.getIdentityAssertion();
},
function GET_IDENTITY_ASSERTION_WITH_SPECIFIC_NAME(test) {
return new Promise(resolve => {
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html', 'user@example.com');
test.pcLocal._pc.onidentityresult = function(e) {
checkIdentity(e.assertion, 'user@example.com');
resolve();
};
test.pcLocal._pc.onidpassertionerror = function(e) {
ok(false, "Got error callback from getIdentityAssertion");
resolve();
};
test.pcLocal._pc.getIdentityAssertion();
});
function GET_IDENTITY_ASSERTION_WITH_SPECIFIC_NAME(t) {
t.setIdentityProvider(t.pcLocal, 'example.com', 'idp.js', 'user@example.com');
t.pcLocal._pc.onidentityresult = e => {
checkIdentity(e.assertion, 'user@example.com');
t.next();
};
t.pcLocal._pc.onidpassertionerror = e => {
ok(false, 'user@ should not fire error event');
t.next();
};
t.pcLocal._pc.getIdentityAssertion();
}
]);
test.run();

View File

@ -8,119 +8,122 @@
<script class="testbody" type="application/javascript">
"use strict";
var Cu = SpecialPowers.Cu;
var rtcid = Cu.import("resource://gre/modules/media/IdpProxy.jsm");
var IdpProxy = rtcid.IdpProxy;
var request = {
type: "SIGN",
message: "foo"
};
var rtcid = Cu.import("resource://gre/modules/media/IdpSandbox.jsm");
var IdpSandbox = rtcid.IdpSandbox;
var dummyPayload = JSON.stringify({
this: 'is',
a: ['stu', 6],
obj: null
});
function test_domain_sandbox(done) {
function test_domain_sandbox() {
var diabolical = {
toString : function() {
return "example.com/path";
return 'example.com/path';
}
};
var domains = [ "ex/foo", "user@ex", "user:pass@ex", "ex#foo", "ex?foo",
"", 12, null, diabolical, true ];
var domains = [ 'ex/foo', 'user@ex', 'user:pass@ex', 'ex#foo', 'ex?foo',
'', 12, null, diabolical, true ];
domains.forEach(function(domain) {
try {
var idp = new IdpProxy(domain);
ok(false, "IdpProxy didn't catch bad domain: " + domain);
var idp = new IdpSandbox(domain);
ok(false, 'IdpSandbox allowed a bad domain: ' + domain);
} catch (e) {
var str = (typeof domain === "string") ? domain : typeof domain;
ok(true, "Evil domain '" + str + "' raises exception");
var str = (typeof domain === 'string') ? domain : typeof domain;
ok(true, 'Evil domain "' + str + '" raises exception');
}
});
done();
}
function test_protocol_sandbox(done) {
var protos = [ "../evil/proto", "..%2Fevil%2Fproto",
"\\evil", "%5cevil", 12, true, {} ];
function test_protocol_sandbox() {
var protos = [ '../evil/proto', '..%2Fevil%2Fproto',
'\\evil', '%5cevil', 12, true, {} ];
protos.forEach(function(proto) {
try {
var idp = new IdpProxy("example.com", proto);
ok(false, "IdpProxy didn't catch bad protocol: " + proto);
var idp = new IdpSandbox('example.com', proto);
ok(false, 'IdpSandbox allowed a bad protocol: ' + proto);
} catch (e) {
var str = (typeof proto === "string") ? proto : typeof proto;
ok(true, "Evil protocol '" + proto + "' raises exception");
var str = (typeof proto === 'string') ? proto : typeof proto;
ok(true, 'Evil protocol "' + proto + '" raises exception');
}
});
done();
}
function handleFailure(done) {
function failure(error) {
ok(false, "IdP error" + error);
done();
}
return failure;
function makeSandbox(hash) {
var sandbox = new IdpSandbox('example.com',
'idp.js' + (hash ? ('#' + hash) : ''));
return sandbox.start().then(idp => SpecialPowers.wrap(idp));
}
function test_success_response(done) {
var idp;
var failure = handleFailure(done);
function handleResponse(response) {
is(SpecialPowers.wrap(response).type, "SUCCESS", "IdP responds with SUCCESS");
idp.close();
done();
}
idp = new IdpProxy("example.com", "idp.html");
idp.start(failure);
idp.send(request, handleResponse);
function test_generate_assertion() {
return makeSandbox()
.then(idp => idp.generateAssertion(dummyPayload,
'https://example.net'))
.then(response => {
response = SpecialPowers.wrap(response);
is(response.idp.domain, 'example.com', 'domain is correct');
is(response.idp.protocol, 'idp.js', 'protocol is correct');
ok(typeof response.assertion === 'string', 'assertion is present');
});
}
function test_error_response(done) {
var idp;
var failure = handleFailure(done);
// test that the test IdP can eat its own dogfood; which is the only way to test
// validateAssertion, since that consumes the output of generateAssertion (in
// theory, generateAssertion could identify a different IdP domain).
function handleResponse(response) {
is(SpecialPowers.wrap(response).type, "ERROR", "IdP should produce ERROR");
idp.close();
done();
}
idp = new IdpProxy("example.com", "idp.html#error");
idp.start(failure);
idp.send(request, handleResponse);
function test_validate_assertion() {
return makeSandbox()
.then(idp => idp.generateAssertion(dummyPayload,
'https://example.net', 'user'))
.then(assertion => {
var wrapped = SpecialPowers.wrap(assertion);
return makeSandbox()
.then(idp => idp.validateAssertion(wrapped.assertion,
'https://example.net'));
}).then(response => {
response = SpecialPowers.wrap(response);
is(response.identity, 'user@example.com');
is(response.contents, dummyPayload);
});
}
function test_delayed_response(done) {
var idp;
var failure = handleFailure(done);
function handleResponse(response) {
is(SpecialPowers.wrap(response).type, "SUCCESS",
"IdP should handle delayed response");
idp.close();
done();
}
idp = new IdpProxy("example.com", "idp.html#delay100");
idp.start(failure);
idp.send(request, handleResponse);
// We don't want to test the #bad or the #hang instructions,
// errors of the sort those generate aren't handled by the sandbox code.
function test_assertion_failure(reason) {
return () => {
return makeSandbox(idpName(reason))
.then(idp => idp.generateAssertion('hello', 'example.net'))
.then(r => ok(false, 'should not succeed on ' + reason),
e => ok(true, 'failed correctly on ' + reason));
};
}
var TESTS = [ test_domain_sandbox, test_protocol_sandbox,
test_success_response, test_error_response,
test_delayed_response ];
function run_all_tests() {
[
test_domain_sandbox,
test_protocol_sandbox,
test_generate_assertion,
test_validate_assertion,
function run_next_test() {
if (TESTS.length) {
var test = TESTS.shift();
test(run_next_test);
} else {
SimpleTest.finish();
}
// fail of the IdP fails
test_assertion_failure('fail'),
// fail if the IdP throws
test_assertion_failure('throw'),
// fail if the IdP is not ready
test_assertion_failure('not_ready')
].reduce((p, test) => {
return p.then(test)
.catch(e => ok(false, test.name + ' failed: ' +
SpecialPowers.wrap(e).message + '\n' +
SpecialPowers.wrap(e).stack));
}, Promise.resolve())
.then(() => SimpleTest.finish());
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
"set" : [ [ "dom.messageChannel.enabled", true ] ]
}, run_next_test);
set: [ [ 'dom.messageChannel.enabled', true ] ]
}, run_all_tests);
</script>
</body>
</html>

View File

@ -35,8 +35,8 @@ function theTest() {
fake: true,
peerIdentity: id1
}]);
test.setIdentityProvider(test.pcLocal, 'test1.example.com', 'idp.html');
test.setIdentityProvider(test.pcRemote, 'test2.example.com', 'idp.html');
test.setIdentityProvider(test.pcLocal, 'test1.example.com', 'idp.js');
test.setIdentityProvider(test.pcRemote, 'test2.example.com', 'idp.js');
test.chain.append([
function PEER_IDENTITY_IS_SET_CORRECTLY(test) {

View File

@ -17,8 +17,8 @@ var test;
function theTest() {
test = new PeerConnectionTest();
test.setMediaConstraints([{audio: true}], [{audio: true}]);
test.setIdentityProvider(test.pcLocal, "test1.example.com", "idp.html", "someone");
test.setIdentityProvider(test.pcRemote, "test2.example.com", "idp.html", "someone");
test.setIdentityProvider(test.pcLocal, "test1.example.com", "idp.js", "someone");
test.setIdentityProvider(test.pcRemote, "test2.example.com", "idp.js", "someone");
var localEvents = trapIdentityEvents(test.pcLocal._pc);
var remoteEvents = trapIdentityEvents(test.pcRemote._pc);

View File

@ -8,6 +8,7 @@
<body>
<pre id="test">
<script type="application/javascript">
'use strict';
createHTML({
title: "Identity Provider returning errors is handled correctly",
bug: "942367"
@ -16,43 +17,62 @@
runNetworkTest(function () {
var test = new PeerConnectionTest();
test.setMediaConstraints([{audio: true}], [{audio: true}]);
// first example generates an error
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html#error', 'nobody');
// second generates a bad assertion; which fails to validate
test.setIdentityProvider(test.pcRemote, 'example.com', 'idp.html#bad', 'nobody');
// first example generates a bad assertion that can be detected immediately
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.js#bad-assert', 'nobody');
// second generates a bad assertion, but that only fails to validate
test.setIdentityProvider(test.pcRemote, 'example.com', 'idp.js#bad-validate', 'nobody');
var localEvents = trapIdentityEvents(test.pcLocal._pc);
var remoteEvents = trapIdentityEvents(test.pcRemote._pc);
// if we get a peeridentity event, something is wrong
function assertNoIdentity() {
ok(!localEvents.peeridentity,
'no peer identity event for local peer');
ok(!remoteEvents.peeridentity,
'no peer identity event for remote peer');
throw new Error('got peeridentity, aborting test');
}
test.pcLocal._pc.onpeeridentity = assertNoIdentity;
test.pcRemote._pc.onpeeridentity = assertNoIdentity;
test.chain.append([
function CHECK_IDENTITY_EVENTS(test) {
function checkEvents() {
ok(localEvents.idpassertionerror, 'local assertion generation should fail (idpassertionerror)');
is(localEvents.idpassertionerror.idp, 'example.com', 'event IdP is correct');
is(localEvents.idpassertionerror.protocol, 'idp.html#error', 'event IdP protocol is #error');
ok(!remoteEvents.idpassertionerror, 'remote assertion generation should succeed (idpassertionerror)');
ok(!localEvents.identityresult, 'local assertion generation should fail (identityresult)');
ok(remoteEvents.identityresult, 'remote assertion generation should succeed (identityresult)');
ok(!localEvents.peeridentity, 'no peer identity event for local peer');
ok(!remoteEvents.peeridentity, 'no peer identity event for remote peer');
ok(localEvents.idpvalidationerror, 'local fails to validate');
is(localEvents.idpvalidationerror.idp, 'example.com', 'event IdP is correct');
is(localEvents.idpvalidationerror.protocol, 'idp.html#bad', 'event IdP protocol is #bad');
ok(!remoteEvents.idpvalidationerror, 'remote doesn\'t even see an assertion');
}
// we actually have to wait on this because IdP validation happens asynchronously
if (localEvents.idpvalidationerror) {
checkEvents();
return Promise.resolve();
}
// have to let the other event handler have a chance to record success
// before we run the checks that rely on that recording
function CHECK_IDENTITY_EVENTS(t) {
return new Promise(resolve => {
test.pcLocal._pc.onidpvalidationerror = resolve;
}).then(checkEvents);
var checkEvents = () => {
ok(localEvents.idpassertionerror,
'local assertion generation should fail (idpassertionerror)');
is(localEvents.idpassertionerror.idp,
'example.com', 'event IdP is correct');
is(localEvents.idpassertionerror.protocol,
'idp.js#bad-assert', 'event IdP protocol is #bad-assert');
ok(!remoteEvents.idpassertionerror,
'remote assertion generation should succeed (idpassertionerror)');
ok(!localEvents.identityresult,
'local assertion generation should fail (identityresult)');
ok(remoteEvents.identityresult,
'remote assertion generation should succeed (identityresult)');
ok(localEvents.idpvalidationerror, 'local fails to validate');
is(localEvents.idpvalidationerror.idp, 'example.com',
'event IdP is correct');
is(localEvents.idpvalidationerror.protocol, 'idp.js#bad-validate',
'event IdP protocol is #bad-validate');
ok(!remoteEvents.idpvalidationerror,
'remote doesn\'t even see an assertion');
resolve();
};
// we actually have to wait on this because IdP validation is asynchronous
if (localEvents.idpvalidationerror) {
checkEvents();
} else {
// have to let the other event handlers have a chance to record success
// before we run the checks that rely on that recording
t.pcLocal._pc.onidpvalidationerror = checkEvents;
}
});
},
function PEER_IDENTITY_IS_EMPTY(test) {