mirror of
https://git.code.sf.net/p/seeddms/code
synced 2025-11-28 18:40:39 +00:00
415 lines
12 KiB
PHP
415 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Implementation of SetupWebauthn view
|
|
*
|
|
* @category DMS
|
|
* @package SeedDMS
|
|
* @license GPL 2
|
|
* @version @version@
|
|
* @author Uwe Steinmann <uwe@steinmann.cx>
|
|
* @copyright Copyright (C) 2016 Uwe Steinmann
|
|
* @version Release: @package_version@
|
|
*/
|
|
|
|
/**
|
|
* Include parent class
|
|
*/
|
|
require_once("class.Bootstrap.php");
|
|
|
|
/**
|
|
* Include classes for 2-factor authentication
|
|
*/
|
|
require "vendor/autoload.php";
|
|
|
|
/**
|
|
* Class which outputs the html page for ForcePasswordChange view
|
|
*
|
|
* @category DMS
|
|
* @package SeedDMS
|
|
* @author Markus Westphal, Malcolm Cowe, Uwe Steinmann <uwe@steinmann.cx>
|
|
* @copyright Copyright (C) 2016 Uwe Steinmann
|
|
* @version Release: @package_version@
|
|
*/
|
|
class SeedDMS_View_SetupWebauthn extends SeedDMS_Bootstrap_Style {
|
|
|
|
function css() { /* {{{ */
|
|
header('Content-Type: text/css; charset=UTF-8');
|
|
?>
|
|
.cdokey {
|
|
display: none;
|
|
background-color: orange;
|
|
color: white;
|
|
font-weight: bold;
|
|
margin: 10px 0;
|
|
padding: 10px;
|
|
}
|
|
<?php
|
|
} /* }}} */
|
|
|
|
function js() { /* {{{ */
|
|
header('Content-Type: application/javascript');
|
|
?>
|
|
function webauthnRegister(key, callback){
|
|
key = JSON.parse(key);
|
|
key.publicKey.attestation = undefined;
|
|
key.publicKey.challenge = new Uint8Array(key.publicKey.challenge); // convert type for use by key
|
|
key.publicKey.user.id = new Uint8Array(key.publicKey.user.id);
|
|
|
|
navigator.credentials.create({publicKey: key.publicKey})
|
|
.then(function (aNewCredentialInfo) {
|
|
console.log("Credentials.Create response: ", aNewCredentialInfo);
|
|
var cd = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(aNewCredentialInfo.response.clientDataJSON)));
|
|
if (key.b64challenge != cd.challenge) {
|
|
callback(false, 'key returned something unexpected (1)');
|
|
}
|
|
if ('http://'+key.publicKey.rp.name != cd.origin) {
|
|
console.log(key.publicKey.rp.name);
|
|
console.log(cd.origin);
|
|
return callback(false, 'key returned something unexpected (2)');
|
|
}
|
|
if (! ('type' in cd)) {
|
|
return callback(false, 'key returned something unexpected (3)');
|
|
}
|
|
if (cd.type != 'webauthn.create') {
|
|
return callback(false, 'key returned something unexpected (4)');
|
|
}
|
|
|
|
var ao = [];
|
|
(new Uint8Array(aNewCredentialInfo.response.attestationObject)).forEach(function(v){
|
|
ao.push(v);
|
|
});
|
|
var rawId = [];
|
|
(new Uint8Array(aNewCredentialInfo.rawId)).forEach(function(v){
|
|
rawId.push(v);
|
|
});
|
|
var info = {
|
|
rawId: rawId,
|
|
id: aNewCredentialInfo.id,
|
|
type: aNewCredentialInfo.type,
|
|
response: {
|
|
attestationObject: ao,
|
|
clientDataJSON:
|
|
JSON.parse(String.fromCharCode.apply(null, new Uint8Array(aNewCredentialInfo.response.clientDataJSON)))
|
|
}
|
|
};
|
|
console.log("Info: ", info);
|
|
callback(true, JSON.stringify(info));
|
|
})
|
|
.catch(function (aErr) {
|
|
if (
|
|
("name" in aErr) && (aErr.name == "AbortError" || aErr.name == "NS_ERROR_ABORT")
|
|
|| aErr.name == 'NotAllowedError'
|
|
) {
|
|
callback(false, 'abort');
|
|
} else {
|
|
callback(false, aErr.toString());
|
|
}
|
|
});
|
|
}
|
|
function webauthnAuthenticate(key, cb){
|
|
var pk = JSON.parse(key);
|
|
var originalChallenge = pk.challenge;
|
|
pk.challenge = new Uint8Array(pk.challenge);
|
|
pk.allowCredentials.forEach(function(k, idx){
|
|
pk.allowCredentials[idx].id = new Uint8Array(k.id);
|
|
});
|
|
/* ask the browser to prompt the user */
|
|
navigator.credentials.get({publicKey: pk})
|
|
.then(function(aAssertion) {
|
|
// console.log("Credentials.Get response: ", aAssertion);
|
|
var ida = [];
|
|
(new Uint8Array(aAssertion.rawId)).forEach(function(v){ ida.push(v); });
|
|
var cd = JSON.parse(String.fromCharCode.apply(null,
|
|
new Uint8Array(aAssertion.response.clientDataJSON)));
|
|
var cda = [];
|
|
(new Uint8Array(aAssertion.response.clientDataJSON)).forEach(function(v){ cda.push(v); });
|
|
var ad = [];
|
|
(new Uint8Array(aAssertion.response.authenticatorData)).forEach(function(v){ ad.push(v); });
|
|
var sig = [];
|
|
(new Uint8Array(aAssertion.response.signature)).forEach(function(v){ sig.push(v); });
|
|
var info = {
|
|
type: aAssertion.type,
|
|
originalChallenge: originalChallenge,
|
|
rawId: ida,
|
|
response: {
|
|
authenticatorData: ad,
|
|
clientData: cd,
|
|
clientDataJSONarray: cda,
|
|
signature: sig
|
|
}
|
|
};
|
|
cb(true, JSON.stringify(info));
|
|
})
|
|
.catch(function (aErr) {
|
|
if (("name" in aErr) && (aErr.name == "AbortError" || aErr.name == "NS_ERROR_ABORT" ||
|
|
aErr.name == "NotAllowedError")) {
|
|
cb(false, 'abort');
|
|
} else {
|
|
cb(false, aErr.toString());
|
|
}
|
|
});
|
|
}
|
|
|
|
$(function(){
|
|
|
|
$('#iregisterform').submit(function(ev){
|
|
var self = $(this);
|
|
ev.preventDefault();
|
|
var cp = $('select[name=cp]').val();
|
|
if (cp == "") {
|
|
$('.cerror').show().text("Please choose cross-platform setting - see note below about what this means");
|
|
return;
|
|
}
|
|
|
|
$('.cerror').empty().hide();
|
|
|
|
$.ajax({url: '../out/out.SetupWebauthn.php',
|
|
method: 'POST',
|
|
data: {action: 'registeruser', registerusername: self.find('[name=registerusername]').val(), crossplatform: cp},
|
|
dataType: 'json',
|
|
success: function(j){
|
|
/* activate the key and get the response */
|
|
webauthnRegister(j.challenge, function(success, info){
|
|
if (success) {
|
|
$.ajax({url: '../out/out.SetupWebauthn.php',
|
|
method: 'POST',
|
|
data: {action: 'register', register: info},
|
|
dataType: 'json',
|
|
success: function(j){
|
|
noty({
|
|
text: 'registration completed successfully',
|
|
type: 'success',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
},
|
|
error: function(xhr, status, error){
|
|
noty({
|
|
text: 'registration failed: '+error+": "+xhr.responseText,
|
|
type: 'error',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
// $('.cerror').text("registration failed: "+error+": "+xhr.responseText).show();
|
|
}
|
|
});
|
|
} else {
|
|
noty({
|
|
text: info,
|
|
type: 'error',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
// $('.cerror').text(info).show();
|
|
}
|
|
});
|
|
},
|
|
|
|
error: function(xhr, status, error){
|
|
noty({
|
|
text: "couldn't initiate registration: "+error+": "+xhr.responseText,
|
|
type: 'error',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
$('#iloginform').submit(function(ev){
|
|
var self = $(this);
|
|
ev.preventDefault();
|
|
$('.cerror').empty().hide();
|
|
|
|
$.ajax({url: '../out/out.SetupWebauthn.php',
|
|
method: 'POST',
|
|
data: {action: 'preparelogin', loginusername: self.find('[name=loginusername]').val()},
|
|
dataType: 'json',
|
|
success: function(j){
|
|
/* activate the key and get the response */
|
|
webauthnAuthenticate(j.challenge, function(success, info){
|
|
if (success) {
|
|
$.ajax({url: '../out/out.SetupWebauthn.php',
|
|
method: 'POST',
|
|
data: {action: 'login', login: info},
|
|
dataType: 'json',
|
|
success: function(j){
|
|
noty({
|
|
text: 'login completed successfully',
|
|
type: 'success',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
},
|
|
error: function(xhr, status, error){
|
|
noty({
|
|
text: 'login failed: '+error+": "+xhr.responseText,
|
|
type: 'error',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
// $('.cerror').text("login failed: "+error+": "+xhr.responseText).show();
|
|
}
|
|
});
|
|
} else {
|
|
noty({
|
|
text: info,
|
|
type: 'error',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
// $('.cerror').text(info).show();
|
|
}
|
|
});
|
|
},
|
|
|
|
error: function(xhr, status, error){
|
|
noty({
|
|
text: "couldn't initiate login: "+error+": "+xhr.responseText,
|
|
type: 'error',
|
|
dismissQueue: true,
|
|
layout: 'topRight',
|
|
theme: 'defaultTheme',
|
|
timeout: 1500,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
<?php
|
|
} /* }}} */
|
|
|
|
function registeruser() { /* {{{ */
|
|
$dms = $this->params['dms'];
|
|
$user = $this->params['user'];
|
|
|
|
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
|
|
|
|
/* initiate the registration */
|
|
$username = $user->getLogin();
|
|
$userid = $user->getId();
|
|
$crossplatform = ! empty($_POST['crossplatform']) && $_POST['crossplatform'] == 'Yes';
|
|
|
|
// $user->setWebauthn($webauthn->cancel());
|
|
|
|
$j = ['challenge' => $webauthn->prepareChallengeForRegistration($username, $userid, $crossplatform)];
|
|
header('Content-type: application/json');
|
|
echo json_encode($j);
|
|
} /* }}} */
|
|
|
|
function register() { /* {{{ */
|
|
$dms = $this->params['dms'];
|
|
$user = $this->params['user'];
|
|
|
|
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
|
|
|
|
/* The heart of the matter */
|
|
$user->setWebauthn($webauthn->register($_POST['register'], $user->getWebauthn()));
|
|
|
|
$j = 'ok';
|
|
|
|
header('Content-type: application/json');
|
|
echo json_encode($j);
|
|
} /* }}} */
|
|
|
|
function preparelogin() { /* {{{ */
|
|
$dms = $this->params['dms'];
|
|
$user = $this->params['user'];
|
|
|
|
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
|
|
|
|
$j['challenge'] = $webauthn->prepareForLogin($user->getWebauthn());
|
|
header('Content-type: application/json');
|
|
echo json_encode($j);
|
|
} /* }}} */
|
|
|
|
function login() { /* {{{ */
|
|
$dms = $this->params['dms'];
|
|
$user = $this->params['user'];
|
|
|
|
$webauthn = new \Davidearl\WebAuthn\WebAuthn($_SERVER['HTTP_HOST']);
|
|
|
|
if (! $webauthn->authenticate($_POST['login'], $user->getWebauthn())) {
|
|
http_response_code(401);
|
|
echo 'failed to authenticate with that key';
|
|
exit;
|
|
}
|
|
$j = 'ok';
|
|
header('Content-type: application/json');
|
|
echo json_encode($j);
|
|
} /* }}} */
|
|
|
|
function show() { /* {{{ */
|
|
$dms = $this->params['dms'];
|
|
$user = $this->params['user'];
|
|
$sitename = $this->params['sitename'];
|
|
|
|
$this->htmlStartPage(getMLText("webauthn"), "forcepasswordchange");
|
|
$this->globalNavigation();
|
|
$this->contentStart();
|
|
$this->pageNavigation(getMLText("my_account"), "my_account");
|
|
$this->contentHeading(getMLText('webauthn_auth'));
|
|
echo "<div class=\"alert\">".getMLText('webauthn_info')."</div>";
|
|
echo '<div class="row-fluid">';
|
|
echo '<div class="span6">';
|
|
$this->contentHeading('webauthn_registration');
|
|
$this->contentContainerStart();
|
|
?>
|
|
<div class='cerror'></div>
|
|
<div class='cdone'></div>
|
|
|
|
<div class='ccontent'></div>
|
|
<form class="form-horizontal" id="iregisterform" action="/" method="post" name="form1">
|
|
<input type="hidden" name="registerusername" value="<?= $user->getLogin() ?>" />
|
|
<div class="control-group"><label class="control-label"><?php printMLText('webauth_crossplatform'); ?></label><div class="controls">
|
|
<select name="crossplatform" id="crossplatform">
|
|
<option value="yes">Yes</option>
|
|
<option value="no">No</option>
|
|
</select>
|
|
</div></div>
|
|
<?php
|
|
$this->formSubmit(getMLText('submit_webauthn_register'));
|
|
?>
|
|
</form>
|
|
<?php
|
|
$this->contentContainerEnd();
|
|
|
|
echo $this->infoMsg(getMLText('webauthn_crossplatform_info'));
|
|
echo "</div>";
|
|
if($user->getWebauthn()) {
|
|
echo '<div class="span6">';
|
|
$this->contentHeading('webauthn_login_test');
|
|
$this->contentContainerStart();
|
|
?>
|
|
<form class="form-horizontal" id="iloginform" action="/" method="post" name="form1">
|
|
<input type="hidden" name="loginusername" value="<?= $user->getLogin() ?>" />
|
|
<?php
|
|
$this->formSubmit(getMLText('submit_webauthn_login'));
|
|
?>
|
|
</form>
|
|
<?php
|
|
$this->contentContainerEnd();
|
|
}
|
|
|
|
echo '</div>';
|
|
echo '</div>';
|
|
$this->contentEnd();
|
|
$this->htmlEndPage();
|
|
} /* }}} */
|
|
}
|
|
|