2015-06-04 15:53:04 +02:00
|
|
|
/**
|
|
|
|
* Kolab 2-Factor-Authentication plugin client functions
|
|
|
|
*
|
|
|
|
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
|
|
|
*
|
|
|
|
* @licstart The following is the entire license notice for the
|
|
|
|
* JavaScript code in this page.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* @licend The above is the entire license notice
|
|
|
|
* for the JavaScript code in this page.
|
|
|
|
*/
|
|
|
|
|
|
|
|
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|
|
|
var highsec_call_stack = [];
|
|
|
|
var highsec_dialog;
|
|
|
|
var factor_dialog;
|
2017-09-27 13:38:23 +02:00
|
|
|
|
|
|
|
if (!rcmail.env.kolab_2fa_factors) {
|
|
|
|
rcmail.env.kolab_2fa_factors = {};
|
|
|
|
}
|
|
|
|
|
2015-06-04 15:53:04 +02:00
|
|
|
/**
|
|
|
|
* Equivalend of PHP time()
|
|
|
|
*/
|
|
|
|
function time() {
|
|
|
|
return Math.round(new Date().getTime() / 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render the settings UI
|
|
|
|
*/
|
|
|
|
function render() {
|
|
|
|
var table = $('#kolab2fa-factors tbody');
|
|
|
|
table.html('');
|
|
|
|
|
|
|
|
var rows = 0;
|
2015-06-10 18:20:08 +02:00
|
|
|
$.each(rcmail.env.kolab_2fa_factors, function(id, props) {
|
2015-06-04 15:53:04 +02:00
|
|
|
if (props.active) {
|
2017-11-06 12:34:59 +01:00
|
|
|
var tr = $('<tr>').addClass(props.method).appendTo(table),
|
|
|
|
button = $('<a class="button icon delete">').attr({href: '#', rel: id})
|
|
|
|
.append($('<span class="inner">').text(rcmail.get_label('remove','kolab_2fa')));
|
|
|
|
|
2015-06-10 18:20:08 +02:00
|
|
|
$('<td>').addClass('name').text(props.label || props.name).appendTo(tr);
|
2015-06-04 15:53:04 +02:00
|
|
|
$('<td>').addClass('created').text(props.created || '??').appendTo(tr);
|
2017-11-06 12:34:59 +01:00
|
|
|
$('<td>').addClass('actions buttons-cell').append(button).appendTo(tr);
|
2015-06-04 15:53:04 +02:00
|
|
|
rows++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
table.parent()[(rows > 0 ? 'show' : 'hide')]();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open dialog to add the given authentication factor
|
|
|
|
*/
|
|
|
|
function add_factor(method) {
|
|
|
|
var lock, form = $('#kolab2fa-prop-' + method),
|
|
|
|
props = rcmail.env.kolab_2fa_factors[method];
|
|
|
|
|
|
|
|
if (form.length) {
|
|
|
|
form.get(0).reset();
|
|
|
|
form.find('img.qrcode').attr('src', 'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7');
|
|
|
|
form.off('submit');
|
|
|
|
|
|
|
|
factor_dialog = rcmail.show_popup_dialog(
|
|
|
|
form.show(),
|
|
|
|
rcmail.get_label('addfactor', 'kolab_2fa'),
|
|
|
|
[
|
|
|
|
{
|
|
|
|
text: rcmail.gettext('save', 'kolab_2fa'),
|
2017-11-06 12:34:59 +01:00
|
|
|
'class': 'mainaction save',
|
2015-06-04 15:53:04 +02:00
|
|
|
click: function(e) {
|
|
|
|
save_data(method);
|
2015-06-08 15:38:58 +02:00
|
|
|
}
|
2015-06-04 15:53:04 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
text: rcmail.gettext('cancel'),
|
2017-11-06 12:34:59 +01:00
|
|
|
'class': 'cancel',
|
2015-06-04 15:53:04 +02:00
|
|
|
click: function() {
|
|
|
|
factor_dialog.dialog('close');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
{
|
|
|
|
open: function(event, ui) {
|
2015-06-10 18:20:08 +02:00
|
|
|
$(event.target).find('input[name="_verify_code"]').keypress(function(e) {
|
|
|
|
if (e.which == 13) {
|
2019-04-08 10:35:45 +02:00
|
|
|
$(e.target).closest('.ui-dialog').find('button.mainaction').click();
|
2015-06-10 18:20:08 +02:00
|
|
|
}
|
|
|
|
});
|
2015-06-04 15:53:04 +02:00
|
|
|
},
|
|
|
|
close: function(event, ui) {
|
|
|
|
form.hide().appendTo(document.body);
|
|
|
|
factor_dialog = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.data('method', method)
|
|
|
|
.data('timestamp', time());
|
|
|
|
|
|
|
|
form.on('submit', function(e) {
|
|
|
|
save_data(method);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
// load generated data
|
|
|
|
lock = rcmail.set_busy(true, 'loading');
|
|
|
|
rcmail.http_post('plugin.kolab-2fa-data', { _method: method }, lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the given factor from the account
|
|
|
|
*/
|
2015-06-10 18:20:08 +02:00
|
|
|
function remove_factor(id) {
|
|
|
|
if (rcmail.env.kolab_2fa_factors[id]) {
|
|
|
|
rcmail.env.kolab_2fa_factors[id].active = false;
|
2015-06-04 15:53:04 +02:00
|
|
|
}
|
|
|
|
render();
|
|
|
|
|
|
|
|
var lock = rcmail.set_busy(true, 'saving');
|
2015-06-10 18:20:08 +02:00
|
|
|
rcmail.http_post('plugin.kolab-2fa-save', { _method: id, _data: 'false' }, lock);
|
2015-06-04 15:53:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Submit factor settings form
|
|
|
|
*/
|
|
|
|
function save_data(method) {
|
2015-06-10 18:20:08 +02:00
|
|
|
var lock, data, form = $('#kolab2fa-prop-' + method),
|
2015-06-04 15:53:04 +02:00
|
|
|
verify = form.find('input[name="_verify_code"]');
|
|
|
|
|
|
|
|
if (verify.length && !verify.val().length) {
|
|
|
|
alert(rcmail.get_label('verifycodemissing','kolab_2fa'));
|
|
|
|
verify.select();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-10 18:20:08 +02:00
|
|
|
data = form_data(form);
|
2015-06-04 15:53:04 +02:00
|
|
|
lock = rcmail.set_busy(true, 'saving');
|
|
|
|
rcmail.http_post('plugin.kolab-2fa-save', {
|
2015-06-10 18:20:08 +02:00
|
|
|
_method: data.id || method,
|
|
|
|
_data: JSON.stringify(data),
|
2015-06-04 15:53:04 +02:00
|
|
|
_verify_code: verify.val(),
|
|
|
|
_timestamp: factor_dialog ? factor_dialog.data('timestamp') : null
|
|
|
|
}, lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Collect all factor properties from the form
|
|
|
|
*/
|
|
|
|
function form_data(form)
|
|
|
|
{
|
|
|
|
var data = {};
|
|
|
|
form.find('input, select').each(function(i, elem) {
|
|
|
|
if (elem.name.indexOf('_prop') === 0) {
|
|
|
|
k = elem.name.match(/\[([a-z0-9_.-]+)\]$/i) ? RegExp.$1 : null;
|
|
|
|
if (k) {
|
|
|
|
data[k] = elem.tagName == 'SELECT' ? $('option:selected', elem).val() : $(elem).val();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute the given function after the user authorized the session with a 2nd factor
|
|
|
|
*/
|
2015-06-10 18:20:08 +02:00
|
|
|
function require_high_security(func, exclude)
|
2015-06-04 15:53:04 +02:00
|
|
|
{
|
|
|
|
// request 2nd factor auth
|
|
|
|
if (!rcmail.env.session_secured || rcmail.env.session_secured < time() - 120) {
|
|
|
|
var method, name;
|
|
|
|
|
|
|
|
// find an active factor
|
2015-06-10 18:20:08 +02:00
|
|
|
$.each(rcmail.env.kolab_2fa_factors, function(id, prop) {
|
|
|
|
if (prop.active && !method || method == exclude) {
|
|
|
|
method = id;
|
|
|
|
name = prop.label || prop.name;
|
|
|
|
if (!exclude || id !== exclude) {
|
|
|
|
return true;
|
|
|
|
}
|
2015-06-04 15:53:04 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// we have a registered factor, use it
|
|
|
|
if (method) {
|
|
|
|
highsec_call_stack.push(func);
|
|
|
|
|
2015-06-11 17:16:46 +02:00
|
|
|
// TODO: list all active factors to choose from
|
2015-06-04 15:53:04 +02:00
|
|
|
var html = String($('#kolab2fa-highsecuritydialog').html()).replace('$name', name);
|
2015-06-11 17:16:46 +02:00
|
|
|
|
2015-06-04 15:53:04 +02:00
|
|
|
highsec_dialog = rcmail.show_popup_dialog(
|
|
|
|
html,
|
|
|
|
rcmail.get_label('highsecurityrequired', 'kolab_2fa'),
|
|
|
|
[
|
|
|
|
{
|
|
|
|
text: rcmail.gettext('enterhighsecurity', 'kolab_2fa'),
|
|
|
|
click: function(e) {
|
|
|
|
var lock, code = highsec_dialog.find('input[name="_code"]').val();
|
|
|
|
|
|
|
|
if (code && code.length) {
|
|
|
|
lock = rcmail.set_busy(true, 'verifying');
|
|
|
|
rcmail.http_post('plugin.kolab-2fa-verify', {
|
|
|
|
_method: method,
|
|
|
|
_code: code,
|
|
|
|
_session: 1,
|
|
|
|
_timestamp: highsec_dialog.data('timestamp')
|
|
|
|
}, lock);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
highsec_dialog.find('input[name="_code"]').select();
|
|
|
|
}
|
|
|
|
},
|
2017-11-06 12:34:59 +01:00
|
|
|
'class': 'mainaction save'
|
2015-06-04 15:53:04 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
text: rcmail.gettext('cancel'),
|
2017-11-06 12:34:59 +01:00
|
|
|
'class': 'cancel',
|
2015-06-04 15:53:04 +02:00
|
|
|
click: function() {
|
|
|
|
highsec_dialog.dialog('close');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
{
|
|
|
|
open: function(event, ui) {
|
|
|
|
// submit code on <Enter>
|
|
|
|
$(event.target).find('input[name="_code"]').keypress(function(e) {
|
|
|
|
if (e.which == 13) {
|
2019-04-08 10:35:45 +02:00
|
|
|
$(e.target).closest('.ui-dialog').find('button.mainaction').click();
|
2015-06-04 15:53:04 +02:00
|
|
|
}
|
|
|
|
}).select();
|
|
|
|
},
|
|
|
|
close: function(event, ui) {
|
|
|
|
$(this).remove();
|
|
|
|
highsec_dialog = null;
|
|
|
|
highsec_call_stack.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
).data('timestamp', time());
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// just trigger the callback
|
|
|
|
func.call(this);
|
|
|
|
};
|
|
|
|
|
|
|
|
// callback for factor data provided by the server
|
|
|
|
rcmail.addEventListener('plugin.render_data', function(data) {
|
2015-06-10 18:20:08 +02:00
|
|
|
var method = data.method,
|
2015-06-04 15:53:04 +02:00
|
|
|
form = $('#kolab2fa-prop-' + method);
|
|
|
|
|
|
|
|
if (form.length) {
|
|
|
|
$.each(data, function(field, value) {
|
2015-06-10 18:20:08 +02:00
|
|
|
form.find('[name="_prop[' + field + ']"]').val(value);
|
2015-06-04 15:53:04 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
if (data.qrcode) {
|
|
|
|
$('img.qrcode[rel='+method+']').attr('src', "data:image/png;base64," + data.qrcode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (window.console) {
|
|
|
|
console.error("Cannot assign auth data", data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// callback for save action
|
|
|
|
rcmail.addEventListener('plugin.save_success', function(data) {
|
2015-06-10 18:20:08 +02:00
|
|
|
if (!data.active && rcmail.env.kolab_2fa_factors[data.id]) {
|
|
|
|
delete rcmail.env.kolab_2fa_factors[data.id];
|
|
|
|
}
|
|
|
|
else if (rcmail.env.kolab_2fa_factors[data.id]) {
|
|
|
|
$.extend(rcmail.env.kolab_2fa_factors[data.id], data);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rcmail.env.kolab_2fa_factors[data.id] = data;
|
2015-06-04 15:53:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (factor_dialog) {
|
|
|
|
factor_dialog.dialog('close');
|
|
|
|
}
|
|
|
|
|
|
|
|
render();
|
|
|
|
});
|
|
|
|
|
|
|
|
// callback for verify action
|
|
|
|
rcmail.addEventListener('plugin.verify_response', function(data) {
|
|
|
|
// execute high-security call stack and close dialog
|
|
|
|
if (data.success && highsec_dialog && highsec_dialog.is(':visible')) {
|
|
|
|
var func;
|
|
|
|
while (highsec_call_stack.length) {
|
|
|
|
func = highsec_call_stack.pop();
|
|
|
|
func();
|
|
|
|
}
|
|
|
|
|
|
|
|
highsec_dialog.dialog('close');
|
|
|
|
rcmail.env.session_secured = time();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rcmail.display_message(data.message, data.success ? 'confirmation' : 'warning');
|
|
|
|
|
|
|
|
if (highsec_dialog && highsec_dialog.is(':visible')) {
|
|
|
|
highsec_dialog.find('input[name="_code"]').val('').select();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$('#kolab2fa-prop-' + data.method + ' input.k2fa-verify').val('').select();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// callback for save failure
|
|
|
|
rcmail.addEventListener('plugin.reset_form', function(method) {
|
|
|
|
if (rcmail.env.kolab_2fa_factors[method]) {
|
|
|
|
rcmail.env.kolab_2fa_factors[method].active = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
render();
|
|
|
|
});
|
|
|
|
|
|
|
|
// handler for selections
|
|
|
|
$('#kolab2fa-add').change(function() {
|
|
|
|
var method = $('option:selected', this).val();
|
|
|
|
|
2015-06-11 17:16:46 +02:00
|
|
|
// require auth verification
|
|
|
|
require_high_security(function() {
|
|
|
|
add_factor(method);
|
|
|
|
});
|
|
|
|
|
2015-06-04 15:53:04 +02:00
|
|
|
this.selectedIndex = 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
// handler for delete button clicks
|
|
|
|
$('#kolab2fa-factors tbody').on('click', '.button.delete', function(e) {
|
2015-06-10 18:20:08 +02:00
|
|
|
var id = $(this).attr('rel');
|
2015-06-04 15:53:04 +02:00
|
|
|
|
|
|
|
// require auth verification
|
|
|
|
require_high_security(function() {
|
|
|
|
if (confirm(rcmail.get_label('authremoveconfirm', 'kolab_2fa'))) {
|
2015-06-10 18:20:08 +02:00
|
|
|
remove_factor(id);
|
2015-06-04 15:53:04 +02:00
|
|
|
}
|
2015-06-10 18:20:08 +02:00
|
|
|
}, id);
|
2015-06-04 15:53:04 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
// submit verification code on <Enter>
|
|
|
|
$('.propform input.k2fa-verify').keypress(function(e) {
|
|
|
|
if (e.which == 13) {
|
|
|
|
$(this).closest('.propform').find('.button.verify').click();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// render list initially
|
|
|
|
render();
|
|
|
|
});
|