This commit is contained in:
Roland Gruber 2020-01-05 19:05:55 +01:00
parent 03ced7c697
commit 9e1e0634e6
7 changed files with 306 additions and 13 deletions

BIN
lam/graphics/webauthn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

133
lam/lib/tools/webauthn.inc Normal file
View File

@ -0,0 +1,133 @@
<?php
namespace LAM\TOOLS\WEBAUTHN;
use LAM\LIB\TWO_FACTOR\TwoFactorProviderService;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2020 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* Webauthn device management.
*
* @author Roland Gruber
* @package tools
*/
/**
* Webauthn device management.
*
* @package tools
*/
class toolWebauthn implements \LAMTool {
/**
* Returns the name of the tool.
*
* @return string name
*/
function getName() {
return "Webauthn";
}
/**
* returns a description text for the tool.
*
* @return string description
*/
function getDescription() {
return _("Here you can manage your webauthn devices.");
}
/**
* Returns a link to the tool page (relative to templates/).
*
* @return string link
*/
function getLink() {
return "tools/webauthn.php";
}
/**
* Returns if the tool requires write access to LDAP.
*
* @return boolean true if write access is needed
*/
function getRequiresWriteAccess() {
return false;
}
/**
* Returns if the tool requires password change rights.
*
* @return boolean true if password change rights are needed
*/
function getRequiresPasswordChangeRights() {
return false;
}
/**
* Returns the link to the tool image (relative to graphics/)
*
* @return string image URL
*/
function getImageLink() {
return 'webauthn.png';
}
/**
* Returns the prefered position of this tool on the tools page.
* The position may be between 0 and 1000. 0 is the top position.
*
* @return int prefered position
*/
function getPosition() {
return 800;
}
/**
* Returns a list of sub tools or an empty array.
*
* @return array list of subtools (LAMTool)
*/
function getSubTools() {
return array();
}
/**
* Returns if the tool is visible in the menu.
*
* @return boolean visible
*/
function isVisible() {
return ($_SESSION['config']->getTwoFactorAuthentication() === TwoFactorProviderService::TWO_FACTOR_WEBAUTHN);
}
/**
* Returns if a tool may be hidden by configuration in the LAM server profile.
*
* @return boolean hideable
*/
function isHideable() {
return true;
}
}
?>

View File

@ -49,7 +49,7 @@ use \LAMException;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2019 Roland Gruber
Copyright (C) 2019 - 2020 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -546,7 +546,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo
$pdo = $this->getPDO();
$statement = $pdo->prepare('select * from ' . self::TABLE_NAME . ' where userId like :searchTerm');
$statement->execute(array(
':searchTerm' => '%' . $searchTerm . '%'
':searchTerm' => $searchTerm
));
$results = $statement->fetchAll();
$devices = array();

View File

@ -1,7 +1,7 @@
/**
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2019 Roland Gruber
Copyright (C) 2003 - 2020 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -1581,18 +1581,41 @@ window.lam.webauthn.addDeviceActionListeners = function() {
/**
* Removes a webauthn device.
*
* @param element button
* @param event click event
*/
window.lam.webauthn.removeDevice = function(event) {
event.preventDefault();
const element = jQuery(event.target);
window.lam.webauthn.removeDeviceDialog(element, 'webauthnDevices');
return false;
}
/**
* Removes a user's own webauthn device.
*
* @param event click event
*/
window.lam.webauthn.removeOwnDevice = function(event) {
event.preventDefault();
const element = jQuery(event.target);
window.lam.webauthn.removeDeviceDialog(element, 'webauthnOwnDevices');
return false;
}
/**
* Opens the remove device diaog.
*
* @param element delete button
* @param action action for request (delete|deleteOwn)
*/
window.lam.webauthn.removeDeviceDialog = function(element, action) {
const dialogTitle = element.data('dialogtitle');
const okText = element.data('oktext');
const cancelText = element.data('canceltext');
let buttonList = {};
buttonList[okText] = function() {
jQuery('#webauthnDeleteConfirm').dialog('close');
window.lam.webauthn.sendRemoveDeviceRequest(element);
window.lam.webauthn.sendRemoveDeviceRequest(element, action);
};
buttonList[cancelText] = function() {
jQuery(this).dialog("close");
@ -1604,16 +1627,15 @@ window.lam.webauthn.removeDevice = function(event) {
buttons: buttonList,
width: 'auto'
});
return false;
}
/**
* Sends the remove request to server.
*
* @param element button element
* @param action action (delete|deleteOwn)
*/
window.lam.webauthn.sendRemoveDeviceRequest = function(element) {
window.lam.webauthn.sendRemoveDeviceRequest = function(element, action) {
const dn = element.data('dn');
const credential = element.data('credential');
const resultDiv = jQuery('#webauthn_results');
@ -1626,7 +1648,7 @@ window.lam.webauthn.sendRemoveDeviceRequest = function(element) {
credentialId: credential
};
jQuery.ajax({
url: '../misc/ajax.php?function=webauthnDevices',
url: '../misc/ajax.php?function=' + action,
method: 'POST',
data: data
})

View File

@ -15,7 +15,7 @@ use \LAMCfgMain;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2011 - 2019 Roland Gruber
Copyright (C) 2011 - 2020 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -159,6 +159,9 @@ class Ajax {
ob_end_clean();
echo $jsonOut;
}
elseif ($function === 'webauthnOwnDevices') {
$this->manageWebauthnOwnDevices();
}
}
/**
@ -255,7 +258,7 @@ class Ajax {
private function manageWebauthnDevicesSearch($searchTerm) {
include_once __DIR__ . '/../../lib/webauthn.inc';
$database = new \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite();
$results = $database->searchDevices($searchTerm);
$results = $database->searchDevices('%' . $searchTerm . '%');
$row = new htmlResponsiveRow();
$row->addVerticalSpacer('0.5rem');
if (empty($results)) {
@ -326,6 +329,23 @@ class Ajax {
echo json_encode(array('content' => $content));
}
/**
* Manages requests to setup user's own webauthn devices.
*/
private function manageWebauthnOwnDevices() {
$action = $_POST['action'];
$dn = $_POST['dn'];
$sessionDn = $_SESSION['ldap']->getUserName();
if ($sessionDn !== $dn) {
logNewMessage(LOG_ERR, 'Webauthn delete canceled, DN does not match.');
die();
}
if ($action === 'delete') {
$credentialId = $_POST['credentialId'];
$this->manageWebauthnDevicesDelete($dn, $credentialId);
}
}
/**
* Handles DN selection fields.
*

View File

@ -0,0 +1,117 @@
<?php
namespace LAM\TOOLS\WEBAUTHN;
use \htmlButton;
use htmlDiv;
use htmlGroup;
use \htmlOutputText;
use \htmlResponsiveRow;
use \htmlResponsiveTable;
use \htmlStatusMessage;
use \htmlTitle;
use \LAM\LOGIN\WEBAUTHN\PublicKeyCredentialSourceRepositorySQLite;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2020 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* Allows webauthn device management.
*
* @author Roland Gruber
* @package tools
*/
/** security functions */
include_once(__DIR__ . "/../../lib/security.inc");
/** access to configuration options */
include_once(__DIR__ . "/../../lib/config.inc");
/** webauthn */
include_once __DIR__ . '/../../lib/webauthn.inc';
// start session
startSecureSession();
enforceUserIsLoggedIn();
checkIfToolIsActive('toolWebauthn');
setlanguage();
include __DIR__ . '/../../lib/adminHeader.inc';
echo '<div class="user-bright smallPaddingContent">';
echo "<form action=\"webauthn.php\" method=\"post\">\n";
$tabindex = 1;
$container = new htmlResponsiveRow();
$container->add(new htmlTitle(_("Webauthn devices")), 12);
$userDn = $_SESSION['ldap']->getUserName();
$database = new PublicKeyCredentialSourceRepositorySQLite();
$results = $database->searchDevices($userDn);
$container->addVerticalSpacer('0.5rem');
$buttonGroup = new htmlGroup();
$reloadButton = new htmlButton('reload', _('Reload'));
$reloadButton->setIconClass('refreshButton');
$buttonGroup->addElement($reloadButton);
$container->add($buttonGroup, 12);
$container->addVerticalSpacer('2rem');
if (empty($results)) {
$container->add(new htmlStatusMessage('INFO', _('No devices found.')), 12);
}
else {
$titles = array(
_('Registration'),
_('Last use'),
_('Delete')
);
$data = array();
$id = 0;
foreach ($results as $result) {
$delButton = new htmlButton('deleteDevice' . $id, 'delete.png', true);
$delButton->addDataAttribute('credential', $result['credentialId']);
$delButton->addDataAttribute('dn', $result['dn']);
$delButton->addDataAttribute('dialogtitle', _('Remove device'));
$delButton->addDataAttribute('oktext', _('Ok'));
$delButton->addDataAttribute('canceltext', _('Cancel'));
$delButton->setOnClick('window.lam.webauthn.removeOwnDevice(event);');
$data[] = array(
new htmlOutputText(date('Y-m-d H:i:s', $result['registrationTime'])),
new htmlOutputText(date('Y-m-d H:i:s', $result['lastUseTime'])),
$delButton
);
$id++;
}
$table = new htmlResponsiveTable($titles, $data);
$tableDiv = new htmlDiv('webauthn_results', $table);
$tableDiv->addDataAttribute('sec_token_value', getSecurityTokenValue());
$container->add($tableDiv, 12);
}
$container->addVerticalSpacer('2rem');
$confirmationDiv = new htmlDiv('webauthnDeleteConfirm', new htmlOutputText(_('Do you really want to remove this device?')), array('hidden'));
$container->add($confirmationDiv, 12);
parseHtml(null, $container, array(), false, $tabindex, 'user');
echo '</form>';
echo '</div>';
include __DIR__ . '/../../lib/adminFooter.inc';
?>

View File

@ -148,8 +148,9 @@ class PublicKeyCredentialSourceRepositorySQLiteTest extends TestCase {
"uh1",
1);
$this->database->saveCredentialSource($source1);
$this->assertNotEmpty($this->database->searchDevices('h1'));
$this->assertEmpty($this->database->searchDevices('h2'));
$this->assertNotEmpty($this->database->searchDevices('uh1'));
$this->assertNotEmpty($this->database->searchDevices('%h1%'));
$this->assertEmpty($this->database->searchDevices('uh2'));
}
public function test_deleteDevice() {