diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 1c8dba45..5bf2652f 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -53,6 +53,7 @@ require_once(CLASS_DIR . '/bitcoinwrapper.class.php'); require_once(CLASS_DIR . '/monitoring.class.php'); require_once(CLASS_DIR . '/notification.class.php'); require_once(CLASS_DIR . '/user.class.php'); +require_once(CLASS_DIR . '/csrftoken.class.php'); require_once(CLASS_DIR . '/invitation.class.php'); require_once(CLASS_DIR . '/share.class.php'); require_once(CLASS_DIR . '/worker.class.php'); diff --git a/public/include/classes/base.class.php b/public/include/classes/base.class.php index d3dd18c1..d8bb23bb 100644 --- a/public/include/classes/base.class.php +++ b/public/include/classes/base.class.php @@ -73,6 +73,9 @@ class Base { public function setTokenType($tokentype) { $this->tokentype = $tokentype; } + public function setCSRFToken($token) { + $this->CSRFToken = $token; + } public function setShare($share) { $this->share = $share; } diff --git a/public/include/classes/csrftoken.class.php b/public/include/classes/csrftoken.class.php new file mode 100644 index 00000000..68f8e427 --- /dev/null +++ b/public/include/classes/csrftoken.class.php @@ -0,0 +1,74 @@ +salt; $salt2 = $this->salty; $seed = $salt1; + $lead = $this->config['csrf']['leadtime']; + $lead_sec = ($lead <= 11 && $lead >= 0) ? $lead : 3; + if ($minute == 59 && $second > (60-$lead_sec)) { + $minute = 0; + $fhour = ($hour == 23) ? $hour = 0 : $hour+=1; + } + $seed.= (strpos($timing, 'm') !== false) ? $month : ''; + $seed.= (strpos($timing, 'd') !== false) ? $day : ''; + $seed.= (strpos($timing, 'y') !== false) ? $year : ''; + $seed.= (strpos($timing, 'H') !== false) ? $hour : ''; + $seed.= (strpos($timing, 'i') !== false) ? $minute : ''; + $seed.= (strpos($timing, 's') !== false) ? $second : ''; + $seed.= ($seedExtra !== '') ? $seedExtra.$salt2 : $salt2; + return $this->getHash($seed); + } + + /** + * Convenience method to get a token expired message with a token type, and ? image with description + * @param string $tokentype if you want a specific tokentype, set it here + * @param string $dowhat What will be put in the string "Simply $dowhat again to...", default is try + */ + public static function getErrorWithDescriptionHTML($tokentype="", $dowhat="try") { + return ($tokentype !== "") ? "$tokentype token expired, please try again ".self::getDescriptionImageHTML($dowhat) : "Token expired, please try again ".self::getDescriptionImageHTML($dowhat); + } + + /** + * Gets the HTML image (?) with short csrf description for users for the incorrect token error message + * @param dowhat string What will be put in the string "Simply $dowhat again to...", default is try + * @return string HTML image with description + */ + public static function getDescriptionImageHTML($dowhat="try") { + $string = "salty.$string.$this->salt); + } +} + +$csrftoken = new CSRFToken(); +$csrftoken->setDebug($debug); +$csrftoken->setMysql($mysqli); +$csrftoken->setSalt(SALT); +$csrftoken->setSalty(SALTY); +$csrftoken->setMail($mail); +$csrftoken->setUser($user); +$csrftoken->setToken($oToken); +$csrftoken->setConfig($config); +$csrftoken->setErrorCodes($aErrorCodes); +?> \ No newline at end of file diff --git a/public/include/classes/news.class.php b/public/include/classes/news.class.php index f83e4253..f71ab7d8 100644 --- a/public/include/classes/news.class.php +++ b/public/include/classes/news.class.php @@ -102,4 +102,5 @@ $news = new News(); $news->setDebug($debug); $news->setMysql($mysqli); $news->setUser($user); +$news->setErrorCodes($aErrorCodes); ?> diff --git a/public/include/classes/token.class.php b/public/include/classes/token.class.php index 902a6b90..4bcfc033 100644 --- a/public/include/classes/token.class.php +++ b/public/include/classes/token.class.php @@ -6,6 +6,15 @@ if (!defined('SECURITY')) die('Hacking attempt'); class Token Extends Base { protected $table = 'tokens'; + /** + * Return time token was created + * @param id int Token ID + * @param time string Creation timestamp + **/ + public function getCreationTime($token) { + return $this->getSingle($token, 'time', 'token', 's'); + } + /** * Fetch a token from our table * @param name string Setting name @@ -27,13 +36,34 @@ class Token Extends Base { * @param account_id int Account id of user * @param token string Token to check * @param type int Type of token + * @param checkTimeExplicitly Check the token time for expiration; can cause issues w/ timezone & sync * @return int 0 or 1 */ - public function isTokenValid($account_id, $token, $type) { - $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? AND token = ? AND type = ? AND UNIX_TIMESTAMP(time) < NOW() LIMIT 1"); - if ($stmt && $stmt->bind_param('isi', $account_id, $token, $type) && $stmt->execute()) - return $stmt->get_result()->num_rows; - return $this->sqlError(); + public function isTokenValid($account_id, $token, $type, $checkTimeExplicitly=false) { + if (!is_int($account_id) || !is_int($type)) { + $this->setErrorMessage("Invalid token"); + return 0; + } + $expiretime = $this->tokentype->getExpiration($type); + $ctimedata = new DateTime($this->getCreationTime($token)); + $checktime = $ctimedata->getTimestamp() + $expiretime; + $now = time(); + if ($checktime >= $now && $checkTimeExplicitly || !$checkTimeExplicitly) { + if ($checkTimeExplicitly) { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? AND token = ? AND type = ? AND ? >= UNIX_TIMESTAMP() LIMIT 1"); + $stmt->bind_param('isii', $account_id, $token, $type, $checktime); + } else { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? AND token = ? AND type = ? LIMIT 1"); + $stmt->bind_param('isi', $account_id, $token, $type); + } + if ($stmt->execute()) + $res = $stmt->get_result(); + return $res->num_rows; + return $this->sqlError(); + } else { + $this->setErrorMessage("Token has expired or is invalid"); + return 0; + } } /** diff --git a/public/include/classes/tools.class.php b/public/include/classes/tools.class.php index 1cf4f875..0c573369 100644 --- a/public/include/classes/tools.class.php +++ b/public/include/classes/tools.class.php @@ -70,20 +70,25 @@ class Tools extends Base { // Check the API type for configured URL if (!$strApiType = $this->getApiType($this->config['price']['url'])) return false; - // Extract price depending on API type - switch ($strApiType) { - case 'coinchose': - foreach ($aData as $aItem) { - if($strCurrency == $aItem[0]) - return $aItem['price']; + // if api data is valid, extract price depending on API type + if (is_array($aData)) { + switch ($strApiType) { + case 'coinchose': + foreach ($aData as $aItem) { + if($strCurrency == $aItem[0]) + return $aItem['price']; + } + break; + case 'btce': + return $aData['ticker']['last']; + break; + case 'cryptsy': + return @$aData['return']['markets'][$strCurrency]['lasttradeprice']; + break; } - break; - case 'btce': - return $aData['ticker']['last']; - break; - case 'cryptsy': - return $aData['return']['markets'][$strCurrency]['lasttradeprice']; - break; + } else { + $this->setErrorMessage("Got an invalid response from ticker API"); + return false; } // Catchall, we have no data extractor for this API url $this->setErrorMessage("Undefined API to getPrice() on URL " . $this->config['price']['url']); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 80cc346a..9bf67a6a 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -25,6 +25,9 @@ class User extends Base { public function getUserEmail($username, $lower=false) { return $this->getSingle($username, 'email', 'username', 's', $lower); } + public function getUserNotifyEmail($username, $lower=false) { + return $this->getSingle($username, 'notify_email', 'username', 's', $lower); + } public function getUserNoFee($id) { return $this->getSingle($id, 'no_fees', 'id'); } @@ -58,6 +61,9 @@ class User extends Base { public function isAdmin($id) { return $this->getUserAdmin($id); } + public function getSignupTime($id) { + return $this->getSingle($id, 'signup_timestamp', 'id'); + } public function changeNoFee($id) { $field = array('name' => 'no_fees', 'type' => 'i', 'value' => !$this->isNoFee($id)); return $this->updateSingle($id, $field); @@ -145,11 +151,14 @@ class User extends Base { $notifs->setSetting($this->setting); $notifs->setErrorCodes($this->aErrorCodes); $ndata = $notifs->getNotificationSettings($uid); - if ($ndata['success_login'] == 1) { + if (@$ndata['success_login'] == 1) { // seems to be active, let's send it $aDataN['username'] = $username; $aDataN['email'] = $this->getUserEmail($username); $aDataN['subject'] = 'Successful login notification'; + $aDataN['LOGINIP'] = $this->getCurrentIP(); + $aDataN['LOGINUSER'] = $this->user; + $aDataN['LOGINTIME'] = date('m/d/y H:i:s'); $notifs->sendNotification($uid, 'success_login', $aDataN); } return true; @@ -654,15 +663,15 @@ class User extends Base { ! $this->setting->getValue('accounts_confirm_email_disabled') ? $is_locked = 1 : $is_locked = 0; $is_admin = 0; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, pin, api_key, is_locked) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_locked) + VALUES (?, ?, ?, ?, ?, ?, ?) "); } else { $is_locked = 0; $is_admin = 1; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin, is_locked) - VALUES (?, ?, ?, ?, ?, 1, ?) + INSERT INTO $this->table (username, pass, email, signup_timestamp, pin, api_key, is_admin, is_locked) + VALUES (?, ?, ?, ?, ?, ?, 1, ?) "); } @@ -671,8 +680,9 @@ class User extends Base { $pin_hash = $this->getHash($pin); $apikey_hash = $this->getHash($username); $username_clean = strip_tags($username); + $signup_time = time(); - if ($this->checkStmt($stmt) && $stmt->bind_param('sssssi', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) { + if ($this->checkStmt($stmt) && $stmt->bind_param('sssissi', $username_clean, $password_hash, $email1, $signup_time, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) { if (! $this->setting->getValue('accounts_confirm_email_disabled') && $is_admin != 1) { if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) { $aData['username'] = $username_clean; @@ -794,26 +804,36 @@ class User extends Base { } /** - * Gets the current CSRF token for this user/type setting and time chunk - * @param string User; for hash seed, if username isn't available use IP - * @param string Type of token; for hash seed, should be unique per page/use - * @return string CSRF token + * Convenience function to get IP address, no params is the same as REMOTE_ADDR + * @param trustremote bool must be FALSE to checkclient or checkforwarded + * @param checkclient bool check HTTP_CLIENT_IP for a valid ip first + * @param checkforwarded bool check HTTP_X_FORWARDED_FOR for a valid ip first + * @return string IP address */ - public function getCSRFToken($user, $type) { - $date = date('m/d/y/H/i/s'); - $data = explode('/', $date); - $month = $data[0]; $day = $data[1]; $year = $data[2]; - $hour = $data[3]; $minute = $data[4]; $second = $data[5]; - $seed = $this->salty; - $lead = $this->config['csrf']['options']['leadtime']; - if ($lead >= 11) { $lead = 10; } - if ($lead <= 0) { $lead = 3; } - if ($minute == 59 && $second > (60-$lead)) { - $minute = 0; - $fhour = ($hour == 23) ? $hour = 0 : $hour+=1; + public function getCurrentIP($trustremote=true, $checkclient=false, $checkforwarded=false) { + $client = (isset($_SERVER['HTTP_CLIENT_IP'])) ? $_SERVER['HTTP_CLIENT_IP'] : false; + $fwd = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : false; + $remote = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : @$_SERVER['REMOTE_ADDR']; + // shared internet + if (filter_var($client, FILTER_VALIDATE_IP) && !$trustremote && $checkclient) { + return $client; + } else if (strpos($fwd, ',') !== false && !$trustremote && $checkforwarded) { + // multiple proxies + $ips = explode(',', $fwd); + $path = array(); + foreach ($ips as $ip) { + if (filter_var($ip, FILTER_VALIDATE_IP)) { + $path[] = $ip; + } + } + return array_pop($path); + } else if (filter_var($fwd, FILTER_VALIDATE_IP) && !$trustremote && $checkforwarded) { + // single + return $fwd; + } else { + // as usual + return $remote; } - $seed = $seed.$month.$day.$user.$type.$year.$hour.$minute.$seed; - return $this->getHash($seed); } } @@ -822,7 +842,6 @@ $user = new User(); $user->setDebug($debug); $user->setMysql($mysqli); $user->setSalt(SALT); -$user->setSalty(SALTY); $user->setSmarty($smarty); $user->setConfig($config); $user->setMail($mail); diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 7c9eebba..35178f35 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -7,7 +7,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); * This is used in the version check to ensure you run the latest version of the configuration file. * Once you upgraded your config, change the version here too. **/ -$config['version'] = '0.0.4'; +$config['version'] = '0.0.5'; // Our include directory for additional features define('INCLUDE_DIR', BASEPATH . 'include'); @@ -134,18 +134,19 @@ $config['twofactor']['options']['changepw'] = true; * form is submitted. * * Options: - * enabled = Whether or not we will generate/check for valid CSRF tokens - * leadtime = Length of time in seconds to give as leeway, 1-10s - * login = Use and check CSRF tokens for the login forms - * + * enabled = Whether or not we will generate/check for valid CSRF tokens + * leadtime = Length of time in seconds to give as leeway between minute switches + * * Don't change this unless you know why you're changing it + * disabled_forms = Which forms you want to disable csrf protection on, if enabled + * * Valid options : login, contact, accountedit, workers, notifications, invite, register, passreset, unlockaccount * Default: - * enabled = true - * leadtime = 3 - * login = true + * enabled = true + * leadtime = 3 + * disabled_forms = array(); */ $config['csrf']['enabled'] = true; -$config['csrf']['options']['leadtime'] = 3; -$config['csrf']['forms']['login'] = true; +$config['csrf']['leadtime'] = 3; +$config['csrf']['disabled_forms'] = array(); /** * Lock account after maximum failed logins diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 8df98502..68b603eb 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -6,42 +6,54 @@ if (!defined('SECURITY')) // twofactor stuff $cp_editable = $wf_editable = $ea_editable = $wf_sent = $ea_sent = $cp_sent = 0; -$ea_token = (!isset($_POST['ea_token'])) ? '' : $_POST['ea_token']; -$cp_token = (!isset($_POST['cp_token'])) ? '' : $_POST['cp_token']; -$wf_token = (!isset($_POST['wf_token'])) ? '' : $_POST['wf_token']; -// set old token and type so we can use it later -$old_token = ""; -$old_token_type = 0; -if ($ea_token !== "") { - $old_token = $ea_token; - $old_token_type = 5; -} else if ($wf_token !== "") { - $old_token = $wf_token; - $old_token_type = 7; -} else if ($cp_token !== "") { - $old_token_type = 6; - $old_token = $cp_token; + +// 2fa - set old token so we can use it if an error happens or we need to use post +$oldtoken_ea = (isset($_POST['ea_token']) && $_POST['ea_token'] !== '') ? $_POST['ea_token'] : @$_GET['ea_token']; +$oldtoken_cp = (isset($_POST['cp_token']) && $_POST['cp_token'] !== '') ? $_POST['cp_token'] : @$_GET['cp_token']; +$oldtoken_wf = (isset($_POST['wf_token']) && $_POST['wf_token'] !== '') ? $_POST['wf_token'] : @$_GET['wf_token']; +$updating = (@$_POST['do']) ? 1 : 0; + +// csrf stuff +$csrfenabled = ($config['csrf']['enabled'] && !in_array('accountedit', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'editaccount') == @$_POST['ctoken']) ? 1 : 0; } if ($user->isAuthenticated()) { if ($config['twofactor']['enabled']) { $popupmsg = 'E-mail confirmations are required for '; $popuptypes = array(); - if ($config['twofactor']['options']['details']) { + if ($config['twofactor']['options']['details'] && $oldtoken_ea !== "") { $popuptypes[] = 'editing your details'; - $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); + $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $oldtoken_ea, 5); $ea_sent = $user->token->doesTokenExist('account_edit', $_SESSION['USERDATA']['id']); } - if ($config['twofactor']['options']['changepw']) { + if ($config['twofactor']['options']['changepw'] && $oldtoken_cp !== "") { $popuptypes[] = 'changing your password'; - $cp_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $cp_token, 6); + $cp_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $oldtoken_cp, 6); $cp_sent = $user->token->doesTokenExist('change_pw', $_SESSION['USERDATA']['id']); } - if ($config['twofactor']['options']['withdraw']) { + if ($config['twofactor']['options']['withdraw'] && $oldtoken_wf !== "") { $popuptypes[] = 'withdrawals'; - $wf_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $wf_token, 7); + $wf_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $oldtoken_wf, 7); $wf_sent = $user->token->doesTokenExist('withdraw_funds', $_SESSION['USERDATA']['id']); } + + // get the status of a token if set + $message_tokensent_invalid = 'A token was sent to your e-mail that will allow you to '; + $message_tokensent_valid = 'You can currently '; + $messages_tokensent_status = array( + 'ea' => 'edit your account details', + 'wf' => 'withdraw funds', + 'cp' => 'change your password' + ); + // build the message we're going to show them for their token(s) + $eaprep_sent = ($ea_sent) ? $message_tokensent_valid.$messages_tokensent_status['ea'] : ""; + $eaprep_edit = ($ea_editable) ? $message_tokensent_invalid.$messages_tokensent_status['ea'] : ""; + $wfprep_sent = ($wf_sent) ? $message_tokensent_valid.$messages_tokensent_status['wf'] : ""; + $wfprep_edit = ($wf_editable) ? $message_tokensent_invalid.$messages_tokensent_status['wf'] : ""; + $cpprep_sent = ($cp_sent) ? $message_tokensent_valid.$messages_tokensent_status['cp'] : ""; + $cpprep_edit = ($cp_editable) ? $message_tokensent_invalid.$messages_tokensent_status['cp'] : ""; $ptc = 0; $ptcn = count($popuptypes); foreach ($popuptypes as $pt) { @@ -53,13 +65,19 @@ if ($user->isAuthenticated()) { } $ptc++; } + // display global notice about tokens being in use and for which bits they're active $_SESSION['POPUP'][] = array('CONTENT' => $popupmsg, 'TYPE' => 'info'); } + if (isset($_POST['do']) && $_POST['do'] == 'genPin') { - if ($user->generatePin($_SESSION['USERDATA']['id'], $_POST['currentPassword'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Your PIN # has been sent to your email.', 'TYPE' => 'success'); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($user->generatePin($_SESSION['USERDATA']['id'], $_POST['currentPassword'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Your PIN # has been sent to your email.', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } else { @@ -71,26 +89,18 @@ if ($user->isAuthenticated()) { $isvalid = in_array($_POST['utype'],$validtypes); if ($isvalid) { $ctype = strip_tags($_POST['utype']); - $send = $user->sendChangeConfigEmail($ctype, $_SESSION['USERDATA']['id']); - if ($send) { - $_SESSION['POPUP'][] = array('CONTENT' => 'A confirmation was sent to your e-mail, follow that link to continue', 'TYPE' => 'success'); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + $send = $user->sendChangeConfigEmail($ctype, $_SESSION['USERDATA']['id']); + if ($send) { + $_SESSION['POPUP'][] = array('CONTENT' => 'A confirmation was sent to your e-mail, follow that link to continue', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } } else { - // back to get, was only post to fix for old token - $ea_token = (!isset($_GET['ea_token'])) ? '' : $_GET['ea_token']; - $cp_token = (!isset($_GET['cp_token'])) ? '' : $_GET['cp_token']; - $wf_token = (!isset($_GET['wf_token'])) ? '' : $_GET['wf_token']; - if ($ea_token == '' && isset($_POST['ea_token']) && strlen($_POST['ea_token']) > 1) { - $ea_token = $_POST['ea_token']; - } else if ($ea_token == '' && isset($_POST['cp_token']) && strlen($_POST['cp_token']) > 1) { - $cp_token = $_POST['cp_token']; - } else if ($wf_token == '' && isset($_POST['wf_token']) && strlen($_POST['wf_token']) > 1) { - $wf_token = $_POST['wf_token']; - } - switch (@$_POST['do']) { case 'cashOut': if ($setting->getValue('disable_payouts') == 1 || $setting->getValue('disable_manual_payouts') == 1) { @@ -100,10 +110,14 @@ if ($user->isAuthenticated()) { $dBalance = $aBalance['confirmed']; if ($dBalance > $config['txfee']) { if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { - if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $wf_token)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'], $oldtoken_wf)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $iPayoutId->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } else { $_SESSION['POPUP'][] = array('CONTENT' => 'You already have one active manual payout request.', 'TYPE' => 'errormsg'); @@ -115,54 +129,74 @@ if ($user->isAuthenticated()) { break; case 'updateAccount': - if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $ea_token)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); - } + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'], $oldtoken_ea)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); + } break; case 'updatePassword': - if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $cp_token)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); - } + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'], $oldtoken_cp)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); + } break; } } } } } + + // 2fa - one last time so we can sync with changes we made during this page if ($user->isAuthenticated() && $config['twofactor']['enabled']) { - // set the token to be the old token so we still have it if it errors out - if ($old_token_type == 5) { - $ea_token = $old_token; - } else if ($old_token_type == 7) { - $wf_token = $old_token; - } else if ($old_token_type == 6) { - $cp_token = $old_token; - } - if ($config['twofactor']['options']['details']) { + // set the token to be the old token, just in case an error occured + $ea_token = (@$oldtoken_ea !== '') ? $oldtoken_ea : @$ea_token; + $wf_token = (@$oldtoken_wf !== '') ? $oldtoken_wf : @$wf_token; + $cp_token = (@$oldtoken_cp !== '') ? $oldtoken_cp : @$cp_token; + if ($config['twofactor']['options']['details'] && $ea_token !== "") { $ea_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $ea_token, 5); $ea_sent = $user->token->doesTokenExist('account_edit', $_SESSION['USERDATA']['id']); } - if ($config['twofactor']['options']['changepw']) { + if ($config['twofactor']['options']['changepw'] && $cp_token !== "") { $cp_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $cp_token, 6); $cp_sent = $user->token->doesTokenExist('change_pw', $_SESSION['USERDATA']['id']); } - if ($config['twofactor']['options']['withdraw']) { + if ($config['twofactor']['options']['withdraw'] && $wf_token !== "") { $wf_editable = $user->token->isTokenValid($_SESSION['USERDATA']['id'], $wf_token, 7); $wf_sent = $user->token->doesTokenExist('withdraw_funds', $_SESSION['USERDATA']['id']); } + + // display token info per each - only when sent and editable or just sent, not by default + (!empty($eaprep_sent) && !empty($eaprep_edit)) ? $_SESSION['POPUP'][] = array('CONTENT' => $eaprep_sent, 'TYPE' => 'success'):""; + (!empty($eaprep_sent) && empty($eaprep_edit)) ? $_SESSION['POPUP'][] = array('CONTENT' => $message_tokensent_invalid.$messages_tokensent_status['ea'], 'TYPE' => 'success'):""; + (!empty($wfprep_sent) && !empty($wfprep_edit)) ? $_SESSION['POPUP'][] = array('CONTENT' => $wfprep_sent, 'TYPE' => 'success'):""; + (!empty($wfprep_sent) && empty($wfprep_edit)) ? $_SESSION['POPUP'][] = array('CONTENT' => $message_tokensent_invalid.$messages_tokensent_status['wf'], 'TYPE' => 'success'):""; + (!empty($cpprep_sent) && !empty($cpprep_edit)) ? $_SESSION['POPUP'][] = array('CONTENT' => $cpprep_sent, 'TYPE' => 'success'):""; + (!empty($cpprep_sent) && empty($cpprep_edit)) ? $_SESSION['POPUP'][] = array('CONTENT' => $message_tokensent_invalid.$messages_tokensent_status['cp'], 'TYPE' => 'success'):""; } -// Tempalte specifics -$smarty->assign("CONTENT", "default.tpl"); + +// csrf stuff $smarty->assign("CHANGEPASSUNLOCKED", $cp_editable); $smarty->assign("WITHDRAWUNLOCKED", $wf_editable); $smarty->assign("DETAILSUNLOCKED", $ea_editable); $smarty->assign("CHANGEPASSSENT", $cp_sent); $smarty->assign("WITHDRAWSENT", $wf_sent); $smarty->assign("DETAILSSENT", $ea_sent); +if ($csrfenabled && !in_array('accountedit', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'editaccount'); + $smarty->assign('CTOKEN', $token); +} +// Tempalte specifics +$smarty->assign("CONTENT", "default.tpl"); ?> diff --git a/public/include/pages/account/invitations.inc.php b/public/include/pages/account/invitations.inc.php index 1fada08d..e3328f01 100644 --- a/public/include/pages/account/invitations.inc.php +++ b/public/include/pages/account/invitations.inc.php @@ -5,13 +5,22 @@ if (!defined('SECURITY')) die('Hacking attempt'); if ($user->isAuthenticated()) { if (!$setting->getValue('disable_invitations')) { + // csrf stuff + $csrfenabled = ($config['csrf']['enabled'] && !in_array('invitations', $config['csrf']['disabled_forms'])) ? 1 : 0; + if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'invitations') == @$_POST['ctoken']) ? 1 : 0; + } if ($invitation->getCountInvitations($_SESSION['USERDATA']['id']) >= $config['accounts']['invitations']['count']) { $_SESSION['POPUP'][] = array('CONTENT' => 'You have exceeded the allowed invitations of ' . $config['accounts']['invitations']['count'], 'TYPE' => 'errormsg'); } else if (isset($_POST['do']) && $_POST['do'] == 'sendInvitation') { - if ($invitation->sendInvitation($_SESSION['USERDATA']['id'], $_POST['data'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Invitation sent', 'TYPE' => 'success'); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($invitation->sendInvitation($_SESSION['USERDATA']['id'], $_POST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Invitation sent', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to send invitation to recipient: ' . $invitation->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to send invitation to recipient: ' . $invitation->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } $aInvitations = $invitation->getInvitations($_SESSION['USERDATA']['id']); @@ -21,5 +30,10 @@ if ($user->isAuthenticated()) { $_SESSION['POPUP'][] = array('CONTENT' => 'Invitations are disabled', 'TYPE' => 'errormsg'); } } +// csrf token +if ($csrfenabled && !in_array('invitations', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'invitations'); + $smarty->assign('CTOKEN', $token); +} $smarty->assign('CONTENT', 'default.tpl'); ?> diff --git a/public/include/pages/account/notifications.inc.php b/public/include/pages/account/notifications.inc.php index 49e18496..ff2f7c28 100644 --- a/public/include/pages/account/notifications.inc.php +++ b/public/include/pages/account/notifications.inc.php @@ -7,11 +7,21 @@ if ($user->isAuthenticated()) { $_SESSION['POPUP'][] = array('CONTENT' => 'Notification system disabled by admin.', 'TYPE' => 'info'); $smarty->assign('CONTENT', 'empty'); } else { + // csrf stuff + $csrfenabled = ($config['csrf']['enabled'] && !in_array('notifications', $config['csrf']['disabled_forms'])) ? 1 : 0; + if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'editnotifs') == @$_POST['ctoken']) ? 1 : 0; + } + if (@$_REQUEST['do'] == 'save') { - if ($notification->updateSettings($_SESSION['USERDATA']['id'], $_REQUEST['data'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Updated notification settings', 'TYPE' => 'success'); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($notification->updateSettings($_SESSION['USERDATA']['id'], $_REQUEST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Updated notification settings', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $notification->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $notification->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } @@ -22,6 +32,11 @@ if ($user->isAuthenticated()) { // Fetch user notification settings $aSettings = $notification->getNotificationSettings($_SESSION['USERDATA']['id']); + // csrf token + if ($csrfenabled && !in_array('notifications', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'editnotifs'); + $smarty->assign('CTOKEN', $token); + } $smarty->assign('NOTIFICATIONS', $aNotifications); $smarty->assign('SETTINGS', $aSettings); $smarty->assign('CONTENT', 'default.tpl'); diff --git a/public/include/pages/account/unlock.inc.php b/public/include/pages/account/unlock.inc.php index fef18761..3fb4667e 100644 --- a/public/include/pages/account/unlock.inc.php +++ b/public/include/pages/account/unlock.inc.php @@ -3,18 +3,33 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); +// csrf stuff +$csrfenabled = ($config['csrf']['enabled'] && !in_array('unlockaccount', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'unlockaccount') == @$_POST['ctoken']) ? 1 : 0; +} + // Confirm an account by token if (!isset($_GET['token']) || empty($_GET['token'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Missing token', 'TYPE' => 'errormsg'); } else if (!$aToken = $oToken->getToken($_GET['token'], 'account_unlock')) { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to re-activate your account. Invalid token.', 'TYPE' => 'errormsg'); } else { - if ($user->setUserFailed($aToken['account_id'], 0) && $user->setUserPinFailed($aToken['account_id'], 0) && $user->changeLocked($aToken['account_id'])) { - $oToken->deleteToken($aToken['token']); - $_SESSION['POPUP'][] = array('CONTENT' => 'Account re-activated. Please login.'); + if (!$csrfenabled || $csrfenabled && !$nocsrf) { + if ($user->setUserFailed($aToken['account_id'], 0) && $user->setUserPinFailed($aToken['account_id'], 0) && $user->changeLocked($aToken['account_id'])) { + $oToken->deleteToken($aToken['token']); + $_SESSION['POPUP'][] = array('CONTENT' => 'Account re-activated. Please login.'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to re-activate account. Contact site support.', 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to re-activate account. Contact site support.', 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } +// csrf token +if ($csrfenabled && !in_array('unlockaccount', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'unlockaccount'); + $smarty->assign('CTOKEN', $token); +} $smarty->assign('CONTENT', 'default.tpl'); ?> diff --git a/public/include/pages/account/workers.inc.php b/public/include/pages/account/workers.inc.php index 3ccacfd3..42e03b3e 100644 --- a/public/include/pages/account/workers.inc.php +++ b/public/include/pages/account/workers.inc.php @@ -3,6 +3,12 @@ if (!defined('SECURITY')) die('Hacking attempt'); if ($user->isAuthenticated()) { + // csrf stuff + $csrfenabled = ($config['csrf']['enabled'] && !in_array('workers', $config['csrf']['disabled_forms'])) ? 1 : 0; + if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'workers') == @$_POST['ctoken']) ? 1 : 0; + } + switch (@$_REQUEST['do']) { case 'delete': if ($worker->deleteWorker($_SESSION['USERDATA']['id'], $_GET['id'])) { @@ -12,17 +18,25 @@ if ($user->isAuthenticated()) { } break; case 'add': - if ($worker->addWorker($_SESSION['USERDATA']['id'], $_POST['username'], $_POST['password'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Worker added', 'TYPE' => 'success'); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($worker->addWorker($_SESSION['USERDATA']['id'], $_POST['username'], $_POST['password'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Worker added', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $worker->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $worker->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } break; case 'update': - if ($worker->updateWorkers($_SESSION['USERDATA']['id'], @$_POST['data'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Worker updated', 'TYPE' => 'success'); + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($worker->updateWorkers($_SESSION['USERDATA']['id'], @$_POST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Worker updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $worker->getError(), 'TYPE' => 'errormsg'); + } } else { - $_SESSION['POPUP'][] = array('CONTENT' => $worker->getError(), 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } break; } @@ -32,7 +46,10 @@ if ($user->isAuthenticated()) { $smarty->assign('WORKERS', $aWorkers); } - +// csrf token +if ($csrfenabled && !in_array('workers', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'workers'); + $smarty->assign('CTOKEN', $token); +} $smarty->assign('CONTENT', 'default.tpl'); - ?> diff --git a/public/include/pages/contactform.inc.php b/public/include/pages/contactform.inc.php index ad4a54c7..821f1a77 100644 --- a/public/include/pages/contactform.inc.php +++ b/public/include/pages/contactform.inc.php @@ -14,7 +14,11 @@ if ($setting->getValue('disable_contactform')) { require_once(INCLUDE_DIR . '/lib/recaptchalib.php'); $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'))); } - + // csrf token + if ($config['csrf']['enabled'] && !in_array('contact', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'contact'); + $smarty->assign('CTOKEN', $token); + } // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); } diff --git a/public/include/pages/contactform/contactform.inc.php b/public/include/pages/contactform/contactform.inc.php index 28425bfd..3c85df09 100644 --- a/public/include/pages/contactform/contactform.inc.php +++ b/public/include/pages/contactform/contactform.inc.php @@ -3,7 +3,6 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); - if ($setting->getValue('recaptcha_enabled')) { // Load re-captcha specific data require_once(INCLUDE_DIR . '/lib/recaptchalib.php'); @@ -15,19 +14,30 @@ if ($setting->getValue('recaptcha_enabled')) { ); } +// csrf if enabled +$csrfenabled = ($config['csrf']['enabled'] && !in_array('contact', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'contact') == @$_POST['ctoken']) ? 1 : 0; +} + if ($setting->getValue('disable_contactform')) { $_SESSION['POPUP'][] = array('CONTENT' => 'Contactform is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); } else if ($setting->getValue('disable_contactform') && !$user->isAuthenticated(false)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Contactform is disabled for guests.', 'TYPE' => 'errormsg'); } else { - // Check if recaptcha is enabled, process form data if valid - if($setting->getValue('recaptcha_enabled') && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ + if ($setting->getValue('recaptcha_enabled') && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ + // Check if recaptcha is enabled, process form data if valid if ($rsp->is_valid) { - $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'))); - if ($mail->contactform($_POST['senderName'], $_POST['senderEmail'], $_POST['senderSubject'], $_POST['senderMessage'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Thanks for sending your message! We will get back to you shortly'); + // Check if csrf is enabled and fail if token is invalid + if (!$nocsrf && $csrfenabled) { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'There was a problem sending your message. Please try again.' . $user->getError(), 'TYPE' => 'errormsg'); + $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'))); + if ($mail->contactform($_POST['senderName'], $_POST['senderEmail'], $_POST['senderSubject'], $_POST['senderMessage'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Thanks for sending your message! We will get back to you shortly'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'There was a problem sending your message. Please try again.' . $user->getError(), 'TYPE' => 'errormsg'); + } } } else { $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'), $rsp->error)); @@ -39,15 +49,22 @@ if ($setting->getValue('disable_contactform')) { $_SESSION['POPUP'][] = array('CONTENT' => 'Empty Captcha, please try again.', 'TYPE' => 'errormsg'); // Captcha disabled } else { - if ($mail->contactform($_POST['senderName'], $_POST['senderEmail'], $_POST['senderSubject'], $_POST['senderMessage'])) { + // Check if csrf is enabled and fail if token is invalid + if (!$nocsrf && $csrfenabled) { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); + } else if ($mail->contactform($_POST['senderName'], $_POST['senderEmail'], $_POST['senderSubject'], $_POST['senderMessage'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Thanks for sending your message! We will get back to you shortly'); - } else { + } else { $_SESSION['POPUP'][] = array('CONTENT' => 'There was a problem sending your message. Please try again. ' . $user->getError(), 'TYPE' => 'errormsg'); } } } +// csrf token +if ($config['csrf']['enabled'] && !in_array('contact', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'contact'); + $smarty->assign('CTOKEN', $token); +} // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); - ?> diff --git a/public/include/pages/home.inc.php b/public/include/pages/home.inc.php index 4dffeb8a..3033c160 100644 --- a/public/include/pages/home.inc.php +++ b/public/include/pages/home.inc.php @@ -22,12 +22,11 @@ if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { } else { $debug->append('Using cached page', 3); } -// csrf token - update if it's enabled -$token = ''; -if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { - $token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login'); +// csrf token +if ($config['csrf']['enabled'] && !in_array('login', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'login'); + $smarty->assign('CTOKEN', $token); } // Load news entries for Desktop site and unauthenticated users $smarty->assign("CONTENT", "default.tpl"); -$smarty->assign('CTOKEN', $token); ?> diff --git a/public/include/pages/login.inc.php b/public/include/pages/login.inc.php index 7122d22b..09f5e1e8 100644 --- a/public/include/pages/login.inc.php +++ b/public/include/pages/login.inc.php @@ -3,6 +3,11 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); +// csrf if enabled +$csrfenabled = ($config['csrf']['enabled'] && !in_array('login', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'login') == @$_POST['ctoken']) ? 1 : 0; +} // ReCaptcha handling if enabled if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins')) { @@ -26,8 +31,18 @@ if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST $_SESSION['POPUP'][] = array('CONTENT' => 'You are not allowed to login during maintenace.', 'TYPE' => 'info'); } else if (!empty($_POST['username']) && !empty($_POST['password'])) { $nocsrf = 1; + $recaptchavalid = 0; + if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins') && $rsp->is_valid) { + if ($rsp->is_valid) { + // recaptcha is enabled and valid + $recaptchavalid = 1; + } else { + // error out, invalid captcha + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: The captcha you entered was incorrect', 'TYPE' => 'errormsg'); + } + } if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { - if ((isset($_POST['ctoken']) && $_POST['ctoken'] !== $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login')) || (!isset($_POST['ctoken']))) { + if ((isset($_POST['ctoken']) && $_POST['ctoken'] !== $csrftoken->getBasic($user->getCurrentIP(), 'login')) || (!isset($_POST['ctoken']))) { // csrf protection is on and this token is invalid, error out -> time expired $nocsrf = 0; } @@ -43,18 +58,15 @@ if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserId($_POST } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to login: '. $user->getError(), 'TYPE' => 'errormsg'); } - } else if ($nocsrf == 0) { - $img = ""; - $_SESSION['POPUP'][] = array('CONTENT' => "Login token expired, please try again $img", 'TYPE' => 'info'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } -// csrf token - update if it's enabled -$token = ''; -if ($config['csrf']['enabled'] && $config['csrf']['forms']['login']) { - $token = $user->getCSRFToken($_SERVER['REMOTE_ADDR'], 'login'); +// csrf token +if ($csrfenabled && !in_array('login', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'login'); + $smarty->assign('CTOKEN', $token); } - // Load login template $smarty->assign('CONTENT', 'default.tpl'); -$smarty->assign('CTOKEN', $token); ?> diff --git a/public/include/pages/logout.inc.php b/public/include/pages/logout.inc.php index 6a537c1d..30425851 100644 --- a/public/include/pages/logout.inc.php +++ b/public/include/pages/logout.inc.php @@ -7,5 +7,4 @@ if (!defined('SECURITY')) // This probably (?) never fails $user->logoutUser(); $smarty->assign("CONTENT", "default.tpl"); -// header('Location: index.php?page=home'); ?> diff --git a/public/include/pages/password.inc.php b/public/include/pages/password.inc.php index aecab054..adc1047a 100644 --- a/public/include/pages/password.inc.php +++ b/public/include/pages/password.inc.php @@ -4,6 +4,11 @@ if (!defined('SECURITY')) die('Hacking attempt'); +// csrf token +if ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'resetpass'); + $smarty->assign('CTOKEN', $token); +} // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); ?> diff --git a/public/include/pages/password/change.inc.php b/public/include/pages/password/change.inc.php index d8815b1d..2c3e08a0 100644 --- a/public/include/pages/password/change.inc.php +++ b/public/include/pages/password/change.inc.php @@ -4,13 +4,31 @@ if (!defined('SECURITY')) die('Hacking attempt'); -if (isset($_POST['do']) && $_POST['do'] == 'resetPassword') { - if ($user->resetPassword($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.', 'TYPE' => 'success'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); - } +// csrf stuff +$csrfenabled = ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + // we have to use editaccount token because this that's where we'll get pushed here from + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'editaccount') == @$_POST['ctoken']) ? 1 : 0; } + +if (!$csrfenabled || $csrfenabled && $nocsrf) { + if (isset($_POST['do']) && $_POST['do'] == 'resetPassword') { + if ($user->resetPassword($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); + } + } +} else { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); +} + +// csrf token +if ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'editaccount'); + $smarty->assign('CTOKEN', $token); +} + // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php index cefba0ac..a32ce104 100644 --- a/public/include/pages/password/reset.inc.php +++ b/public/include/pages/password/reset.inc.php @@ -3,13 +3,28 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -// Process password reset request -if ($user->initResetPassword($_POST['username'], $smarty)) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset', 'TYPE' => 'success'); -} else { - $_SESSION['POPUP'][] = array('CONTENT' => htmlentities($user->getError(), ENT_QUOTES), 'TYPE' => 'errormsg'); +// csrf stuff +$csrfenabled = ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'resetpass') == @$_POST['ctoken']) ? 1 : 0; } +// Process password reset request +if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($user->initResetPassword($_POST['username'], $smarty)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => htmlentities($user->getError(), ENT_QUOTES), 'TYPE' => 'errormsg'); + } +} else { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); +} + +// csrf token +if ($config['csrf']['enabled'] && !in_array('passreset', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'resetpass'); + $smarty->assign('CTOKEN', $token); +} // Tempalte specifics, user default template by parent page $smarty->assign("CONTENT", "../default.tpl"); ?> diff --git a/public/include/pages/register.inc.php b/public/include/pages/register.inc.php index 692c3e42..487e67cb 100644 --- a/public/include/pages/register.inc.php +++ b/public/include/pages/register.inc.php @@ -14,6 +14,12 @@ if ($setting->getValue('lock_registration') && $setting->getValue('disable_invit require_once(INCLUDE_DIR . '/lib/recaptchalib.php'); $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'), null, true)); } + // csrf token + if ($config['csrf']['enabled'] && !in_array('register', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'register'); + $smarty->assign('CTOKEN', $token); + } + // Load news entries for Desktop site and unauthenticated users $smarty->assign("CONTENT", "default.tpl"); } ?> diff --git a/public/include/pages/register/register.inc.php b/public/include/pages/register/register.inc.php index 7090a1c4..26fd12f5 100644 --- a/public/include/pages/register/register.inc.php +++ b/public/include/pages/register/register.inc.php @@ -14,6 +14,13 @@ if ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_ena ); $smarty->assign("RECAPTCHA", recaptcha_get_html($setting->getValue('recaptcha_public_key'), $rsp->error, true)); if (!$rsp->is_valid) $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again.', 'TYPE' => 'errormsg'); + $recaptcha = ($rsp->isvalid) ? 1 : 0; +} + +// csrf if enabled +$csrfenabled = ($config['csrf']['enabled'] && !in_array('register', $config['csrf']['disabled_forms'])) ? 1 : 0; +if ($csrfenabled) { + $nocsrf = ($csrftoken->getBasic($user->getCurrentIP(), 'register') == $_POST['ctoken']) ? 1 : 0; } if ($setting->getValue('disable_invitations') && $setting->getValue('lock_registration')) { @@ -21,17 +28,27 @@ if ($setting->getValue('disable_invitations') && $setting->getValue('lock_regist } else if ($setting->getValue('lock_registration') && !$setting->getValue('disable_invitations') && !isset($_POST['token'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Only invited users are allowed to register.', 'TYPE' => 'errormsg'); } else { - // Check if recaptcha is enabled, process form data if valid or disabled - if ($setting->getValue('recaptcha_enabled') != 1 || $setting->getValue('recaptcha_enabled_registrations') != 1 || $rsp->is_valid) { - isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; - if ($user->register(@$_POST['username'], @$_POST['password1'], @$_POST['password2'], @$_POST['pin'], @$_POST['email1'], @$_POST['email2'], @$_POST['tac'], $token)) { - ! $setting->getValue('accounts_confirm_email_disabled') ? $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mailbox to activate this account') : $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); + // Check if csrf is enabled and fail if token is invalid + if (!$csrfenabled || $csrfenabled && $nocsrf) { + if ($setting->getValue('recaptcha_enabled') != 1 || $setting->getValue('recaptcha_enabled_registrations') != 1 || $rsp->is_valid) { + // Check if recaptcha is enabled, process form data if valid or disabled + isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; + if ($user->register(@$_POST['username'], @$_POST['password1'], @$_POST['password2'], @$_POST['pin'], @$_POST['email1'], @$_POST['email2'], @$_POST['tac'], $token)) { + ! $setting->getValue('accounts_confirm_email_disabled') ? $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mailbox to activate this account') : $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); + } } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $csrftoken->getErrorWithDescriptionHTML(), 'TYPE' => 'info'); } } // We load the default registration template instead of an action specific one $smarty->assign("CONTENT", "../default.tpl"); +// csrf token +if ($config['csrf']['enabled'] && !in_array('register', $config['csrf']['disabled_forms'])) { + $token = $csrftoken->getBasic($user->getCurrentIP(), 'register'); + $smarty->assign('CTOKEN', $token); +} ?> diff --git a/public/include/version.inc.php b/public/include/version.inc.php index 21dd0010..c5734b85 100644 --- a/public/include/version.inc.php +++ b/public/include/version.inc.php @@ -4,8 +4,8 @@ if (!defined('SECURITY')) die('Hacking attempt'); define('MPOS_VERSION', '0.0.2'); -define('DB_VERSION', '0.0.3'); -define('CONFIG_VERSION', '0.0.4'); +define('DB_VERSION', '0.0.4'); +define('CONFIG_VERSION', '0.0.5'); // Fetch installed database version $db_version = $setting->getValue('DB_VERSION'); diff --git a/public/templates/mail/notifications/success_login.tpl b/public/templates/mail/notifications/success_login.tpl index df92934b..e1a07bac 100644 --- a/public/templates/mail/notifications/success_login.tpl +++ b/public/templates/mail/notifications/success_login.tpl @@ -1,6 +1,9 @@

Your account has successfully logged in

+

User: {$LOGINUSER}

+

IP: {$LOGINIP}

+

Time: {$LOGINTIME}

If you initiated this login, you can ignore this message. If you did NOT, please notify an administrator.



diff --git a/public/templates/mobile/login/default.tpl b/public/templates/mobile/login/default.tpl index 899a990f..f1331892 100644 --- a/public/templates/mobile/login/default.tpl +++ b/public/templates/mobile/login/default.tpl @@ -1,6 +1,6 @@
- {if $GLOBAL.csrf.enabled && $GLOBAL.csrf.forms.login}{/if} + {if $GLOBAL.csrf.enabled && !"login"|in_array:$GLOBAL.csrf.disabled_forms}{/if}

{nocache}{$RECAPTCHA|default:"" nofilter}{/nocache}
diff --git a/public/templates/mobile/password/change/default.tpl b/public/templates/mobile/password/change/default.tpl index 01db0d84..c547b759 100644 --- a/public/templates/mobile/password/change/default.tpl +++ b/public/templates/mobile/password/change/default.tpl @@ -2,6 +2,7 @@ + {if $GLOBAL.csrf.enabled && !"editaccount"|in_array:$GLOBAL.csrf.disabled_forms}{/if} diff --git a/public/templates/mobile/password/default.tpl b/public/templates/mobile/password/default.tpl index b4af5660..8e436b24 100644 --- a/public/templates/mobile/password/default.tpl +++ b/public/templates/mobile/password/default.tpl @@ -1,6 +1,7 @@ +{if $GLOBAL.csrf.enabled && !"passreset"|in_array:$GLOBAL.csrf.disabled_forms}{/if}

If you have an email set for your account, enter your username to get your password reset

diff --git a/public/templates/mpos/account/edit/default.tpl b/public/templates/mpos/account/edit/default.tpl index 4e5c779b..cea3d882 100644 --- a/public/templates/mpos/account/edit/default.tpl +++ b/public/templates/mpos/account/edit/default.tpl @@ -56,7 +56,8 @@
New Password:
diff --git a/public/templates/mpos/contactform/contactform/default.tpl b/public/templates/mpos/contactform/contactform/default.tpl index 8b137891..65928029 100644 --- a/public/templates/mpos/contactform/contactform/default.tpl +++ b/public/templates/mpos/contactform/contactform/default.tpl @@ -1 +1,30 @@ - + + + + {if $GLOBAL.csrf.enabled && !"contact"|in_array:$GLOBAL.csrf.disabled_forms}{/if} +
+

Contact Us

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
{nocache}{$RECAPTCHA|default:""}{/nocache}
+
+
+ +
+
+ diff --git a/public/templates/mpos/contactform/default.tpl b/public/templates/mpos/contactform/default.tpl index a086e69b..46669896 100644 --- a/public/templates/mpos/contactform/default.tpl +++ b/public/templates/mpos/contactform/default.tpl @@ -1,6 +1,7 @@ + {if $GLOBAL.csrf.enabled && !"contact"|in_array:$GLOBAL.csrf.disabled_forms}{/if}

Contact Us

diff --git a/public/templates/mpos/dashboard/overview.tpl b/public/templates/mpos/dashboard/overview.tpl index 0aae5c56..6fb42246 100644 --- a/public/templates/mpos/dashboard/overview.tpl +++ b/public/templates/mpos/dashboard/overview.tpl @@ -1,5 +1,5 @@
-

Overview {if $GLOBAL.config.price.currency}{$GLOBAL.config.currency}/{$GLOBAL.config.price.currency}: {$GLOBAL.price|number_format:"8"}{/if} / Pool Workers: {$GLOBAL.workers}

+

Overview {if $GLOBAL.config.price.currency}{$GLOBAL.config.currency}/{$GLOBAL.config.price.currency}: {$GLOBAL.price|number_format:"8"|default:"0"}{/if} / Pool Workers: {$GLOBAL.workers}

diff --git a/public/templates/mpos/global/header.tpl b/public/templates/mpos/global/header.tpl index ecc997c1..135b7ec5 100644 --- a/public/templates/mpos/global/header.tpl +++ b/public/templates/mpos/global/header.tpl @@ -1,4 +1,4 @@

{$GLOBAL.website.name|default:"Unknown Pool"}

{if $smarty.request.action|escape|default:""}{$smarty.request.action|escape|capitalize}{else}{$smarty.request.page|escape|default:"home"|capitalize}{/if}

-
+ \ No newline at end of file diff --git a/public/templates/mpos/login/default.tpl b/public/templates/mpos/login/default.tpl index 0d94bd0e..c01d7954 100644 --- a/public/templates/mpos/login/default.tpl +++ b/public/templates/mpos/login/default.tpl @@ -1,7 +1,7 @@
- {if $GLOBAL.csrf.enabled && $GLOBAL.csrf.forms.login}{/if} + {if $GLOBAL.csrf.enabled && !"login"|in_array:$GLOBAL.csrf.disabled_forms}{/if}

Login with existing account

diff --git a/public/templates/mpos/login/small.tpl b/public/templates/mpos/login/small.tpl new file mode 100644 index 00000000..105b83cb --- /dev/null +++ b/public/templates/mpos/login/small.tpl @@ -0,0 +1,18 @@ +{if $smarty.session.AUTHENTICATED|default:"0" == 0} + +{/if} diff --git a/public/templates/mpos/password/change/default.tpl b/public/templates/mpos/password/change/default.tpl index 19960b9b..f22815bf 100644 --- a/public/templates/mpos/password/change/default.tpl +++ b/public/templates/mpos/password/change/default.tpl @@ -3,6 +3,8 @@ + {if $GLOBAL.csrf.enabled && $GLOBAL.csrf.options.sitewide}{/if} +

Password reset

@@ -17,9 +19,22 @@
- + {nocache} + + {if $GLOBAL.csrf.enabled && $GLOBAL.csrf.options.sitewide}{/if} + + {if $GLOBAL.twofactor.enabled && $GLOBAL.twofactor.options.changepw} + {if $CHANGEPASSSENT == 1 && $CHANGEPASSUNLOCKED == 1} + + {elseif $CHANGEPASSSENT == 0 && $CHANGEPASSUNLOCKED == 1 || $CHANGEPASSSENT == 1 && $CHANGEPASSUNLOCKED == 0} + + {elseif $CHANGEPASSSENT == 0 && $CHANGEPASSUNLOCKED == 0} + + {/if} + {else} + + {/if} + {/nocache}
diff --git a/public/templates/mpos/password/default.tpl b/public/templates/mpos/password/default.tpl index 825770b0..5227292b 100644 --- a/public/templates/mpos/password/default.tpl +++ b/public/templates/mpos/password/default.tpl @@ -2,6 +2,7 @@
+ {if $GLOBAL.csrf.enabled && !"passreset"|in_array:$GLOBAL.csrf.disabled_forms}{/if}

Password reset

If you have an email set for your account, enter your username to get your password reset

diff --git a/public/templates/mpos/register/default.tpl b/public/templates/mpos/register/default.tpl index d60f2209..bea79a6f 100644 --- a/public/templates/mpos/register/default.tpl +++ b/public/templates/mpos/register/default.tpl @@ -7,6 +7,7 @@ {if $smarty.request.token|default:""} {/if} +{if $GLOBAL.csrf.enabled && !"register"|in_array:$GLOBAL.csrf.disabled_forms}{/if}
diff --git a/sql/000_base_structure.sql b/sql/000_base_structure.sql index c97b8073..e221a84a 100644 --- a/sql/000_base_structure.sql +++ b/sql/000_base_structure.sql @@ -15,10 +15,12 @@ CREATE TABLE IF NOT EXISTS `accounts` ( `username` varchar(40) NOT NULL, `pass` varchar(255) NOT NULL, `email` varchar(255) DEFAULT NULL COMMENT 'Assocaited email: used for validating users, and re-setting passwords', + `notify_email` VARCHAR( 255 ) NULL DEFAULT NULL, `loggedIp` varchar(255) DEFAULT NULL, `is_locked` tinyint(1) NOT NULL DEFAULT '0', `failed_logins` int(5) unsigned DEFAULT '0', `failed_pins` int(5) unsigned DEFAULT '0', + `signup_timestamp` int(10) DEFAULT '0', `last_login` int(10) DEFAULT NULL, `pin` varchar(255) NOT NULL COMMENT 'four digit pin to allow account changes', `api_key` varchar(255) DEFAULT NULL, @@ -130,7 +132,7 @@ CREATE TABLE IF NOT EXISTS `settings` ( UNIQUE KEY `setting` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.3'); +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.4'); CREATE TABLE IF NOT EXISTS `shares` ( `id` bigint(30) NOT NULL AUTO_INCREMENT, @@ -203,9 +205,9 @@ INSERT INTO `token_types` (`id`, `name`, `expiration`) VALUES (2, 'confirm_email', 0), (3, 'invitation', 0), (4, 'account_unlock', 0), -(5, 'account_edit', 360), -(6, 'change_pw', 360), -(7, 'withdraw_funds', 360); +(5, 'account_edit', 3600), +(6, 'change_pw', 3600), +(7, 'withdraw_funds', 3600); CREATE TABLE IF NOT EXISTS `transactions` ( `id` int(255) NOT NULL AUTO_INCREMENT, diff --git a/sql/014_accounts_update.sql b/sql/014_accounts_update.sql new file mode 100644 index 00000000..ed7802c0 --- /dev/null +++ b/sql/014_accounts_update.sql @@ -0,0 +1,5 @@ +ALTER TABLE `accounts` ADD COLUMN `signup_timestamp` INT( 10 ) NOT NULL DEFAULT '0' AFTER `failed_pins`; +ALTER TABLE `accounts` ADD COLUMN `notify_email` VARCHAR( 255 ) NULL DEFAULT NULL AFTER `email`; +TRUNCATE TABLE `token_types`; +INSERT INTO `token_types` (`id`, `name`, `expiration`) VALUES (1, 'password_reset', 3600), (2, 'confirm_email', 0), (3, 'invitation', 0), (4, 'account_unlock', 0), (5, 'account_edit', 3600), (6, 'change_pw', 3600), (7, 'withdraw_funds', 3600); +INSERT INTO `settings` (`name`, `value`) VALUES ('DB_VERSION', '0.0.4') ON DUPLICATE KEY UPDATE `value` = '0.0.4'; diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..8eaf1edc --- /dev/null +++ b/tests/README.md @@ -0,0 +1,19 @@ +How to run these tests +====================== + + * Download and install phpunit (http://phpunit.de/manual/3.7/en/installation.html) + * `phpunit tests/unit` + + +How to add tests +================ + + * Add unit tests to the matching folder/file.php or create a new one + * Add any new suites to the phpunit.xml file + * Below is the suite entry for the sample test in unit/config/SampleTest.php + +``` + + unit/config + +``` diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 00000000..6cafbdac --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,5 @@ + + + unit/config + + \ No newline at end of file diff --git a/tests/unit/config/SampleTest.php b/tests/unit/config/SampleTest.php new file mode 100644 index 00000000..9b445a57 --- /dev/null +++ b/tests/unit/config/SampleTest.php @@ -0,0 +1,21 @@ +assertNotEmpty(SALT); + $this->assertGreaterThan(1, strlen(SALT)); + } +} + +?> \ No newline at end of file