YubiKey support
This commit is contained in:
parent
a55c337efd
commit
fc24f4a2aa
|
@ -311,6 +311,10 @@ $helpArray = array (
|
||||||
"Text" => _('Protect the self service login with a captcha.')),
|
"Text" => _('Protect the self service login with a captcha.')),
|
||||||
"523" => array ("Headline" => _('Base color'),
|
"523" => array ("Headline" => _('Base color'),
|
||||||
"Text" => _('Background color for self service pages.')),
|
"Text" => _('Background color for self service pages.')),
|
||||||
|
"524" => array ("Headline" => _('Client id'),
|
||||||
|
"Text" => _('Please enter your client id for the verification API.')),
|
||||||
|
"525" => array ("Headline" => _('Secret key'),
|
||||||
|
"Text" => _('Please enter your secret key for the verification API.')),
|
||||||
"550" => array ("Headline" => _("From address"),
|
"550" => array ("Headline" => _("From address"),
|
||||||
"Text" => _("This email address will be set as sender address of all password mails. If empty the system default (php.ini) will be used.")),
|
"Text" => _("This email address will be set as sender address of all password mails. If empty the system default (php.ini) will be used.")),
|
||||||
"551" => array ("Headline" => _("Subject"),
|
"551" => array ("Headline" => _("Subject"),
|
||||||
|
|
|
@ -53,6 +53,8 @@ interface TwoFactorProvider {
|
||||||
*/
|
*/
|
||||||
public function verify2ndFactor($user, $password, $serial, $twoFactorInput);
|
public function verify2ndFactor($user, $password, $serial, $twoFactorInput);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -223,6 +225,71 @@ class PrivacyIDEAProvider implements TwoFactorProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication via YubiKeys.
|
||||||
|
*
|
||||||
|
* @author Roland Gruber
|
||||||
|
*/
|
||||||
|
class YubicoProvider implements TwoFactorProvider {
|
||||||
|
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param TwoFactorConfiguration $config configuration
|
||||||
|
*/
|
||||||
|
public function __construct(&$config) {
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::getSerials()
|
||||||
|
*/
|
||||||
|
public function getSerials($user, $password) {
|
||||||
|
$keyAttributeName = strtolower('yubiKeyId');
|
||||||
|
$loginDn = $_SESSION['ldap']->getUserName();
|
||||||
|
$handle = getLDAPServerHandle();
|
||||||
|
$ldapData = ldapGetDN($loginDn, array($keyAttributeName), $handle);
|
||||||
|
if (empty($ldapData[$keyAttributeName])) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
return array(implode(', ', $ldapData[$keyAttributeName]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::verify2ndFactor()
|
||||||
|
*/
|
||||||
|
public function verify2ndFactor($user, $password, $serial, $twoFactorInput) {
|
||||||
|
include_once(__DIR__ . "/3rdParty/yubico/Yubico.php");
|
||||||
|
$serialData = $this->getSerials($user, $password);
|
||||||
|
if (empty($serialData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$serials = explode(', ', $serialData[0]);
|
||||||
|
$serialMatched = false;
|
||||||
|
foreach ($serials as $serial) {
|
||||||
|
if (strpos($twoFactorInput, $serial) === 0) {
|
||||||
|
$serialMatched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$serialMatched) {
|
||||||
|
throw new \Exception(_('YubiKey id does not match allowed list of key ids.'));
|
||||||
|
}
|
||||||
|
$url = $this->config->twoFactorAuthenticationURL;
|
||||||
|
$httpsverify = !$this->config->twoFactorAuthenticationInsecure;
|
||||||
|
$clientId = $this->config->twoFactorAuthenticationClientId;
|
||||||
|
$secretKey = $this->config->twoFactorAuthenticationSecretKey;
|
||||||
|
$auth = new \Auth_Yubico($clientId, $secretKey, $url, $httpsverify);
|
||||||
|
$auth->verify($twoFactorInput);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the correct 2 factor provider.
|
* Returns the correct 2 factor provider.
|
||||||
*/
|
*/
|
||||||
|
@ -232,6 +299,8 @@ class TwoFactorProviderService {
|
||||||
const TWO_FACTOR_NONE = 'none';
|
const TWO_FACTOR_NONE = 'none';
|
||||||
/** 2factor authentication via privacyIDEA */
|
/** 2factor authentication via privacyIDEA */
|
||||||
const TWO_FACTOR_PRIVACYIDEA = 'privacyidea';
|
const TWO_FACTOR_PRIVACYIDEA = 'privacyidea';
|
||||||
|
/** 2factor authentication via YubiKey */
|
||||||
|
const TWO_FACTOR_YUBICO = 'yubico';
|
||||||
|
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
|
@ -260,6 +329,9 @@ class TwoFactorProviderService {
|
||||||
if ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA) {
|
if ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA) {
|
||||||
return new PrivacyIDEAProvider($this->config);
|
return new PrivacyIDEAProvider($this->config);
|
||||||
}
|
}
|
||||||
|
elseif ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_YUBICO) {
|
||||||
|
return new YubicoProvider($this->config);
|
||||||
|
}
|
||||||
throw new \Exception('Invalid provider: ' . $this->config->twoFactorAuthentication);
|
throw new \Exception('Invalid provider: ' . $this->config->twoFactorAuthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,11 +342,11 @@ class TwoFactorProviderService {
|
||||||
* @return TwoFactorConfiguration configuration
|
* @return TwoFactorConfiguration configuration
|
||||||
*/
|
*/
|
||||||
private function getConfigSelfService(&$profile) {
|
private function getConfigSelfService(&$profile) {
|
||||||
$config = new TwoFactorConfiguration();
|
$tfConfig = new TwoFactorConfiguration();
|
||||||
$config->twoFactorAuthentication = $profile->twoFactorAuthentication;
|
$tfConfig->twoFactorAuthentication = $profile->twoFactorAuthentication;
|
||||||
$config->twoFactorAuthenticationInsecure = $profile->twoFactorAuthenticationInsecure;
|
$tfConfig->twoFactorAuthenticationInsecure = $profile->twoFactorAuthenticationInsecure;
|
||||||
$config->twoFactorAuthenticationURL = $profile->twoFactorAuthenticationURL;
|
$tfConfig->twoFactorAuthenticationURL = $profile->twoFactorAuthenticationURL;
|
||||||
return $config;
|
return $tfConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -284,11 +356,13 @@ class TwoFactorProviderService {
|
||||||
* @return TwoFactorConfiguration configuration
|
* @return TwoFactorConfiguration configuration
|
||||||
*/
|
*/
|
||||||
private function getConfigAdmin($conf) {
|
private function getConfigAdmin($conf) {
|
||||||
$config = new TwoFactorConfiguration();
|
$tfConfig = new TwoFactorConfiguration();
|
||||||
$config->twoFactorAuthentication = $conf->getTwoFactorAuthentication();
|
$tfConfig->twoFactorAuthentication = $conf->getTwoFactorAuthentication();
|
||||||
$config->twoFactorAuthenticationInsecure = $conf->getTwoFactorAuthenticationInsecure();
|
$tfConfig->twoFactorAuthenticationInsecure = $conf->getTwoFactorAuthenticationInsecure();
|
||||||
$config->twoFactorAuthenticationURL = $conf->getTwoFactorAuthenticationURL();
|
$tfConfig->twoFactorAuthenticationURL = $conf->getTwoFactorAuthenticationURL();
|
||||||
return $config;
|
$tfConfig->twoFactorAuthenticationClientId = $conf->getTwoFactorAuthenticationClientId();
|
||||||
|
$tfConfig->twoFactorAuthenticationSecretKey = $conf->getTwoFactorAuthenticationSecretKey();
|
||||||
|
return $tfConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -299,7 +373,30 @@ class TwoFactorProviderService {
|
||||||
* @author Roland Gruber
|
* @author Roland Gruber
|
||||||
*/
|
*/
|
||||||
class TwoFactorConfiguration {
|
class TwoFactorConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string provider id
|
||||||
|
*/
|
||||||
public $twoFactorAuthentication = null;
|
public $twoFactorAuthentication = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var service URL
|
||||||
|
*/
|
||||||
public $twoFactorAuthenticationURL = null;
|
public $twoFactorAuthenticationURL = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var disable certificate check
|
||||||
|
*/
|
||||||
public $twoFactorAuthenticationInsecure = false;
|
public $twoFactorAuthenticationInsecure = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var client ID for API access
|
||||||
|
*/
|
||||||
|
public $twoFactorAuthenticationClientId = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var secret key for API access
|
||||||
|
*/
|
||||||
|
public $twoFactorAuthenticationSecretKey = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Class for verifying Yubico One-Time-Passcodes
|
||||||
|
*
|
||||||
|
* @category Auth
|
||||||
|
* @package Auth_Yubico
|
||||||
|
* @author Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com>
|
||||||
|
* @author Roland Gruber
|
||||||
|
* @copyright 2007-2015 Yubico AB
|
||||||
|
* @copyright 2018 Roland Gruber
|
||||||
|
* @license https://opensource.org/licenses/bsd-license.php New BSD License
|
||||||
|
* @version 2.0
|
||||||
|
* @link https://www.yubico.com/
|
||||||
|
*
|
||||||
|
* Adapted for LAM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for verifying Yubico One-Time-Passcodes
|
||||||
|
*/
|
||||||
|
class Auth_Yubico {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yubico client ID
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yubico client key
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $clientKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL part of validation server
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last query to server
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $lastquery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response from server
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag whether to verify HTTPS server certificates or not.
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
private $httpsVerify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* Sets up the object
|
||||||
|
*
|
||||||
|
* @param string $id The client identity
|
||||||
|
* @param string $key The client MAC key
|
||||||
|
* @param string $url URL
|
||||||
|
* @param boolean $httpsverify Flag whether to use verify HTTPS
|
||||||
|
* server certificates
|
||||||
|
*/
|
||||||
|
public function __construct($id, $key, $url, $httpsverify) {
|
||||||
|
$this->clientId = $id;
|
||||||
|
$this->clientKey = base64_decode($key);
|
||||||
|
$this->httpsVerify = $httpsverify;
|
||||||
|
$this->url = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse input string into password, yubikey prefix,
|
||||||
|
* ciphertext, and OTP.
|
||||||
|
*
|
||||||
|
* @param string Input string to parse
|
||||||
|
* @param string Optional delimiter re-class, default is '[:]'
|
||||||
|
* @return array Keyed array with fields
|
||||||
|
*/
|
||||||
|
private function parsePasswordOTP($str, $delim = '[:]') {
|
||||||
|
if (!preg_match("/^((.*)" . $delim . ")?(([cbdefghijklnrtuv]{0,12})([cbdefghijklnrtuv]{32}))$/i", $str, $matches)) {
|
||||||
|
/* Dvorak? */
|
||||||
|
if (!preg_match("/^((.*)" . $delim . ")?(([jxe\\.uidchtnbpygk]{0,12})([jxe\\.uidchtnbpygk]{32}))$/i", $str, $matches)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ret['otp'] = strtr($matches[3], "jxe.uidchtnbpygk", "cbdefghijklnrtuv");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$ret['otp'] = $matches[3];
|
||||||
|
}
|
||||||
|
$ret['password'] = $matches[2];
|
||||||
|
$ret['prefix'] = $matches[4];
|
||||||
|
$ret['ciphertext'] = $matches[5];
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse parameters from last response
|
||||||
|
*
|
||||||
|
* example: getParameters("timestamp", "sessioncounter", "sessionuse");
|
||||||
|
*
|
||||||
|
* @param array @parameters Array with strings representing
|
||||||
|
* parameters to parse
|
||||||
|
* @return array parameter array from last response
|
||||||
|
*/
|
||||||
|
private function getParameters($parameters) {
|
||||||
|
if ($parameters == null) {
|
||||||
|
$parameters = array(
|
||||||
|
'timestamp',
|
||||||
|
'sessioncounter',
|
||||||
|
'sessionuse'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$param_array = array();
|
||||||
|
foreach ($parameters as $param) {
|
||||||
|
if (!preg_match("/" . $param . "=([0-9]+)/", $this->response, $out)) {
|
||||||
|
throw new LAMException(_('Error'), 'Could not parse parameter ' . $param . ' from response');
|
||||||
|
}
|
||||||
|
$param_array[$param] = $out[1];
|
||||||
|
}
|
||||||
|
return $param_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify Yubico OTP against multiple URLs
|
||||||
|
* Protocol specification 2.0 is used to construct validation requests
|
||||||
|
*
|
||||||
|
* @param string $token Yubico OTP
|
||||||
|
* @param int $use_timestamp 1=>send request with ×tamp=1 to
|
||||||
|
* get timestamp and session information
|
||||||
|
* in the response
|
||||||
|
* @param string $sl Sync level in percentage between 0
|
||||||
|
* and 100 or "fast" or "secure".
|
||||||
|
* @param int $timeout Max number of seconds to wait
|
||||||
|
* for responses
|
||||||
|
*/
|
||||||
|
public function verify($token, $use_timestamp = null, $sl = null, $timeout = null) {
|
||||||
|
/* Construct parameters string */
|
||||||
|
$ret = $this->parsePasswordOTP($token);
|
||||||
|
if (!$ret) {
|
||||||
|
throw new LAMException(_('Error'), 'Could not parse Yubikey OTP');
|
||||||
|
}
|
||||||
|
$params = array(
|
||||||
|
'id' => $this->clientId,
|
||||||
|
'otp' => $ret['otp'],
|
||||||
|
'nonce' => md5(uniqid(getRandomNumber()))
|
||||||
|
);
|
||||||
|
/* Take care of protocol version 2 parameters */
|
||||||
|
if ($use_timestamp) {
|
||||||
|
$params['timestamp'] = 1;
|
||||||
|
}
|
||||||
|
if ($sl) {
|
||||||
|
$params['sl'] = $sl;
|
||||||
|
}
|
||||||
|
if ($timeout) {
|
||||||
|
$params['timeout'] = $timeout;
|
||||||
|
}
|
||||||
|
ksort($params);
|
||||||
|
$parameters = '';
|
||||||
|
foreach ($params as $p => $v) {
|
||||||
|
$parameters .= "&" . $p . "=" . $v;
|
||||||
|
}
|
||||||
|
$parameters = ltrim($parameters, "&");
|
||||||
|
|
||||||
|
/* Generate signature. */
|
||||||
|
if ($this->clientKey != "") {
|
||||||
|
$signature = base64_encode(hash_hmac('sha1', $parameters, $this->clientKey, true));
|
||||||
|
$signature = preg_replace('/\+/', '%2B', $signature);
|
||||||
|
$parameters .= '&h=' . $signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate and prepare request. */
|
||||||
|
$mh = curl_multi_init();
|
||||||
|
$ch = array();
|
||||||
|
$query = $this->url . "?" . $parameters;
|
||||||
|
|
||||||
|
$this->lastquery = $query;
|
||||||
|
logNewMessage(LOG_DEBUG, 'Yubico url: ' . $query);
|
||||||
|
|
||||||
|
$handle = curl_init($query);
|
||||||
|
curl_setopt($handle, CURLOPT_USERAGENT, "LAM Auth Yubico");
|
||||||
|
curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
if (!$this->httpsVerify) {
|
||||||
|
curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0);
|
||||||
|
curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
|
||||||
|
}
|
||||||
|
curl_setopt($handle, CURLOPT_FAILONERROR, true);
|
||||||
|
/*
|
||||||
|
* If timeout is set, we better apply it here as well
|
||||||
|
* in case the validation server fails to follow it.
|
||||||
|
*/
|
||||||
|
if ($timeout) {
|
||||||
|
curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
|
||||||
|
}
|
||||||
|
// TODO single curl call
|
||||||
|
curl_multi_add_handle($mh, $handle);
|
||||||
|
|
||||||
|
$ch[(int) $handle] = $handle;
|
||||||
|
|
||||||
|
/* Execute and read request. */
|
||||||
|
$this->response = null;
|
||||||
|
$replay = False;
|
||||||
|
$valid = False;
|
||||||
|
do {
|
||||||
|
/* Let curl do its work. */
|
||||||
|
while (($mrc = curl_multi_exec($mh, $active)) == CURLM_CALL_MULTI_PERFORM);
|
||||||
|
|
||||||
|
while ($info = curl_multi_info_read($mh)) {
|
||||||
|
if ($info['result'] == CURLE_OK) {
|
||||||
|
|
||||||
|
/* We have a complete response from one server. */
|
||||||
|
|
||||||
|
$str = curl_multi_getcontent($info['handle']);
|
||||||
|
logNewMessage(LOG_DEBUG, 'Yubico answer: ' . $str);
|
||||||
|
$cinfo = curl_getinfo($info['handle']);
|
||||||
|
|
||||||
|
if (preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) {
|
||||||
|
$status = $out[1];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There are 3 cases.
|
||||||
|
*
|
||||||
|
* 1. OTP or Nonce values doesn't match - ignore
|
||||||
|
* response.
|
||||||
|
*
|
||||||
|
* 2. We have a HMAC key. If signature is invalid -
|
||||||
|
* ignore response. Return if status=OK/REPLAYED_OTP/BAD_OTP.
|
||||||
|
*
|
||||||
|
* 3. Return if status=OK or status=REPLAYED_OTP.
|
||||||
|
*/
|
||||||
|
if (!preg_match("/otp=" . $params['otp'] . "/", $str) || !preg_match("/nonce=" . $params['nonce'] . "/", $str)) {
|
||||||
|
/* Case 1. Ignore response. */
|
||||||
|
}
|
||||||
|
elseif ($this->clientKey != "") {
|
||||||
|
/* Case 2. Verify signature first */
|
||||||
|
$rows = explode("\r\n", trim($str));
|
||||||
|
$response = array();
|
||||||
|
foreach ($rows as $key => $val) {
|
||||||
|
/*
|
||||||
|
* = is also used in BASE64 encoding so we only replace the first = by # which is not
|
||||||
|
* used in BASE64
|
||||||
|
*/
|
||||||
|
$val = preg_replace('/=/', '#', $val, 1);
|
||||||
|
$row = explode("#", $val);
|
||||||
|
$response[$row[0]] = $row[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = array(
|
||||||
|
'nonce',
|
||||||
|
'otp',
|
||||||
|
'sessioncounter',
|
||||||
|
'sessionuse',
|
||||||
|
'sl',
|
||||||
|
'status',
|
||||||
|
't',
|
||||||
|
'timeout',
|
||||||
|
'timestamp'
|
||||||
|
);
|
||||||
|
sort($parameters);
|
||||||
|
$check = Null;
|
||||||
|
foreach ($parameters as $param) {
|
||||||
|
if (array_key_exists($param, $response)) {
|
||||||
|
if ($check) $check = $check . '&';
|
||||||
|
$check = $check . $param . '=' . $response[$param];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$checksignature = base64_encode(hash_hmac('sha1', utf8_encode($check), $this->clientKey, true));
|
||||||
|
|
||||||
|
if ($response['h'] == $checksignature) {
|
||||||
|
if ($status == 'REPLAYED_OTP') {
|
||||||
|
$this->response = $str;
|
||||||
|
$replay = True;
|
||||||
|
}
|
||||||
|
if ($status == 'OK') {
|
||||||
|
$this->response = $str;
|
||||||
|
$valid = True;
|
||||||
|
}
|
||||||
|
// TODO status BAD_OTP
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO throw invalid signature exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Case 3. We check the status directly */
|
||||||
|
if ($status == 'REPLAYED_OTP') {
|
||||||
|
$this->response = $str;
|
||||||
|
$replay = True;
|
||||||
|
}
|
||||||
|
if ($status == 'OK') {
|
||||||
|
$this->response = $str;
|
||||||
|
$valid = True;
|
||||||
|
}
|
||||||
|
// TODO status BAD_OTP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($valid || $replay) {
|
||||||
|
/* We have status=OK or status=REPLAYED_OTP, return. */
|
||||||
|
foreach ($ch as $h) {
|
||||||
|
curl_multi_remove_handle($mh, $h);
|
||||||
|
curl_close($h);
|
||||||
|
}
|
||||||
|
curl_multi_close($mh);
|
||||||
|
if ($replay) {
|
||||||
|
throw new LAMException(_('Error'), 'OTP replay detected.');
|
||||||
|
}
|
||||||
|
if ($valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_multi_remove_handle($mh, $info['handle']);
|
||||||
|
curl_close($info['handle']);
|
||||||
|
unset($ch[(int) $info['handle']]);
|
||||||
|
}
|
||||||
|
curl_multi_select($mh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ($active);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Typically this is only reached
|
||||||
|
* when the timeout is reached and there is no
|
||||||
|
* OK/REPLAYED_REQUEST answer (think firewall).
|
||||||
|
*/
|
||||||
|
|
||||||
|
foreach ($ch as $h) {
|
||||||
|
curl_multi_remove_handle($mh, $h);
|
||||||
|
curl_close($h);
|
||||||
|
}
|
||||||
|
curl_multi_close($mh);
|
||||||
|
|
||||||
|
throw new LAMException(_('Error'), 'Invalid answer: ' . print_r($this->response, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
?>
|
|
@ -591,6 +591,8 @@ class LAMConfig {
|
||||||
|
|
||||||
private $twoFactorAuthentication = TwoFactorProviderService::TWO_FACTOR_NONE;
|
private $twoFactorAuthentication = TwoFactorProviderService::TWO_FACTOR_NONE;
|
||||||
private $twoFactorAuthenticationURL = 'https://localhost';
|
private $twoFactorAuthenticationURL = 'https://localhost';
|
||||||
|
private $twoFactorAuthenticationClientId = null;
|
||||||
|
private $twoFactorAuthenticationSecretKey = null;
|
||||||
private $twoFactorAuthenticationInsecure = false;
|
private $twoFactorAuthenticationInsecure = false;
|
||||||
private $twoFactorAuthenticationLabel = null;
|
private $twoFactorAuthenticationLabel = null;
|
||||||
private $twoFactorAuthenticationOptional = false;
|
private $twoFactorAuthenticationOptional = false;
|
||||||
|
@ -607,7 +609,7 @@ class LAMConfig {
|
||||||
'pwdResetAllowScreenPassword', 'pwdResetForcePasswordChange', 'pwdResetDefaultPasswordOutput',
|
'pwdResetAllowScreenPassword', 'pwdResetForcePasswordChange', 'pwdResetDefaultPasswordOutput',
|
||||||
'scriptUserName', 'scriptSSHKey', 'scriptSSHKeyPassword', 'twoFactorAuthentication', 'twoFactorAuthenticationURL',
|
'scriptUserName', 'scriptSSHKey', 'scriptSSHKeyPassword', 'twoFactorAuthentication', 'twoFactorAuthenticationURL',
|
||||||
'twoFactorAuthenticationInsecure', 'twoFactorAuthenticationLabel', 'twoFactorAuthenticationOptional',
|
'twoFactorAuthenticationInsecure', 'twoFactorAuthenticationLabel', 'twoFactorAuthenticationOptional',
|
||||||
'twoFactorAuthenticationCaption', 'referentialIntegrityOverlay'
|
'twoFactorAuthenticationCaption', 'twoFactorAuthenticationClientId', 'twoFactorAuthenticationSecretKey', 'referentialIntegrityOverlay'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -867,6 +869,8 @@ class LAMConfig {
|
||||||
if (!in_array("pwdResetDefaultPasswordOutput", $saved)) array_push($file_array, "\n" . "pwdResetDefaultPasswordOutput: " . $this->pwdResetDefaultPasswordOutput . "\n");
|
if (!in_array("pwdResetDefaultPasswordOutput", $saved)) array_push($file_array, "\n" . "pwdResetDefaultPasswordOutput: " . $this->pwdResetDefaultPasswordOutput . "\n");
|
||||||
if (!in_array("twoFactorAuthentication", $saved)) array_push($file_array, "\n" . "twoFactorAuthentication: " . $this->twoFactorAuthentication . "\n");
|
if (!in_array("twoFactorAuthentication", $saved)) array_push($file_array, "\n" . "twoFactorAuthentication: " . $this->twoFactorAuthentication . "\n");
|
||||||
if (!in_array("twoFactorAuthenticationURL", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationURL: " . $this->twoFactorAuthenticationURL . "\n");
|
if (!in_array("twoFactorAuthenticationURL", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationURL: " . $this->twoFactorAuthenticationURL . "\n");
|
||||||
|
if (!in_array("twoFactorAuthenticationClientId", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationClientId: " . $this->twoFactorAuthenticationClientId . "\n");
|
||||||
|
if (!in_array("twoFactorAuthenticationSecretKey", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationSecretKey: " . $this->twoFactorAuthenticationSecretKey . "\n");
|
||||||
if (!in_array("twoFactorAuthenticationInsecure", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationInsecure: " . $this->twoFactorAuthenticationInsecure . "\n");
|
if (!in_array("twoFactorAuthenticationInsecure", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationInsecure: " . $this->twoFactorAuthenticationInsecure . "\n");
|
||||||
if (!in_array("twoFactorAuthenticationLabel", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationLabel: " . $this->twoFactorAuthenticationLabel . "\n");
|
if (!in_array("twoFactorAuthenticationLabel", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationLabel: " . $this->twoFactorAuthenticationLabel . "\n");
|
||||||
if (!in_array("twoFactorAuthenticationOptional", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationOptional: " . $this->twoFactorAuthenticationOptional . "\n");
|
if (!in_array("twoFactorAuthenticationOptional", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationOptional: " . $this->twoFactorAuthenticationOptional . "\n");
|
||||||
|
@ -2151,7 +2155,7 @@ class LAMConfig {
|
||||||
/**
|
/**
|
||||||
* Returns the authentication URL.
|
* Returns the authentication URL.
|
||||||
*
|
*
|
||||||
* @return string $twoFactorAuthenticationURL authentication URL
|
* @return string authentication URL
|
||||||
*/
|
*/
|
||||||
public function getTwoFactorAuthenticationURL() {
|
public function getTwoFactorAuthenticationURL() {
|
||||||
return $this->twoFactorAuthenticationURL;
|
return $this->twoFactorAuthenticationURL;
|
||||||
|
@ -2166,10 +2170,46 @@ class LAMConfig {
|
||||||
$this->twoFactorAuthenticationURL = $twoFactorAuthenticationURL;
|
$this->twoFactorAuthenticationURL = $twoFactorAuthenticationURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the client id.
|
||||||
|
*
|
||||||
|
* @param string $clientId client id
|
||||||
|
*/
|
||||||
|
public function setTwoFactorAuthenticationClientId($clientId) {
|
||||||
|
$this->twoFactorAuthenticationClientId = $clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client id.
|
||||||
|
*
|
||||||
|
* @return string client id
|
||||||
|
*/
|
||||||
|
public function getTwoFactorAuthenticationClientId() {
|
||||||
|
return $this->twoFactorAuthenticationClientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the secret key.
|
||||||
|
*
|
||||||
|
* @param string $secretKey secret key
|
||||||
|
*/
|
||||||
|
public function setTwoFactorAuthenticationSecretKey($secretKey) {
|
||||||
|
$this->twoFactorAuthenticationSecretKey = $secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the secret key.
|
||||||
|
*
|
||||||
|
* @return string secret key
|
||||||
|
*/
|
||||||
|
public function getTwoFactorAuthenticationSecretKey() {
|
||||||
|
return $this->twoFactorAuthenticationSecretKey;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if SSL certificate verification is turned off.
|
* Returns if SSL certificate verification is turned off.
|
||||||
*
|
*
|
||||||
* @return bool $twoFactorAuthenticationInsecure SSL certificate verification is turned off
|
* @return bool SSL certificate verification is turned off
|
||||||
*/
|
*/
|
||||||
public function getTwoFactorAuthenticationInsecure() {
|
public function getTwoFactorAuthenticationInsecure() {
|
||||||
return $this->twoFactorAuthenticationInsecure;
|
return $this->twoFactorAuthenticationInsecure;
|
||||||
|
@ -2187,7 +2227,7 @@ class LAMConfig {
|
||||||
/**
|
/**
|
||||||
* Returns the authentication label.
|
* Returns the authentication label.
|
||||||
*
|
*
|
||||||
* @return string $twoFactorAuthenticationLabel authentication label
|
* @return string authentication label
|
||||||
*/
|
*/
|
||||||
public function getTwoFactorAuthenticationLabel() {
|
public function getTwoFactorAuthenticationLabel() {
|
||||||
return $this->twoFactorAuthenticationLabel;
|
return $this->twoFactorAuthenticationLabel;
|
||||||
|
@ -2205,7 +2245,7 @@ class LAMConfig {
|
||||||
/**
|
/**
|
||||||
* Returns if 2nd factor is optional.
|
* Returns if 2nd factor is optional.
|
||||||
*
|
*
|
||||||
* @return bool $twoFactorAuthenticationOptional 2nd factor is optional
|
* @return bool 2nd factor is optional
|
||||||
*/
|
*/
|
||||||
public function getTwoFactorAuthenticationOptional() {
|
public function getTwoFactorAuthenticationOptional() {
|
||||||
return $this->twoFactorAuthenticationOptional;
|
return $this->twoFactorAuthenticationOptional;
|
||||||
|
@ -2223,7 +2263,7 @@ class LAMConfig {
|
||||||
/**
|
/**
|
||||||
* Returns the caption HTML.
|
* Returns the caption HTML.
|
||||||
*
|
*
|
||||||
* @return string $twoFactorAuthenticationCaption caption HTML
|
* @return string caption HTML
|
||||||
*/
|
*/
|
||||||
public function getTwoFactorAuthenticationCaption() {
|
public function getTwoFactorAuthenticationCaption() {
|
||||||
return $this->twoFactorAuthenticationCaption;
|
return $this->twoFactorAuthenticationCaption;
|
||||||
|
|
|
@ -22,7 +22,7 @@ use \htmlGroup;
|
||||||
/*
|
/*
|
||||||
|
|
||||||
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
|
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
|
||||||
Copyright (C) 2003 - 2017 Roland Gruber
|
Copyright (C) 2003 - 2018 Roland Gruber
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -460,19 +460,29 @@ if (extension_loaded('curl')) {
|
||||||
$twoFactorOptions = array(
|
$twoFactorOptions = array(
|
||||||
_('None') => TwoFactorProviderService::TWO_FACTOR_NONE,
|
_('None') => TwoFactorProviderService::TWO_FACTOR_NONE,
|
||||||
'privacyIDEA' => TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA,
|
'privacyIDEA' => TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA,
|
||||||
|
'YubiKey' => TwoFactorProviderService::TWO_FACTOR_YUBICO,
|
||||||
);
|
);
|
||||||
$twoFactorSelect = new htmlResponsiveSelect('twoFactor', $twoFactorOptions, array($conf->getTwoFactorAuthentication()), _('Provider'), '514');
|
$twoFactorSelect = new htmlResponsiveSelect('twoFactor', $twoFactorOptions, array($conf->getTwoFactorAuthentication()), _('Provider'), '514');
|
||||||
$twoFactorSelect->setHasDescriptiveElements(true);
|
$twoFactorSelect->setHasDescriptiveElements(true);
|
||||||
$twoFactorSelect->setTableRowsToHide(array(
|
$twoFactorSelect->setTableRowsToHide(array(
|
||||||
TwoFactorProviderService::TWO_FACTOR_NONE => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', 'twoFactorOptional', 'twoFactorCaption')
|
TwoFactorProviderService::TWO_FACTOR_NONE => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel',
|
||||||
|
'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'),
|
||||||
|
TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorClientId', 'twoFactorSecretKey')
|
||||||
));
|
));
|
||||||
$twoFactorSelect->setTableRowsToShow(array(
|
$twoFactorSelect->setTableRowsToShow(array(
|
||||||
TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', 'twoFactorOptional', 'twoFactorCaption')
|
TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel',
|
||||||
|
'twoFactorOptional', 'twoFactorCaption'),
|
||||||
|
TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel',
|
||||||
|
'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'),
|
||||||
));
|
));
|
||||||
$row->add($twoFactorSelect, 12);
|
$row->add($twoFactorSelect, 12);
|
||||||
$twoFactorUrl = new htmlResponsiveInputField(_("Base URL"), 'twoFactorURL', $conf->getTwoFactorAuthenticationURL(), '515');
|
$twoFactorUrl = new htmlResponsiveInputField(_("Base URL"), 'twoFactorURL', $conf->getTwoFactorAuthenticationURL(), '515');
|
||||||
$twoFactorUrl->setRequired(true);
|
$twoFactorUrl->setRequired(true);
|
||||||
$row->add($twoFactorUrl, 12);
|
$row->add($twoFactorUrl, 12);
|
||||||
|
$twoFactorClientId = new htmlResponsiveInputField(_("Client id"), 'twoFactorClientId', $conf->getTwoFactorAuthenticationClientId(), '524');
|
||||||
|
$row->add($twoFactorClientId, 12);
|
||||||
|
$twoFactorSecretKey = new htmlResponsiveInputField(_("Secret key"), 'twoFactorSecretKey', $conf->getTwoFactorAuthenticationSecretKey(), '525');
|
||||||
|
$row->add($twoFactorSecretKey, 12);
|
||||||
$twoFactorLabel = new htmlResponsiveInputField(_("Label"), 'twoFactorLabel', $conf->getTwoFactorAuthenticationLabel(), '517');
|
$twoFactorLabel = new htmlResponsiveInputField(_("Label"), 'twoFactorLabel', $conf->getTwoFactorAuthenticationLabel(), '517');
|
||||||
$row->add($twoFactorLabel, 12);
|
$row->add($twoFactorLabel, 12);
|
||||||
$row->add(new htmlResponsiveInputCheckbox('twoFactorOptional', $conf->getTwoFactorAuthenticationOptional(), _('Optional'), '519'), 12);
|
$row->add(new htmlResponsiveInputCheckbox('twoFactorOptional', $conf->getTwoFactorAuthenticationOptional(), _('Optional'), '519'), 12);
|
||||||
|
@ -677,6 +687,8 @@ function checkInput() {
|
||||||
if (extension_loaded('curl')) {
|
if (extension_loaded('curl')) {
|
||||||
$conf->setTwoFactorAuthentication($_POST['twoFactor']);
|
$conf->setTwoFactorAuthentication($_POST['twoFactor']);
|
||||||
$conf->setTwoFactorAuthenticationURL($_POST['twoFactorURL']);
|
$conf->setTwoFactorAuthenticationURL($_POST['twoFactorURL']);
|
||||||
|
$conf->setTwoFactorAuthenticationClientId($_POST['twoFactorClientId']);
|
||||||
|
$conf->setTwoFactorAuthenticationSecretKey($_POST['twoFactorSecretKey']);
|
||||||
$conf->setTwoFactorAuthenticationInsecure(isset($_POST['twoFactorInsecure']) && ($_POST['twoFactorInsecure'] == 'on'));
|
$conf->setTwoFactorAuthenticationInsecure(isset($_POST['twoFactorInsecure']) && ($_POST['twoFactorInsecure'] == 'on'));
|
||||||
$conf->setTwoFactorAuthenticationLabel($_POST['twoFactorLabel']);
|
$conf->setTwoFactorAuthenticationLabel($_POST['twoFactorLabel']);
|
||||||
$conf->setTwoFactorAuthenticationOptional(isset($_POST['twoFactorOptional']) && ($_POST['twoFactorOptional'] == 'on'));
|
$conf->setTwoFactorAuthenticationOptional(isset($_POST['twoFactorOptional']) && ($_POST['twoFactorOptional'] == 'on'));
|
||||||
|
|
|
@ -499,6 +499,94 @@ class LAMConfigTest extends PHPUnit_Framework_TestCase {
|
||||||
$this->assertEquals($val, $this->lAMConfig->getHttpAuthentication());
|
$this->assertEquals($val, $this->lAMConfig->getHttpAuthentication());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthentication() and LAMConfig->setTwoFactorAuthentication()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthentication() {
|
||||||
|
$val = '2fid';
|
||||||
|
$this->lAMConfig->setTwoFactorAuthentication($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthentication());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthentication());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationURL() and LAMConfig->setTwoFactorAuthenticationURL()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationURL() {
|
||||||
|
$val = 'http://example.com';
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationURL($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationURL());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationClientId() and LAMConfig->setTwoFactorAuthenticationClientId()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationClientId() {
|
||||||
|
$val = '1234';
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationClientId($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationClientId());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationClientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationSecretKey() and LAMConfig->setTwoFactorAuthenticationSecretKey()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationSecretKey() {
|
||||||
|
$val = '3333key';
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationSecretKey($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationSecretKey());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationSecretKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationInsecure() and LAMConfig->setTwoFactorAuthenticationInsecure()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationInsecure() {
|
||||||
|
$val = true;
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationInsecure($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationInsecure());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationInsecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationLabel() and LAMConfig->setTwoFactorAuthenticationLabel()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationLabel() {
|
||||||
|
$val = '2falabel';
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationLabel($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationLabel());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationOptional() and LAMConfig->setTwoFactorAuthenticationOptional()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationOptional() {
|
||||||
|
$val = true;
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationOptional($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationOptional());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationOptional());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests LAMConfig->getTwoFactorAuthenticationCaption() and LAMConfig->setTwoFactorAuthenticationCaption()
|
||||||
|
*/
|
||||||
|
public function testTwoFactorAuthenticationCaption() {
|
||||||
|
$val = '2facaption';
|
||||||
|
$this->lAMConfig->setTwoFactorAuthenticationCaption($val);
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationCaption());
|
||||||
|
$this->doSave();
|
||||||
|
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationCaption());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests LAMConfig->getLamProMailFrom() and LAMConfig->setLamProMailFrom()
|
* Tests LAMConfig->getLamProMailFrom() and LAMConfig->setLamProMailFrom()
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue