<?php
###############################################################################
# GoogleAuthenticator.php
#
# @author Anil Kumar <akumar@codepunch.com>
# @link   https://codepunch.com
#
############################################################################### 

namespace 	CodePunch\Config\Security;

use 		CodePunch\Base\Util as UTIL;
use			CodePunch\Base\Text as TEXT;
use			CodePunch\DB\Audit  as AUDIT;
use 		Exception;

###############################################################################

include_once(UTIL::get_install_folder_path()."thirdparty/2fa/google/src/FixedBitNotation.php");
include_once(UTIL::get_install_folder_path()."thirdparty/2fa/google/src/GoogleAuthenticatorInterface.php");
include_once(UTIL::get_install_folder_path()."thirdparty/2fa/google/src/GoogleAuthenticator.php");
include_once(UTIL::get_install_folder_path()."thirdparty/2fa/google/src/GoogleQrUrl.php");
include_once(UTIL::get_install_folder_path()."lib/php/phpqrcode/qrlib.php");

###############################################################################

class GoogleAuthenticator extends Base {
	
	public function getKeys()
	{
		return array(
			'Initialization Code' => 'This is used to activate Google Authenticator based 2 factor authentication for the first time. This key should be provided to the end users so that they can activate 2 factor authentication after they login normally. This will work only once per user.'
		);
	}
	
	###########################################################################
	
	public function get() 
	{
		$auth = $this->getAuthentication();
		$message = "";
		// Check if Google has been initialized for this user.
		$username = UTIL::get_from_array($_SESSION[\CodePunch\Config\Auth::USERNAME], "");
		if($username != "" && $auth && $auth->getDatabase()) {
			$message = "";
			$file = UTIL::get_install_folder_path() . "lib/php/CodePunch/Config/Security/google/google.htm";
			$contents = file_get_contents($file);
			$db = $auth->getDatabase();
			$this->createUserColumn("google_2fa_code");
			$this->createUserColumn("google_2fa_init");
			$setup = new \CodePunch\Config\Settings($auth);
			$twofa_cookie_set_days = $setup->getString("twofa_cookie_set_days", 0);
			if($twofa_cookie_set_days <= 0 || $twofa_cookie_set_days > 45)
				$twofa_cookie_set_days = 0;
			$encrypted_google_2fa_code = $db->findOneOf($db->getUserTableName(), "name", $username, "google_2fa_code");
			$encrypted_google_2fa_init = $db->findOneOf($db->getUserTableName(), "name", $username, "google_2fa_init");
			$userinputkey = UTIL::get_sanitized_request_string("google_2fa_code", "");
			$setupkey = $setup->getEncryptedOption("googleauthenticator_key_initialization_code", "");
			
			// Check if First Time Activation
			$initgoogle2fa = false;
			if($encrypted_google_2fa_code === false || $encrypted_google_2fa_init === false)
				$initgoogle2fa = true;
			else if($encrypted_google_2fa_code == "" || $encrypted_google_2fa_init == "")
				$initgoogle2fa = true;
			else if($userinputkey != "" && $userinputkey == $setupkey && $encrypted_google_2fa_init == "6431")
				$initgoogle2fa = true;
			else if($userinputkey == "" && $encrypted_google_2fa_init == "6431")
				$initgoogle2fa = true;
			if($initgoogle2fa) {
				if($userinputkey != "" && $userinputkey == $setupkey) {
					$google_2fa_code = strtoupper(UTIL::randomText(16));
					$encrypted_google_2fa_code = $auth->encrypt($google_2fa_code);
					$db->updateTable($db->getUserTableName(), array('google_2fa_code'=>$encrypted_google_2fa_code,'google_2fa_init'=>'6431'), "name=?", array($username));
					$qrcodetext = $this->getQRCodeText($username, $google_2fa_code, "Watch MyDomains SED");
					ob_start();
					\QRcode::png($qrcodetext, null, 'M', 5, 3);
					$QR_Base64 = base64_encode(ob_get_contents());
					ob_end_clean();
					$imageData = 'data:image/png;base64,' . $QR_Base64;
					$qrcodedata = "<p class=\"small text-justify\">Setup the Google authenticator app by scanning the QR code or entering the details below</p>";
					$qrcodedata .= "<p class=\"mb-3 mt-3 text-left\">Key: $google_2fa_code<br>";
					$qrcodedata .= "Account Info: Watch My Domains SED ($username)</p>";
					$qrcodedata .= "<p class=\"mb-3 mt-3\"><img class=\"mx-auto d-block\" src=\"$imageData\"></p>";
					$contents = str_ireplace("{{GOOGLEQRCODE}}", $qrcodedata, $contents);
				}
				else {
					$google2famsg = "<p class=\"twofa-prompt\">Please contact your system administrator for an activation code to enter into the box below.</p>";
					$contents = str_ireplace("{{GOOGLE2FASETUPMSG}}", $google2famsg, $contents);	
				}
			}
			
			// Check the user entered 2F Code
			else if($userinputkey != "" && $this->check($userinputkey, $message)) {
				$db->updateTable($db->getUserTableName(), array('google_2fa_init'=>"1346"), "name=?", array($username));
				$auth->processSecondaryLogin($username, $twofa_cookie_set_days);
			}
			
			// 2FA Prompt
			else {
				$google2facode = "<p class=\"twofa-prompt\">Please open <a href=\"https://support.google.com/accounts/answer/1066447?co=GENIE.Platform%3DiOS&hl=en\" target=\"_blank\">Google Authenticator</a> in your phone or tablet, enter the code you see for this application &amp user (<b>{{USERNAME}}</b>) into the box below and click 'Submit'.</p>";
				$contents = str_ireplace("{{GOOGLE2FACODEMSG}}", $google2facode, $contents);
				
				if($twofa_cookie_set_days) {
					$google2favalidate = "<input type=\"checkbox\" name='validate_device' id=\"validate_device\">\nDon't ask again on this device for $twofa_cookie_set_days days.";
					$contents = str_ireplace("{{GOOGLE2FAVALIDATE}}", $google2favalidate, $contents);
				}
				
				$loginurl = UTIL::get_root_url() . "login.php";
				$contents = str_ireplace("{{RELOGINMSG}}", "Not <a href=\"$loginurl\">$username</a>?", $contents);
			}
			
			// Cleanup
			$contents = str_ireplace("{{GOOGLEQRCODE}}", "", $contents);	
			$contents = str_ireplace("{{GOOGLE2FASETUPMSG}}", "", $contents);
			$contents = str_ireplace("{{GOOGLE2FACODEMSG}}", "", $contents);
			$contents = str_ireplace("{{GOOGLE2FAVALIDATE}}", "", $contents);
			$contents = str_ireplace("{{RELOGINMSG}}", "", $contents);
			$contents = str_ireplace("{{USERNAME}}", $username, $contents);

						
			// Valid keys are home, content, bodytail, headtail, header, footer
			return array('content'=>$contents, 'template'=>'login', 'body'=>$message);
		}
		return array();
	}
	
	###########################################################################
	
	private function getQRCodeText($accountName, $secret, $issuer)
	{
		$label = $accountName;
		$otpauthString = 'otpauth://totp/%s?secret=%s';
		$label = $issuer.':'.$label;
		$otpauthString .= '&issuer=%s';
		return sprintf($otpauthString, $label, $secret, $issuer) . "&period=45";
	}
	
	###########################################################################

	private function check($gkey, &$error)
	{
		$error = "";
		$auth = $this->getAuthentication();
		if($auth && $gkey != "") {
			$username = UTIL::get_from_array($_SESSION[\CodePunch\Config\Auth::USERNAME], "");
			if($username != "" && $auth && $auth->getDatabase()) {
				$db = $auth->getDatabase();
				$setup = new \CodePunch\Config\Settings($auth);
				$userid = 0;
				$timewait = 0;
				if(isset( $_SESSION[\CodePunch\Config\Auth::USERINFO]['userid'])) 
					$userid = $_SESSION[\CodePunch\Config\Auth::USERINFO]['userid'];
				$maxattempts = $setup->getOption('max_login_attempts', \CodePunch\Config\Auth::DEFAULT_MAX_LOGIN_COUNT);	
				$loginlockout = $setup->getOption('login_lockout_minutes', \CodePunch\Config\Auth::DEFAULT_LOGIN_LOCKOUT_MINUTES);
				if($db->isLoginAllowed($maxattempts, $loginlockout, $timewait)) {
					$encrypted_google_2fa_code = $db->findOneOf($db->getUserTableName(), "name", $username, "google_2fa_code");
					$google_2fa_code = $auth->decrypt($encrypted_google_2fa_code);
					$g = new \Sonata\GoogleAuthenticator\GoogleAuthenticator();
					if($g->checkCode($google_2fa_code, $gkey)) {
						$db->authenticationLog(true, $username, $userid);
						return true;
					}
					else {
						$failcount = $db->authenticationLog(false, $username, $userid);
						if($failcount > 0)
							$error = "Invalid code ($failcount/$maxattempts)";
						AUDIT::add($db, AUDIT::LOGIN_2FA_FAIL, null, $_SESSION[\CodePunch\Config\Auth::USERNAME]);
					}
				}
				else {
					$error = "Too many failed login attempts. Please wait $timewait seconds.";
				}
			}
		}
		return false;
	}	

	###########################################################################
	
	
}

