Compare commits

...

4 Commits

13 changed files with 317 additions and 30 deletions

View File

@@ -0,0 +1,23 @@
<?php
/**
* Login page extensibility
*
* @api
* @package UIExtensibilityAPI
* @since 3.x.x
*/
interface iTokenLoginUIExtension
{
/**
* @return array
* @api
*/
public function GetTokenInfo() : array;
/**
* @return array
* @api
*/
public function GetUserLogin(array $aTokenInfo) : string;
}

View File

@@ -9,7 +9,7 @@ use Combodo\iTop\Application\Helper\Session;
* @license http://opensource.org/licenses/AGPL-3.0
*/
class LoginBasic extends AbstractLoginFSMExtension
class LoginBasic extends AbstractLoginFSMExtension implements iTokenLoginUIExtension
{
/**
* Return the list of supported login modes for this plugin
@@ -143,4 +143,21 @@ class LoginBasic extends AbstractLoginFSMExtension
}
return array($sAuthUser, $sAuthPwd);
}
public function GetTokenInfo(): array
{
return $this->GetAuthUserAndPassword();
}
public function GetUserLogin(array $aTokenInfo): string
{
$sLogin = $aTokenInfo[0];
$sLoginMode = 'basic';
if (UserRights::CheckCredentials($sLogin, $aTokenInfo[1], $sLoginMode, 'internal'))
{
return $sLogin;
}
throw new Exception("Cannot CheckCredentials user login ($sLogin) with ($sLoginMode) mode");
}
}

View File

@@ -12,7 +12,7 @@ use Combodo\iTop\Application\Helper\Session;
*
* @since 2.7.0
*/
class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension, iTokenLoginUIExtension
{
private $bForceFormOnError = false;
@@ -32,8 +32,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
protected function OnReadCredentials(&$iErrorCode)
{
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') {
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd))
{
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
@@ -68,8 +67,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
{
if (Session::Get('login_mode') == 'form')
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
@@ -153,4 +151,23 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
return $oLoginContext;
}
public function GetTokenInfo(): array
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
return [$sAuthUser, $sAuthPwd];
}
public function GetUserLogin(array $aTokenInfo): string
{
$sLogin = $aTokenInfo[0];
$sLoginMode = 'form';
if (UserRights::CheckCredentials($sLogin, $aTokenInfo[1], $sLoginMode, 'internal'))
{
return $sLogin;
}
throw new Exception("Cannot CheckCredentials user login ($sLogin) with ($sLoginMode) mode");
}
}

View File

@@ -9,7 +9,7 @@ use Combodo\iTop\Application\Helper\Session;
* @license http://opensource.org/licenses/AGPL-3.0
*/
class LoginURL extends AbstractLoginFSMExtension
class LoginURL extends AbstractLoginFSMExtension implements iTokenLoginUIExtension
{
/**
* @var bool
@@ -30,9 +30,7 @@ class LoginURL extends AbstractLoginFSMExtension
{
if (!Session::IsSet('login_mode') && !$this->bErrorOccurred)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!empty($sAuthUser) && !empty($sAuthPwd))
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
{
Session::Set('login_mode', 'url');
}
@@ -53,8 +51,7 @@ class LoginURL extends AbstractLoginFSMExtension
{
if (Session::Get('login_mode') == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
list($sAuthUser, $sAuthPwd) = $this->GetTokenInfo();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
@@ -92,4 +89,23 @@ class LoginURL extends AbstractLoginFSMExtension
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
public function GetTokenInfo(): array
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
return [$sAuthUser, $sAuthPwd];
}
public function GetUserLogin(array $aTokenInfo): string
{
$sLogin = $aTokenInfo[0];
$sLoginMode = 'url';
if (UserRights::CheckCredentials($sLogin, $aTokenInfo[1], $sLoginMode, 'internal'))
{
return $sLogin;
}
throw new Exception("Cannot CheckCredentials user login ($sLogin) with ($sLoginMode) mode");
}
}

View File

@@ -581,6 +581,26 @@ class LoginWebPage extends NiceWebPage
return $aPlugins;
}
public static function GetCurrentLoginPlugin(string $sCurrentLoginMode) : iLoginExtension
{
/** @var iLoginExtension $oLoginExtensionInstance */
foreach (MetaModel::EnumPlugins('iLoginFSMExtension') as $oLoginExtensionInstance)
{
$aLoginModes = $oLoginExtensionInstance->ListSupportedLoginModes();
$aLoginModes = (is_array($aLoginModes) ? $aLoginModes : array());
foreach ($aLoginModes as $sLoginMode)
{
// Keep only the plugins for the current login mode + before + after
if ($sLoginMode == $sCurrentLoginMode)
{
return $oLoginExtensionInstance;
}
}
}
throw new \Exception("should not happen");
}
/**
* Advance Login Finite State Machine to the next step
*

View File

@@ -14,7 +14,10 @@ if (PHP_VERSION_ID < 50600) {
echo $err;
}
}
throw new RuntimeException($err);
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';

View File

@@ -3169,6 +3169,7 @@ return array(
'iRestServiceProvider' => $baseDir . '/application/applicationextension/rest/iRestServiceProvider.php',
'iScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'iSelfRegister' => $baseDir . '/core/userrights.class.inc.php',
'iTokenLoginUIExtension' => $baseDir . '/application/applicationextension/login/iTokenLoginUIExtension.php',
'iTopConfigParser' => $baseDir . '/core/iTopConfigParser.php',
'iTopMutex' => $baseDir . '/core/mutex.class.inc.php',
'iTopOwnershipLock' => $baseDir . '/core/ownershiplock.class.inc.php',

View File

@@ -56,7 +56,7 @@ return array(
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),

View File

@@ -314,8 +314,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
),
'League\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/league/oauth2-google/src',
1 => __DIR__ . '/..' . '/league/oauth2-client/src',
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
1 => __DIR__ . '/..' . '/league/oauth2-google/src',
),
'GuzzleHttp\\Psr7\\' =>
array (
@@ -3524,6 +3524,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'iRestServiceProvider' => __DIR__ . '/../..' . '/application/applicationextension/rest/iRestServiceProvider.php',
'iScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'iSelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
'iTokenLoginUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iTokenLoginUIExtension.php',
'iTopConfigParser' => __DIR__ . '/../..' . '/core/iTopConfigParser.php',
'iTopMutex' => __DIR__ . '/../..' . '/core/mutex.class.inc.php',
'iTopOwnershipLock' => __DIR__ . '/../..' . '/core/ownershiplock.class.inc.php',

View File

@@ -36,7 +36,8 @@ if ($issues) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@@ -518,22 +518,37 @@ try
$bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
$bDebug = utils::ReadParam('debug', false, true /* Allow CLI */);
if ($bIsModeCLI)
{
if ($bIsModeCLI) {
// Next steps:
// specific arguments: 'csv file'
//
$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data');
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data');
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd))
$sTokenInfo = utils::ReadParam('auth_token_info', null, true, 'raw_data');
$sLoginMode = utils::ReadParam('login_mode', null, true, 'raw_data');
if (is_null($sLoginMode) || is_null($sTokenInfo)) {
$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data');
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data');
if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) {
UserRights::Login($sAuthUser); // Login & set the user's language
} else {
$oP->p("Access wrong credentials ('$sAuthUser')");
$oP->output();
exit(EXIT_CODE_ERROR);
}
} else
{
UserRights::Login($sAuthUser); // Login & set the user's language
}
else
{
$oP->p("Access wrong credentials ('$sAuthUser')");
$oP->output();
exit(EXIT_CODE_ERROR);
$oLoginFSMExtensionInstance = LoginWebPage::GetCurrentLoginPlugin($sLoginMode);
if ($oLoginFSMExtensionInstance instanceof iTokenLoginUIExtension){
$aTokenInfo = json_decode(base64_decode($sTokenInfo), true);
/** @var iTokenLoginUIExtension $oLoginFSMExtensionInstance */
$sAuthUser = $oLoginFSMExtensionInstance->GetUserLogin($aTokenInfo);
UserRights::Login($sAuthUser); // Login & set the user's language
} else {
$oP->p("cannot call cron asynchronously via current login mode $sLoginMode");
$oP->output();
exit(EXIT_CODE_ERROR);
}
}
}
else

View File

@@ -0,0 +1,73 @@
<?php
require_once(__DIR__.'/../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
const ERROR_ALREADY_RUNNING = "error_already_running";
const RUNNING = "running";
const STOPPED = "stopped";
const ERROR = "error";
try {
$oCtx = new ContextTag(ContextTag::TAG_CRON);
LoginWebPage::ResetSession(true);
$iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN);
if ($iRet != LoginWebPage::EXIT_CODE_OK){
throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED);
}
$sLogFilename = ReadParam("cron_log_file", "cron.log");
$sStatus = STOPPED;
$sMsg = "";
$sLogFile = APPROOT."log/$sLogFilename";
if (is_file($sLogFile)) {
$sContent = exec("tail -n 1 $sLogFile");
if (0 === strpos($sContent, 'Exiting: ')) {
exec("tail -n 2 $sLogFile", $aContent);
//var_dump($aContent);
$sContent = implode("\n", $aContent);
if (false !== strpos($sContent, 'Already running')) {
$sStatus = ERROR_ALREADY_RUNNING;
} else if (preg_match('/ERROR: (.*)\\n/', $sContent, $aMatches)) {
$sMsg = "$aMatches[1]";
$sStatus = ERROR;
} else {
$sMsg = "$sContent";
$sStatus = STOPPED;
}
} else {
$sStatus = RUNNING;
}
} else {
$sMsg = "missing $sLogFile";
$sStatus = ERROR;
}
http_response_code(200);
$oP = new JsonPage();
$oP->add_header('Access-Control-Allow-Origin: *');
$oP->SetData(["status" => $sStatus, 'message' => $sMsg]);
$oP->SetOutputDataOnly(true);
$oP->Output();
}
catch (Exception $e) {
\IssueLog::Error("Cannot cron status", null, ['msg' => $e->getMessage(), 'stack' => $e->getTraceAsString()]);
http_response_code(500);
$oP = new JsonPage();
$oP->add_header('Access-Control-Allow-Origin: *');
$oP->SetData(["message" => $e->getMessage()]);
$oP->SetOutputDataOnly(true);
$oP->Output();
}
function ReadParam($sParam, $sDefaultValue = null, $sSanitizationFilter = utils::ENUM_SANITIZATION_FILTER_RAW_DATA)
{
$sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter);
if (is_null($sValue)) {
$sValue = utils::ReadPostedParam($sParam, $sDefaultValue, $sSanitizationFilter);
}
return trim($sValue);
}

View File

@@ -0,0 +1,100 @@
<?php
use Hybridauth\Storage\Session;
require_once(__DIR__.'/../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
try {
$oCtx = new ContextTag(ContextTag::TAG_CRON);
LoginWebPage::ResetSession(true);
$iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN);
if ($iRet != LoginWebPage::EXIT_CODE_OK){
throw new Exception("Unknown authentication error (retCode=$iRet)", RestResult::UNAUTHORIZED);
}
$sCurrentLoginMode = \Combodo\iTop\Application\Helper\Session::Get('login_mode', '');
$oLoginFSMExtensionInstance = LoginWebPage::GetCurrentLoginPlugin($sCurrentLoginMode);
if ($oLoginFSMExtensionInstance instanceof iTokenLoginUIExtension){
/** @var iTokenLoginUIExtension $oLoginFSMExtensionInstance */
$aTokenInfo = $oLoginFSMExtensionInstance->GetTokenInfo();
$sTokenInfo = base64_encode(json_encode($aTokenInfo));
} else {
throw new \Exception("cannot call cron asynchronously via current login mode $sCurrentLoginMode");
}
$sLogFilename = ReadParam("cron_log_file", "cron.log");
$sLogFile = APPROOT."log/$sLogFilename";
$sCliParams = ReadParam("cron_cli_parameters");
$bAsynchronous = true;
if (is_null($sCliParams)) {
$sCliParams = "--help";
$bAsynchronous = false;
} else {
$sCliParams = trim(base64_decode($sCliParams, true));
$sCliParams = "--auth_token_info=$sTokenInfo ".$sCliParams;
$sCliParams = "--login_mode=$sCurrentLoginMode ".$sCliParams;
if (false !== strpos($sCliParams, '--status_only=1')) {
$bAsynchronous = false;
}
}
touch($sLogFile);
$sPHPExec = trim(\MetaModel::GetConfig()->Get('php_path'));
if ($bAsynchronous) {
$sCli = sprintf("$sPHPExec %s/cron.php $sCliParams 2>&1 >>$sLogFile &", __DIR__);
file_put_contents($sLogFile, $sCli);
$process = popen($sCli, 'r');
} else {
$sCli = sprintf("\n $sPHPExec %s/cron.php $sCliParams", __DIR__);
$fp = fopen($sLogFile, 'a+');
fwrite($fp, $sCli);
$aDescriptorSpec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
];
$rProcess = proc_open($sCli, $aDescriptorSpec, $aPipes, __DIR__, null);
$sStdOut = stream_get_contents($aPipes[1]);
fclose($aPipes[1]);
$iCode = proc_close($rProcess);
fwrite($fp, $sStdOut);
fwrite($fp, "Exiting: ".time().' ('.date('Y-m-d H:i:s').')');
fclose($fp);
}
http_response_code(200);
$oP = new JsonPage();
$oP->add_header('Access-Control-Allow-Origin: *');
$oP->SetData(["message" => "OK"]);
$oP->SetOutputDataOnly(true);
$oP->Output();
}
catch (Exception $e) {
\IssueLog::Error("Cannot run cron", null, ['msg' => $e->getMessage(), 'stack' => $e->getTraceAsString()]);
http_response_code(500);
$oP = new JsonPage();
$oP->add_header('Access-Control-Allow-Origin: *');
$oP->SetData(["message" => $e->getMessage()]);
$oP->SetOutputDataOnly(true);
$oP->Output();
}
function ReadParam($sParam, $sDefaultValue = null, $sSanitizationFilter = utils::ENUM_SANITIZATION_FILTER_RAW_DATA)
{
$sValue = utils::ReadParam($sParam, null, true, $sSanitizationFilter);
if (is_null($sValue)) {
$sValue = utils::ReadPostedParam($sParam, $sDefaultValue, $sSanitizationFilter);
}
return trim($sValue);
}