Commit 39f1fc4e authored by JC Brand's avatar JC Brand

Show fingerprints in the user details modal

updates #497
parent ce447e40
...@@ -2346,7 +2346,7 @@ ...@@ -2346,7 +2346,7 @@
--primary: #387592; --primary: #387592;
--secondary: #6c757d; --secondary: #6c757d;
--success: #3AA569; --success: #3AA569;
--info: #17a2b8; --info: #3AA569;
--warning: #ffc107; --warning: #ffc107;
--danger: #E77051; --danger: #E77051;
--light: #f8f9fa; --light: #f8f9fa;
...@@ -3594,24 +3594,24 @@ ...@@ -3594,24 +3594,24 @@
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); } box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-info { #conversejs .btn-info {
color: #fff; color: #fff;
background-color: #17a2b8; background-color: #3AA569;
border-color: #17a2b8; } border-color: #3AA569; }
#conversejs .btn-info:hover { #conversejs .btn-info:hover {
color: #fff; color: #fff;
background-color: #138496; background-color: #308957;
border-color: #117a8b; } border-color: #2d7f51; }
#conversejs .btn-info:focus, #conversejs .btn-info.focus { #conversejs .btn-info:focus, #conversejs .btn-info.focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-info.disabled, #conversejs .btn-info:disabled { #conversejs .btn-info.disabled, #conversejs .btn-info:disabled {
color: #fff; color: #fff;
background-color: #17a2b8; background-color: #3AA569;
border-color: #17a2b8; } border-color: #3AA569; }
#conversejs .btn-info:not(:disabled):not(.disabled):active, #conversejs .btn-info:not(:disabled):not(.disabled).active, .show > #conversejs .btn-info.dropdown-toggle { #conversejs .btn-info:not(:disabled):not(.disabled):active, #conversejs .btn-info:not(:disabled):not(.disabled).active, .show > #conversejs .btn-info.dropdown-toggle {
color: #fff; color: #fff;
background-color: #117a8b; background-color: #2d7f51;
border-color: #10707f; } border-color: #29764b; }
#conversejs .btn-info:not(:disabled):not(.disabled):active:focus, #conversejs .btn-info:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-info.dropdown-toggle:focus { #conversejs .btn-info:not(:disabled):not(.disabled):active:focus, #conversejs .btn-info:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-info.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-warning { #conversejs .btn-warning {
color: #212529; color: #212529;
background-color: #ffc107; background-color: #ffc107;
...@@ -3753,25 +3753,25 @@ ...@@ -3753,25 +3753,25 @@
#conversejs .btn-outline-success:not(:disabled):not(.disabled):active:focus, #conversejs .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-outline-success.dropdown-toggle:focus { #conversejs .btn-outline-success:not(:disabled):not(.disabled):active:focus, #conversejs .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-outline-success.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); } box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-outline-info { #conversejs .btn-outline-info {
color: #17a2b8; color: #3AA569;
background-color: transparent; background-color: transparent;
background-image: none; background-image: none;
border-color: #17a2b8; } border-color: #3AA569; }
#conversejs .btn-outline-info:hover { #conversejs .btn-outline-info:hover {
color: #fff; color: #fff;
background-color: #17a2b8; background-color: #3AA569;
border-color: #17a2b8; } border-color: #3AA569; }
#conversejs .btn-outline-info:focus, #conversejs .btn-outline-info.focus { #conversejs .btn-outline-info:focus, #conversejs .btn-outline-info.focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-outline-info.disabled, #conversejs .btn-outline-info:disabled { #conversejs .btn-outline-info.disabled, #conversejs .btn-outline-info:disabled {
color: #17a2b8; color: #3AA569;
background-color: transparent; } background-color: transparent; }
#conversejs .btn-outline-info:not(:disabled):not(.disabled):active, #conversejs .btn-outline-info:not(:disabled):not(.disabled).active, .show > #conversejs .btn-outline-info.dropdown-toggle { #conversejs .btn-outline-info:not(:disabled):not(.disabled):active, #conversejs .btn-outline-info:not(:disabled):not(.disabled).active, .show > #conversejs .btn-outline-info.dropdown-toggle {
color: #fff; color: #fff;
background-color: #17a2b8; background-color: #3AA569;
border-color: #17a2b8; } border-color: #3AA569; }
#conversejs .btn-outline-info:not(:disabled):not(.disabled):active:focus, #conversejs .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-outline-info.dropdown-toggle:focus { #conversejs .btn-outline-info:not(:disabled):not(.disabled):active:focus, #conversejs .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > #conversejs .btn-outline-info.dropdown-toggle:focus {
box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } box-shadow: 0 0 0 0.2rem rgba(58, 165, 105, 0.5); }
#conversejs .btn-outline-warning { #conversejs .btn-outline-warning {
color: #ffc107; color: #ffc107;
background-color: transparent; background-color: transparent;
...@@ -4582,11 +4582,11 @@ ...@@ -4582,11 +4582,11 @@
background-color: #2d7f51; } background-color: #2d7f51; }
#conversejs .badge-info { #conversejs .badge-info {
color: #fff; color: #fff;
background-color: #17a2b8; } background-color: #3AA569; }
#conversejs .badge-info[href]:hover, #conversejs .badge-info[href]:focus { #conversejs .badge-info[href]:hover, #conversejs .badge-info[href]:focus {
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
background-color: #117a8b; } background-color: #2d7f51; }
#conversejs .badge-warning { #conversejs .badge-warning {
color: #212529; color: #212529;
background-color: #ffc107; } background-color: #ffc107; }
...@@ -4658,13 +4658,13 @@ ...@@ -4658,13 +4658,13 @@
#conversejs .alert-success .alert-link { #conversejs .alert-success .alert-link {
color: #11301f; } color: #11301f; }
#conversejs .alert-info { #conversejs .alert-info {
color: #0c5460; color: #1e5637;
background-color: #d1ecf1; background-color: #d8ede1;
border-color: #bee5eb; } border-color: #c8e6d5; }
#conversejs .alert-info hr { #conversejs .alert-info hr {
border-top-color: #abdde5; } border-top-color: #b6dec8; }
#conversejs .alert-info .alert-link { #conversejs .alert-info .alert-link {
color: #062c33; } color: #11301f; }
#conversejs .alert-warning { #conversejs .alert-warning {
color: #856404; color: #856404;
background-color: #fff3cd; background-color: #fff3cd;
...@@ -4782,15 +4782,15 @@ ...@@ -4782,15 +4782,15 @@
background-color: #1e5637; background-color: #1e5637;
border-color: #1e5637; } border-color: #1e5637; }
#conversejs .list-group-item-info { #conversejs .list-group-item-info {
color: #0c5460; color: #1e5637;
background-color: #bee5eb; } background-color: #c8e6d5; }
#conversejs .list-group-item-info.list-group-item-action:hover, #conversejs .list-group-item-info.list-group-item-action:focus { #conversejs .list-group-item-info.list-group-item-action:hover, #conversejs .list-group-item-info.list-group-item-action:focus {
color: #0c5460; color: #1e5637;
background-color: #abdde5; } background-color: #b6dec8; }
#conversejs .list-group-item-info.list-group-item-action.active { #conversejs .list-group-item-info.list-group-item-action.active {
color: #fff; color: #fff;
background-color: #0c5460; background-color: #1e5637;
border-color: #0c5460; } border-color: #1e5637; }
#conversejs .list-group-item-warning { #conversejs .list-group-item-warning {
color: #856404; color: #856404;
background-color: #ffeeba; } background-color: #ffeeba; }
...@@ -5179,11 +5179,11 @@ ...@@ -5179,11 +5179,11 @@
#conversejs button.bg-success:focus { #conversejs button.bg-success:focus {
background-color: #2d7f51 !important; } background-color: #2d7f51 !important; }
#conversejs .bg-info { #conversejs .bg-info {
background-color: #17a2b8 !important; } background-color: #3AA569 !important; }
#conversejs a.bg-info:hover, #conversejs a.bg-info:focus, #conversejs a.bg-info:hover, #conversejs a.bg-info:focus,
#conversejs button.bg-info:hover, #conversejs button.bg-info:hover,
#conversejs button.bg-info:focus { #conversejs button.bg-info:focus {
background-color: #117a8b !important; } background-color: #2d7f51 !important; }
#conversejs .bg-warning { #conversejs .bg-warning {
background-color: #ffc107 !important; } background-color: #ffc107 !important; }
#conversejs a.bg-warning:hover, #conversejs a.bg-warning:focus, #conversejs a.bg-warning:hover, #conversejs a.bg-warning:focus,
...@@ -5239,7 +5239,7 @@ ...@@ -5239,7 +5239,7 @@
#conversejs .border-success { #conversejs .border-success {
border-color: #3AA569 !important; } border-color: #3AA569 !important; }
#conversejs .border-info { #conversejs .border-info {
border-color: #17a2b8 !important; } border-color: #3AA569 !important; }
#conversejs .border-warning { #conversejs .border-warning {
border-color: #ffc107 !important; } border-color: #ffc107 !important; }
#conversejs .border-danger { #conversejs .border-danger {
...@@ -6792,9 +6792,9 @@ ...@@ -6792,9 +6792,9 @@
#conversejs a.text-success:hover, #conversejs a.text-success:focus { #conversejs a.text-success:hover, #conversejs a.text-success:focus {
color: #2d7f51 !important; } color: #2d7f51 !important; }
#conversejs .text-info { #conversejs .text-info {
color: #17a2b8 !important; } color: #3AA569 !important; }
#conversejs a.text-info:hover, #conversejs a.text-info:focus { #conversejs a.text-info:hover, #conversejs a.text-info:focus {
color: #117a8b !important; } color: #2d7f51 !important; }
#conversejs .text-warning { #conversejs .text-warning {
color: #ffc107 !important; } color: #ffc107 !important; }
#conversejs a.text-warning:hover, #conversejs a.text-warning:focus { #conversejs a.text-warning:hover, #conversejs a.text-warning:focus {
...@@ -7213,6 +7213,9 @@ body.reset { ...@@ -7213,6 +7213,9 @@ body.reset {
@media screen and (max-height: 450px) { @media screen and (max-height: 450px) {
#conversejs { #conversejs {
left: 0; } } left: 0; } }
#conversejs .btn--small {
font-size: 80%;
font-weight: normal; }
#conversejs form .form-group { #conversejs form .form-group {
margin-bottom: 2em; } margin-bottom: 2em; }
#conversejs form .form-check-label { #conversejs form .form-check-label {
...@@ -7291,6 +7294,10 @@ body.reset { ...@@ -7291,6 +7294,10 @@ body.reset {
#conversejs #user-profile-modal label { #conversejs #user-profile-modal label {
font-weight: bold; } font-weight: bold; }
#conversejs .fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%; }
#conversejs .chatbox-navback { #conversejs .chatbox-navback {
display: none; } display: none; }
......
This diff is collapsed.
...@@ -8,7 +8,7 @@ msgstr "" ...@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: Converse.js 0.4\n" "Project-Id-Version: Converse.js 0.4\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-07-22 11:17+0200\n" "POT-Creation-Date: 2018-07-22 11:17+0200\n"
"PO-Revision-Date: 2018-07-22 12:12+0200\n" "PO-Revision-Date: 2018-07-22 15:37+0200\n"
"Last-Translator: JC Brand <jc@opkode.com>\n" "Last-Translator: JC Brand <jc@opkode.com>\n"
"Language-Team: Afrikaans <https://hosted.weblate.org/projects/conversejs/" "Language-Team: Afrikaans <https://hosted.weblate.org/projects/conversejs/"
"translations/af/>\n" "translations/af/>\n"
......
#conversejs { #conversejs {
.btn--small {
font-size: 80%;
font-weight: normal;
}
form { form {
.form-group { .form-group {
margin-bottom: 2em; margin-bottom: 2em;
......
...@@ -4,4 +4,9 @@ ...@@ -4,4 +4,9 @@
font-weight: bold; font-weight: bold;
} }
} }
.fingerprint-trust {
display: flex;
justify-content: space-between;
font-size: 95%;
}
} }
...@@ -34,6 +34,8 @@ $green: #3AA569; ...@@ -34,6 +34,8 @@ $green: #3AA569;
$dark-green: #1E9652; $dark-green: #1E9652;
$darkest-green: #0E763B; $darkest-green: #0E763B;
$info: $green !default;
$lightest-green: #E7FBF0; $lightest-green: #E7FBF0;
$light-green: #5CBC86; $light-green: #5CBC86;
$green: #3AA569; $green: #3AA569;
......
...@@ -396,6 +396,15 @@ ...@@ -396,6 +396,15 @@
`</publish>`+ `</publish>`+
`</pubsub>`+ `</pubsub>`+
`</iq>`) `</iq>`)
spyOn(_converse, 'emit');
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.emit).toHaveBeenCalledWith('OMEMOInitialized');
done(); done();
}); });
})); }));
...@@ -494,7 +503,6 @@ ...@@ -494,7 +503,6 @@
'type': 'result'}); 'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => { return test_utils.waitUntil(() => {
return _.filter( return _.filter(
_converse.connection.IQ_stanzas, _converse.connection.IQ_stanzas,
...@@ -565,6 +573,101 @@ ...@@ -565,6 +573,101 @@
done(); done();
}).catch(_.partial(console.error, _)); }).catch(_.partial(console.error, _));
})); }));
it("shows OMEMO device fingerprints in the user details modal",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
let iq_stanza;
test_utils.createContacts(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid);
// We simply emit, to avoid doing all the setup work
_converse.emit('OMEMOInitialized');
const view = _converse.chatboxviews.get(contact_jid);
const show_modal_button = view.el.querySelector('.show-user-details-modal');
show_modal_button.click();
const modal = view.user_details_modal;
test_utils.waitUntil(() => u.isVisible(modal.el), 1000).then(() => {
return test_utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas,
(iq) => {
const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
if (node) { iq_stanza = iq.nodeTree; }
return node;
}).length;});
}).then(() => {
iq_stanza;
expect(iq_stanza.outerHTML).toBe(
`<iq type="get" from="dummy@localhost" to="max.frankfurter@localhost" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
`<query xmlns="http://jabber.org/protocol/disco#items" node="eu.siacs.conversations.axolotl.devicelist"/>`+
`</iq>`);
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('query', {
'xmlns': 'http://jabber.org/protocol/disco#items',
'node': 'eu.siacs.conversations.axolotl.devicelist'
}).c('device', {'id': '555'}).up()
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => u.isVisible(modal.el), 1000).then(function () {
return test_utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas,
(iq) => {
const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] items[node="eu.siacs.conversations.axolotl.bundles:555"]');
if (node) { iq_stanza = iq.nodeTree; }
return node;
}).length;});
});
}).then(() => {
expect(iq_stanza.outerHTML).toBe(
`<iq type="get" from="dummy@localhost" to="max.frankfurter@localhost" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+
`</pubsub>`+
`</iq>`);
const stanza = $iq({
'from': contact_jid,
'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
.c('signedPreKeySignature').t(btoa('2222')).up()
.c('identityKey').t(btoa('3333')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const view = _converse.chatboxviews.get(contact_jid);
const modal = view.user_details_modal;
return test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
}).then(() => {
const view = _converse.chatboxviews.get(contact_jid);
const modal = view.user_details_modal;
expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
const el = modal.el.querySelector('.fingerprints .fingerprint');
expect(el.textContent).toBe('f56d6351aa71cff0debea014d13525e42036187a');
done();
});
}));
}); });
describe("A chatbox with an active OMEMO session", function() { describe("A chatbox with an active OMEMO session", function() {
......
...@@ -389,7 +389,6 @@ ...@@ -389,7 +389,6 @@
function (done, _converse) { function (done, _converse) {
_converse.roster_groups = true; _converse.roster_groups = true;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render(); _converse.rosterview.render();
test_utils.openControlBox(); test_utils.openControlBox();
...@@ -430,7 +429,6 @@ ...@@ -430,7 +429,6 @@
function (done, _converse) { function (done, _converse) {
_converse.roster_groups = true; _converse.roster_groups = true;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
_converse.rosterview.render(); _converse.rosterview.render();
...@@ -477,7 +475,6 @@ ...@@ -477,7 +475,6 @@
_converse.roster_groups = true; _converse.roster_groups = true;
var groups = ['colleagues', 'friends']; var groups = ['colleagues', 'friends'];
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox(); test_utils.openControlBox();
_converse.rosterview.render(); _converse.rosterview.render();
...@@ -576,7 +573,6 @@ ...@@ -576,7 +573,6 @@
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
test_utils.openControlBox(); test_utils.openControlBox();
_converse.roster.create({ _converse.roster.create({
...@@ -726,7 +722,6 @@ ...@@ -726,7 +722,6 @@
var i, t; var i, t;
test_utils.openControlBox(); test_utils.openControlBox();
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
for (i=0; i<mock.pend_names.length; i++) { for (i=0; i<mock.pend_names.length; i++) {
_converse.roster.create({ _converse.roster.create({
...@@ -908,7 +903,6 @@ ...@@ -908,7 +903,6 @@
test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group li').length, 700) test_utils.waitUntil(() => $(_converse.rosterview.el).find('.roster-group li').length, 700)
.then(function () { .then(function () {
var jid, t; var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el); var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) { for (var i=0; i<mock.cur_names.length; i++) {
...@@ -935,7 +929,6 @@ ...@@ -935,7 +929,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length; return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () { }, 700).then(function () {
var jid, t; var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el); var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) { for (var i=0; i<mock.cur_names.length; i++) {
...@@ -963,7 +956,6 @@ ...@@ -963,7 +956,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length; return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () { }, 700).then(function () {
var jid, t; var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el); var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) { for (var i=0; i<mock.cur_names.length; i++) {
...@@ -991,7 +983,6 @@ ...@@ -991,7 +983,6 @@
return $(_converse.rosterview.el).find('.roster-group li').length; return $(_converse.rosterview.el).find('.roster-group li').length;
}, 700).then(function () { }, 700).then(function () {
var jid, t; var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el); var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) { for (var i=0; i<mock.cur_names.length; i++) {
...@@ -1020,7 +1011,6 @@ ...@@ -1020,7 +1011,6 @@
}, 500) }, 500)
.then(function () { .then(function () {
var jid, t; var jid, t;
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
var $roster = $(_converse.rosterview.el); var $roster = $(_converse.rosterview.el);
for (var i=0; i<mock.cur_names.length; i++) { for (var i=0; i<mock.cur_names.length; i++) {
...@@ -1151,7 +1141,6 @@ ...@@ -1151,7 +1141,6 @@
names.push($(item).text().replace(/^\s+|\s+$/g, '')); names.push($(item).text().replace(/^\s+|\s+$/g, ''));
} }
}; };
spyOn(_converse, 'emit');
spyOn(_converse.rosterview, 'update').and.callThrough(); spyOn(_converse.rosterview, 'update').and.callThrough();
spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough(); spyOn(_converse.controlboxtoggle, 'showControlBox').and.callThrough();
for (i=0; i<mock.req_names.length; i++) { for (i=0; i<mock.req_names.length; i++) {
......
...@@ -245,26 +245,41 @@ ...@@ -245,26 +245,41 @@
initialize () { initialize () {
_converse.BootstrapModal.prototype.initialize.apply(this, arguments); _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
this.model.on('contactAdded', this.registerContactEventHandlers, this); this.model.on('contactAdded', this.registerContactEventHandlers, this);
this.model.on('change', this.render, this);
// XXX: Leaky abstraction from converse-omemo
// In part, we're hampered by the fact that we can't
// have sub-views inside a VDOMView.
// If we did, we could put the OMEMO part of this modal
// inside another view and have it render as a sub-view.
// However, for this we'd need some kind of registry and
// way to look up sub-views by tag from the template (which
// I assume is what for example vue.js does).
this.has_omemo = _converse.pluggable.plugins['converse-omemo'].enabled();
if (this.has_omemo) {
const jid = this.model.get('jid');
this.devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid});
this.devicelist.devices.on('change:fingerprint', this.render, this);
} else {
this.devicelist = {};
}
this.registerContactEventHandlers(); this.registerContactEventHandlers();
_converse.emit('userDetailsModalInitialized', this.model);
}, },
toHTML () { toHTML () {
return tpl_user_details_modal(_.extend( return tpl_user_details_modal(_.extend(
this.model.toJSON(), this.model.toJSON(),
this.model.vcard.toJSON(), { this.model.vcard.toJSON(), {
'_': _,
'__': __,
'has_omemo': this.has_omemo,
'devicelist': this.devicelist,
'allow_contact_removal': _converse.allow_contact_removal, 'allow_contact_removal': _converse.allow_contact_removal,
'alt_profile_image': __("The User's Profile Image"), 'alt_profile_image': __("The User's Profile Image"),
'display_name': this.model.getDisplayName(), 'display_name': this.model.getDisplayName(),
'is_roster_contact': !_.isUndefined(this.model.contact), 'is_roster_contact': !_.isUndefined(this.model.contact)
'label_close': __('Close'),
'label_email': __('Email'),
'label_fullname': __('Full Name'),
'label_jid': __('Jabber ID'),
'label_nickname': __('Nickname'),
'label_remove': __('Remove as contact'),
'label_refresh': __('Refresh'),
'label_role': __('Role'),
'label_url': __('URL')
})); }));
}, },
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
} }
}); });
return { return {
'identity_key': parseInt(bundle_el.querySelector('identityKey').textContent, 10), 'identity_key': bundle_el.querySelector('identityKey').textContent,
'signed_prekey': { 'signed_prekey': {
'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10), 'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10),
'public_key': signed_prekey_public_el.textContent, 'public_key': signed_prekey_public_el.textContent,
...@@ -77,7 +77,6 @@ ...@@ -77,7 +77,6 @@
this.buildSessions(devices) this.buildSessions(devices)
.then(() => resolve(devices)) .then(() => resolve(devices))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
...@@ -194,6 +193,26 @@ ...@@ -194,6 +193,26 @@
_converse.NUM_PREKEYS = 100; // Set here so that tests can override _converse.NUM_PREKEYS = 100; // Set here so that tests can override
function generateFingerprint (device) {
return new Promise((resolve, reject) => {
device.getBundle().then((bundle) => {
// TODO: only generate fingerprints when necessary
crypto.subtle.digest('SHA-1', u.base64ToArrayBuffer(bundle['identity_key']))
.then((fp) => {
bundle['fingerprint'] = u.arrayBufferToHex(fp);
device.save('bundle', bundle);
resolve();
}).catch(reject);
});
});
}
_converse.getFingerprintsForContact = function (jid) {
return new Promise((resolve, reject) => {
_converse.getDevicesForContact(jid)
.then((devices) => Promise.all(devices.map(d => generateFingerprint(d))).then(resolve).catch(reject));
});
}
_converse.getDevicesForContact = function (jid) { _converse.getDevicesForContact = function (jid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -405,14 +424,15 @@ ...@@ -405,14 +424,15 @@
'from': _converse.bare_jid, 'from': _converse.bare_jid,
'to': this.get('jid') 'to': this.get('jid')
}).c('pubsub', {'xmlns': Strophe.NS.PUBSUB}) }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
.c('items', {'xmlns': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}`}); .c('items', {'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}`});
_converse.connection.sendIQ( _converse.connection.sendIQ(
stanza, stanza,
(iq) => { (iq) => {
const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, stanza).pop(); const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(),
const bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(); bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(),
this.save(parseBundle(bundle_el)); bundle = parseBundle(bundle_el);
resolve(); this.save('bundle', bundle);
resolve(bundle);
}, },
reject, reject,
_converse.IQ_TIMEOUT _converse.IQ_TIMEOUT
...@@ -479,7 +499,7 @@ ...@@ -479,7 +499,7 @@
(iq) => { (iq) => {
_.forEach( _.forEach(
iq.querySelectorAll('device'), iq.querySelectorAll('device'),
(dev) => this.devices.create({'id': dev.getAttribute('id')}) (dev) => this.devices.create({'id': dev.getAttribute('id'), 'jid': this.get('jid')})
); );
resolve(); resolve();
}, },
...@@ -493,7 +513,7 @@ ...@@ -493,7 +513,7 @@
* server. * server.
* https://xmpp.org/extensions/xep-0384.html#usecases-announcing * https://xmpp.org/extensions/xep-0384.html#usecases-announcing
*/ */
this.devices.create({'id': device_id}); this.devices.create({'id': device_id, 'jid': this.get('jid')});
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const stanza = $iq({ const stanza = $iq({
'from': _converse.bare_jid, 'from': _converse.bare_jid,
...@@ -589,7 +609,7 @@ ...@@ -589,7 +609,7 @@
jid = stanza.getAttribute('from'), jid = stanza.getAttribute('from'),
bundle_el = sizzle(`item > bundle`, items_el).pop(), bundle_el = sizzle(`item > bundle`, items_el).pop(),
devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}), devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}),
device = devicelist.devices.get(device_id) || devicelist.devices.create({'id': device_id}); device = devicelist.devices.get(device_id) || devicelist.devices.create({'id': device_id, 'jid': jid});
device.save({'bundle': parseBundle(bundle_el)}); device.save({'bundle': parseBundle(bundle_el)});
} }
...@@ -613,7 +633,7 @@ ...@@ -613,7 +633,7 @@
if (dev) { if (dev) {
dev.save({'active': true}); dev.save({'active': true});
} else { } else {
devices.create({'id': device_id}) devices.create({'id': device_id, 'jid': jid})
} }
}); });
// Make sure our own device is on the list (i.e. if it was // Make sure our own device is on the list (i.e. if it was
...@@ -661,6 +681,11 @@ ...@@ -661,6 +681,11 @@
_converse.api.listen.on('statusInitialized', initOMEMO); _converse.api.listen.on('statusInitialized', initOMEMO);
_converse.api.listen.on('addClientFeatures', _converse.api.listen.on('addClientFeatures',
() => _converse.api.disco.own.features.add(Strophe.NS.OMEMO_DEVICELIST+"notify")); () => _converse.api.disco.own.features.add(Strophe.NS.OMEMO_DEVICELIST+"notify"));
_converse.api.listen.on('userDetailsModalInitialized', (contact) => {
const jid = contact.get('jid');
_converse.getFingerprintsForContact(jid).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
});
} }
}); });
})); }));
<div class="modal fade" id="user-profile-modal" tabindex="-1" role="dialog" aria-labelledby="user-profile-modal-label" aria-hidden="true"> <div class="modal fade" id="user-details-modal" tabindex="-1" role="dialog" aria-labelledby="user-details-modal-label" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="user-profile-modal-label">{{{o.display_name}}}</h5> <h5 class="modal-title" id="user-details-modal-label">{{{o.display_name}}}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="{{{o.__('Close')}}}"><span aria-hidden="true">&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{[ if (o.image) { ]} {[ if (o.image) { ]}
...@@ -12,28 +12,59 @@ ...@@ -12,28 +12,59 @@
height="100" width="100" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/> height="100" width="100" src="data:{{{o.image_type}}};base64,{{{o.image}}}"/>
{[ } ]} {[ } ]}
{[ if (o.fullname) { ]} {[ if (o.fullname) { ]}
<p><label>{{{o.label_fullname}}}:</label>&nbsp;{{{o.fullname}}}</p> <p><label>{{{o.__('Full Name')}}}:</label>&nbsp;{{{o.fullname}}}</p>
{[ } ]} {[ } ]}
<p><label>{{{o.label_jid}}}:</label>&nbsp;{{{o.jid}}}</p> <p><label>{{{o.__('XMPP Address')}}}:</label>&nbsp;{{{o.jid}}}</p>
{[ if (o.nickname) { ]} {[ if (o.nickname) { ]}
<p><label>{{{o.label_nickname}}}:</label>&nbsp;{{{o.nickname}}}</p> <p><label>{{{o.__('Nickname')}}}:</label>&nbsp;{{{o.nickname}}}</p>
{[ } ]} {[ } ]}
{[ if (o.url) { ]} {[ if (o.url) { ]}
<p><label>{{{o.label_url}}}:</label>&nbsp;<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.url}}}</a></p> <p><label>{{{o.__('URL')}}}:</label>&nbsp;<a target="_blank" rel="noopener" href="{{{o.url}}}">{{{o.url}}}</a></p>
{[ } ]} {[ } ]}
{[ if (o.email) { ]} {[ if (o.email) { ]}
<p><label>{{{o.label_email}}}:</label>&nbsp;<a href="mailto:{{{o.email}}}">{{{o.email}}}</a></p> <p><label>{{{o.__('Email')}}}:</label>&nbsp;<a href="mailto:{{{o.email}}}">{{{o.email}}}</a></p>
{[ } ]} {[ } ]}
{[ if (o.role) { ]} {[ if (o.role) { ]}
<p><label>{{{o.label_role}}}:</label>&nbsp;{{{o.role}}}</p> <p><label>{{{o.__('Role')}}}:</label>&nbsp;{{{o.role}}}</p>
{[ } ]}
{[ if (o.has_omemo) { ]}
<hr>
<ul class="list-group fingerprints">
<li class="list-group-item active">{{{o.__('OMEMO Fingerprints')}}}</li>
{[ if (!o.devicelist.devices) { ]}
<li class="list-group-item"><span class="spinner fa fa-spinner centered"/></li>
{[ } ]}
{[ if (o.devicelist.devices) { ]}
{[ o.devicelist.devices.each(function (device) { ]}
{[ if (device.get('bundle') && device.get('bundle').fingerprint) { ]}
<li class="list-group-item">
<form class="fingerprint-trust">
<span class="fingerprint">{{{device.get('bundle').fingerprint}}}</span>
<div class="btn-group btn-group-toggle">
<label class="btn btn-primary btn--small" {[ if (device.get('trusted') != -1) { ]} active {[ } ]}>
<input type="radio" name="trust-{{{device.get('id')}}}" value="trusted"
{[ if (device.get('trusted') != -1) { ]} checked {[ } ]}>{{{o.__('Trusted')}}}
</label>
<label class="btn btn-secondary btn--small" {[ if (device.get('trusted') != -1) { ]} active {[ } ]}>
<input type="radio" name="trust-{{{device.get('id')}}}" value="untrusted"
{[ if (device.get('trusted') == -1) { ]} checked {[ } ]}>{{{o.__('Untrusted')}}}
</label>
</div>
</form>
</li>
{[ } ]}
{[ }); ]}
{[ } ]}
</ul>
{[ } ]} {[ } ]}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
{[ if (o.allow_contact_removal && o.is_roster_contact) { ]} {[ if (o.allow_contact_removal && o.is_roster_contact) { ]}
<button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>{{{o.label_remove}}}</button> <button type="button" class="btn btn-danger remove-contact"><i class="fa fa-trash"> </i>{{{o.__('Remove as contact')}}}</button>
{[ } ]} {[ } ]}
<button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>{{{o.label_refresh}}}</button> <button type="button" class="btn btn-info refresh-contact"><i class="fa fa-refresh"> </i>{{{o.__('Refresh')}}}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.label_close}}}</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.__('Close')}}}</button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -846,6 +846,22 @@ ...@@ -846,6 +846,22 @@
return result; return result;
}; };
u.arrayBufferToHex = function (ab) {
const hexCodes = [];
const padding = '00000000';
const view = new window.DataView(ab);
for (var i = 0; i < view.byteLength; i += 4) {
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
const value = view.getUint32(i)
// toString(16) will give the hex representation of the number without padding
const stringValue = value.toString(16)
// We use concatenation and slice for padding
const paddedValue = (padding + stringValue).slice(-padding.length)
hexCodes.push(paddedValue);
}
return hexCodes.join("");
};
u.arrayBufferToString = function (ab) { u.arrayBufferToString = function (ab) {
var enc = new TextDecoder("utf-8"); var enc = new TextDecoder("utf-8");
return enc.decode(new Uint8Array(ab)); return enc.decode(new Uint8Array(ab));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment