<?php

namespace App\Config;

class PasswordSecurity extends XmlBasicAbstract
{

	/**
	 * Stores the configuration file (relative to the "files/config/" dir). For config files stored
	 * inside subdirectories, the subdirectory is included (Ex: services/servicesList.xml).
	 * @var String
	 */
	protected $fileName=CONFIG_FILE_PASSWORDSECURITY;


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlBasicAbstract::getAll_rawData_extractDataFromFile()
	 */
	protected function getAll_rawData_extractDataFromFile(\SimpleXMLElement $xml)
	{

		$return = [
			'minLength' => 			(Int)$xml->minLength,
			'validity' => 			(Int)$xml->validity,
			'historyLimit' => 		(Int)$xml->historyLimit,
			'digitsCount' => 		(Int)$xml->content->digits,
			'upperLettersCount' => 	(Int)$xml->content->upperLetters,
			'specialCharsCount' => 	(Int)$xml->content->specialChars,
			'sessionLifetime' =>	(Int)$xml->sessionLifetime?:1800, // 30 minutes
		];

		if ($xml->loginOptions) {
			$return['loginWithPassword'] = (int)$xml->loginOptions->password['active'];
		} else { // fallback to true, if the XML does not contain anything
			$return['loginWithPassword'] = true;
		}
		if ($xml->loginOptions) {
			$return['loginWithAzure'] = (int)$xml->loginOptions->azure['active'];
		} else { // fallback to true, if the XML does not contain anything
			$return['loginWithAzure'] = true;
		}

		return $return;
	}

	/**
	 * Sets the default output in case the XML file is missing.
	 * @return Array
	 */
	protected function getAll_rawData_fileNotExists()
	{
		return [
			'sessionLifetime' => 1800, // 30 minutes
			'loginWithPassword' => true,
			'loginWithAzure' => true,
		];
	}

	/**
	 * (non-PHPdoc)
	 * @see \App\Config\BaseAbstract::save_check()
	 */
	protected function save_check()
	{

		$userCfg = \Entity\User::loadCFG();
		$CFG = \Engine\Loader_Cfg::getInstance()->getGlobal();

		// check password length
		if (!is_numeric($this->itemsList['minLength'])) {
			$this->addErrorMessage('Minimum password length is not valid.');
		}

		$minLength = $userCfg['password']['validation']['minLength'];
		if ($CFG['pciDss']['compliant']) { // PCI-DSS 4 forces a password of minimum 12 chars
			$minLength = max($userCfg['password']['validation']['minLength'], 12);
		}
		if ($this->itemsList['minLength']<$minLength) {
			$this->addErrorMessage(sprintf('Password minimum length cannot be less than %s chars.', $minLength));
		}
		if ($this->itemsList['minLength']>$userCfg['password']['validation']['maxLength']) {
			$this->addErrorMessage(sprintf('Password minimum length cannot be greater than %s chars.', $userCfg['password']['validation']['maxLength']));
		}

		// check validity
		if (!is_numeric($this->itemsList['validity']) or $this->itemsList['validity']<0) {
			$this->addErrorMessage('Password validity is not valid.');
		}
		if ($this->itemsList['validity'] and ($this->itemsList['validity']<7 or $this->itemsList['validity']>3650) ) {
			$this->addErrorMessage('Password validity cannot be smaller than one week or longer than ten years.');
		}
		// PCI-DSS 4 forces a max of 90 days for password validity
		if ($CFG['pciDss']['compliant'] and $this->itemsList['validity']>90) {
			$this->addErrorMessage('Password validity cannot be greater than 90 days due to PCI-DSS restrictions.');
		}

		// check password reuse limit count
		if (!is_numeric($this->itemsList['historyLimit'])) {
			$this->addErrorMessage('Password reuse limit is not valid.');
		}
		elseif ($this->itemsList['historyLimit'] and ($this->itemsList['historyLimit']<0 or $this->itemsList['historyLimit']>50) ) {
			$this->addErrorMessage('Password reuse limit cannot be smaller than 0 or longer than 50.');
		}

		// check digits
		if (!is_numeric($this->itemsList['digitsCount']) or $this->itemsList['digitsCount']<0) {
			$this->addErrorMessage('Digits count is not valid.');
		}

		// upperLettersCount
		if (!is_numeric($this->itemsList['upperLettersCount']) or $this->itemsList['upperLettersCount']<0) {
			$this->addErrorMessage('Upper letters count is not valid.');
		}

		// special chars count
		if (!is_numeric($this->itemsList['specialCharsCount']) or $this->itemsList['specialCharsCount']<0) {
			$this->addErrorMessage('Special chars count is not valid.');
		}

		// check session lifetime
		if (!$this->itemsList['sessionLifetime'] or !is_numeric($this->itemsList['sessionLifetime'])) {
			$this->addErrorMessage('User session lifetime is not set or valid.');
		} else {
			$sessLifetime = $this->getServerSessionLifetime();
			if ($this->itemsList['sessionLifetime']<1 or $this->itemsList['sessionLifetime']>$sessLifetime) {
				$this->addErrorMessage( sprintf( 'User session lifetime must be greater than 1 minute and shorter than %s minutes.', round($sessLifetime/60, 2) ) );
			}
		}

		// login options
		if (!$this->itemsList['loginWithPassword'] and !$this->itemsList['loginWithAzure']) {
			$this->addErrorMessage( 'Select at least one login option.' );
		}

	}


	/**
	 * (non-PHPdoc)
	 * @see \App\Config\XmlBasicAbstract::save_write_do_getXml()
	 */
	protected function save_write_do_getXml()
	{

		// also clears the cache for session lifetime
		$cacheObj = \Engine\Cache\General::getInstance('disk');
		$cacheObj->delete('configuredUserSessionLifetime');

		// start composing the XML object
		$xml=simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><passwordSecurity />');

		// add the deails
		$xml->minLength = $this->itemsList['minLength'];
		$xml->validity = $this->itemsList['validity'];
		$xml->content = '';
		$xml->content->digits = $this->itemsList['digitsCount'];
		$xml->content->upperLetters = $this->itemsList['upperLettersCount'];
		$xml->content->specialChars = $this->itemsList['specialCharsCount'];
		$xml->historyLimit = $this->itemsList['historyLimit'];
		$xml->sessionLifetime = $this->itemsList['sessionLifetime'];
		$xml->loginOptions = '';
		$xml->loginOptions->password = '';
		$xml->loginOptions->password['active'] = (int)$this->itemsList['loginWithPassword'];
		$xml->loginOptions->azure = '';
		$xml->loginOptions->azure['active'] = (int)$this->itemsList['loginWithAzure'];

		// return the simplexml object
		return $xml;

	}


	/**
	 * Returns the session lifetime set at server level - you cannot go passed that value.
	 * The value is in seconds.
	 */
	public function getServerSessionLifetime()
	{
		$sessionCookieLifetime = ini_get('session.cookie_lifetime')?:PHP_INT_MAX;
		return min(ini_get('session.gc_maxlifetime'), $sessionCookieLifetime);
	}

	/**
	 * Returns the user session lifetime but also caches the result.
	 * @return mixed|number
	 */
	public function getUserSessionLifetimeCached()
	{

		$cacheObj = \Engine\Cache\General::getInstance('disk');
		$cacheKey = 'configuredUserSessionLifetime';

		// try from cache
		$cachedValue = $cacheObj->get($cacheKey);
		if ($cachedValue) {
			return $cachedValue;
		}

		// no cached value - get it, cache it and return it
		$userSessValue = $this->get('sessionLifetime');
		$cacheObj->set( $cacheKey, $userSessValue, 3600); // cache for 60 minutes
		return $userSessValue;
	}

}
