zoneminder/web/includes/auth.php

420 lines
16 KiB
PHP
Raw Normal View History

2018-04-07 02:31:11 +08:00
<?php
//
2018-04-07 02:36:23 +08:00
// ZoneMinder auth library, $Date$, $Revision$
2018-04-07 02:31:11 +08:00
// Copyright (C) 2001-2008 Philip Coombes
2018-10-09 22:07:40 +08:00
//
2018-04-07 02:31:11 +08:00
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
2018-10-09 22:07:40 +08:00
//
2018-04-07 02:31:11 +08:00
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
2018-10-09 22:07:40 +08:00
//
2018-04-07 02:31:11 +08:00
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2018-10-09 22:07:40 +08:00
//
//
require_once('session.php');
2019-05-05 19:50:52 +08:00
require_once(__DIR__.'/../vendor/autoload.php');
use \Firebase\JWT\JWT;
2018-04-07 02:31:11 +08:00
2019-05-02 01:22:24 +08:00
// this function migrates mysql hashing to bcrypt, if you are using PHP >= 5.5
// will be called after successful login, only if mysql hashing is detected
function migrateHash($user, $pass) {
2019-05-24 02:27:17 +08:00
if ( function_exists('password_hash') ) {
ZM\Info("Migrating $user to bcrypt scheme");
2019-05-02 01:22:24 +08:00
// let it generate its own salt, and ensure bcrypt as PASSWORD_DEFAULT may change later
// we can modify this later to support argon2 etc as switch to its own password signature detection
$bcrypt_hash = password_hash($pass, PASSWORD_BCRYPT);
//ZM\Info ("hased bcrypt $pass is $bcrypt_hash");
$update_password_sql = 'UPDATE Users SET Password=\''.$bcrypt_hash.'\' WHERE Username=\''.$user.'\'';
2019-05-24 02:27:17 +08:00
ZM\Info($update_password_sql);
2019-05-02 01:22:24 +08:00
dbQuery($update_password_sql);
# Since password field has changed, existing auth_hash is no longer valid
generateAuthHash(ZM_AUTH_HASH_IPS, true);
2019-05-24 02:27:17 +08:00
} else {
ZM\Info('Cannot migrate password scheme to bcrypt, as you are using PHP < 5.3');
2019-05-02 01:22:24 +08:00
return;
}
}
// core function used to login a user to PHP. Is also used for cake sessions for the API
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
function userLogin($username='', $password='', $passwordHashed=false, $from_api_layer = false) {
global $user;
if ( !$username and isset($_REQUEST['username']) )
$username = $_REQUEST['username'];
if ( !$password and isset($_REQUEST['password']) )
$password = $_REQUEST['password'];
// if true, a popup will display after login
2019-05-02 01:22:24 +08:00
// lets validate reCaptcha if it exists
// this only applies if it userLogin was not called from API layer
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
if ( !$from_api_layer
&& defined('ZM_OPT_USE_GOOG_RECAPTCHA')
2018-10-09 22:07:40 +08:00
&& defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
&& ZM_OPT_USE_GOOG_RECAPTCHA
2018-10-09 22:07:40 +08:00
&& ZM_OPT_GOOG_RECAPTCHA_SECRETKEY
&& ZM_OPT_GOOG_RECAPTCHA_SITEKEY )
{
$url = 'https://www.google.com/recaptcha/api/siteverify';
$fields = array (
'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY,
'response' => $_REQUEST['g-recaptcha-response'],
'remoteip' => $_SERVER['REMOTE_ADDR']
);
$res = do_post_request($url, http_build_query($fields));
2019-05-24 02:27:17 +08:00
$responseData = json_decode($res, true);
// credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php
// if recaptcha resulted in error, we might have to deny login
2019-05-24 02:27:17 +08:00
if ( isset($responseData['success']) && ($responseData['success'] == false) ) {
// PP - before we deny auth, let's make sure the error was not 'invalid secret'
// because that means the user did not configure the secret key correctly
// in this case, we prefer to let him login in and display a message to correct
// the key. Unfortunately, there is no way to check for invalid site key in code
// as it produces the same error as when you don't answer a recaptcha
if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
2019-05-24 02:27:17 +08:00
if ( !in_array('invalid-input-secret', $responseData['error-codes']) ) {
Error('reCaptcha authentication failed');
return null;
} else {
Error('Invalid recaptcha secret detected');
}
}
} // end if success==false
} // end if using reCaptcha
2019-05-02 01:22:24 +08:00
// coming here means we need to authenticate the user
// if captcha existed, it was passed
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username = ?';
$sql_values = array($username);
// First retrieve the stored password
// and move password hashing to application space
2019-05-24 02:27:17 +08:00
$saved_user_details = dbFetchOne($sql, NULL, $sql_values);
2019-05-02 01:22:24 +08:00
$password_correct = false;
$password_type = NULL;
2019-05-24 02:27:17 +08:00
if ( $saved_user_details ) {
// if the API layer asked us to login, make sure the user
// has API enabled (admin may have banned API for this user)
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
if ( $from_api_layer ) {
2019-05-24 02:27:17 +08:00
if ( $saved_user_details['APIEnabled'] != 1 ) {
ZM\Error("API disabled for: $username");
$_SESSION['loginFailed'] = true;
unset($user);
return false;
}
}
2019-05-02 01:22:24 +08:00
$saved_password = $saved_user_details['Password'];
2019-05-24 02:27:17 +08:00
if ( $saved_password[0] == '*' ) {
2019-05-02 01:22:24 +08:00
// We assume we don't need to support mysql < 4.1
// Starting MY SQL 4.1, mysql concats a '*' in front of its password hash
// https://blog.pythian.com/hashing-algorithm-in-mysql-password-2/
2019-05-24 02:27:17 +08:00
ZM\Logger::Debug('Saved password is using MYSQL password function');
$input_password_hash = '*'.strtoupper(sha1(sha1($password, true)));
2019-05-02 01:22:24 +08:00
$password_correct = ($saved_password == $input_password_hash);
$password_type = 'mysql';
2019-05-24 02:27:17 +08:00
} else if ( preg_match('/^\$2[ayb]\$.+$/', $saved_password) ) {
ZM\Logger::Debug('bcrypt signature found, assumed bcrypt password');
2019-05-24 02:27:17 +08:00
$password_type = 'bcrypt';
$password_correct = $passwordHashed ? ($password == $saved_password) : password_verify($password, $saved_password);
}
// zmupdate.pl adds a '-ZM-' prefix to overlay encrypted passwords
// this is done so that we don't spend cycles doing two bcrypt password_verify calls
// for every wrong password entered. This will only be invoked for passwords zmupdate.pl has
// overlay hashed
2019-05-24 02:27:17 +08:00
else if ( substr($saved_password, 0,4) == '-ZM-' ) {
ZM\Logger::Debug("Detected bcrypt overlay hashing for $username");
2019-05-24 02:27:17 +08:00
$bcrypt_hash = substr($saved_password, 4);
$mysql_encoded_password = '*'.strtoupper(sha1(sha1($password, true)));
ZM\Logger::Debug("Comparing password $mysql_encoded_password to bcrypt hash: $bcrypt_hash");
$password_correct = password_verify($mysql_encoded_password, $bcrypt_hash);
2019-05-24 02:27:17 +08:00
$password_type = 'mysql'; // so we can migrate later down
} else {
// we really should nag the user not to use plain
ZM\Warning ('assuming plain text password as signature is not known. Please do not use plain, it is very insecure');
$password_type = 'plain';
$password_correct = ($saved_password == $password);
2018-04-07 02:31:11 +08:00
}
} else {
2019-05-24 02:27:17 +08:00
ZM\Error("Could not retrieve user $username details");
2019-05-02 01:22:24 +08:00
$_SESSION['loginFailed'] = true;
unset($user);
return false;
2018-04-07 02:31:11 +08:00
}
2019-05-02 01:22:24 +08:00
$close_session = 0;
if ( !is_session_started() ) {
session_start();
$close_session = 1;
}
2018-04-07 02:31:11 +08:00
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
2019-05-02 01:22:24 +08:00
2019-05-24 02:27:17 +08:00
if ( $password_correct ) {
ZM\Info("Login successful for user \"$username\"");
2019-05-02 01:22:24 +08:00
$user = $saved_user_details;
2019-05-24 02:27:17 +08:00
if ( $password_type == 'mysql' ) {
ZM\Info('Migrating password, if possible for future logins');
2019-05-02 01:22:24 +08:00
migrateHash($username, $password);
}
2018-04-07 02:31:11 +08:00
unset($_SESSION['loginFailed']);
if ( ZM_AUTH_TYPE == 'builtin' ) {
$_SESSION['passwordHash'] = $user['Password'];
}
$_SESSION['username'] = $user['Username'];
if ( ZM_AUTH_RELAY == 'plain' ) {
// Need to save this in session, can't use the value in User because it is hashed
$_SESSION['password'] = $_REQUEST['password'];
}
zm_session_regenerate_id();
2018-04-07 02:31:11 +08:00
} else {
ZM\Warning("Login denied for user \"$username\"");
2018-04-07 02:31:11 +08:00
$_SESSION['loginFailed'] = true;
2018-05-01 01:02:53 +08:00
unset($user);
2018-04-07 02:31:11 +08:00
}
if ( $close_session )
session_write_close();
return isset($user) ? $user: null;
2018-05-01 01:02:53 +08:00
} # end function userLogin
2018-04-07 02:31:11 +08:00
function userLogout() {
global $user;
ZM\Info('User "'.$user['Username'].'" logged out');
2018-05-01 01:02:53 +08:00
unset($user);
zm_session_clear();
2018-04-07 02:31:11 +08:00
}
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
function validateToken ($token, $allowed_token_type='access', $from_api_layer=false) {
global $user;
$key = ZM_AUTH_HASH_SECRET;
2019-05-09 02:07:48 +08:00
//if (ZM_AUTH_HASH_IPS) $key .= $_SERVER['REMOTE_ADDR'];
try {
$decoded_token = JWT::decode($token, $key, array('HS256'));
} catch (Exception $e) {
ZM\Error("Unable to authenticate user. error decoding JWT token:".$e->getMessage());
return array(false, $e->getMessage());
}
// convert from stdclass to array
$jwt_payload = json_decode(json_encode($decoded_token), true);
$type = $jwt_payload['type'];
2019-05-24 02:27:17 +08:00
if ( $type != $allowed_token_type ) {
if ( $allowed_token_type == 'access' ) {
// give a hint that the user is not doing it right
2019-05-24 02:27:17 +08:00
ZM\Error('Please do not use refresh tokens for this operation');
}
2019-05-24 02:27:17 +08:00
return array (false, 'Incorrect token type');
}
$username = $jwt_payload['user'];
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username = ?';
$sql_values = array($username);
2019-05-24 02:27:17 +08:00
$saved_user_details = dbFetchOne($sql, NULL, $sql_values);
2019-05-24 02:27:17 +08:00
if ( $saved_user_details ) {
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
if ($from_api_layer && $saved_user_details['APIEnabled'] == 0) {
// if from_api_layer is true, an additional check will be done
// to make sure APIs are enabled for this user. This is a good place
// to do it, since we are doing a DB dip here.
ZM\Error ("API is disabled for \"$username\"");
unset($user);
return array(false, 'API is disabled for user');
}
$issuedAt = $jwt_payload['iat'];
$minIssuedAt = $saved_user_details['TokenMinExpiry'];
2019-05-24 02:27:17 +08:00
if ( $issuedAt < $minIssuedAt ) {
ZM\Error("Token revoked for $username. Please generate a new token");
$_SESSION['loginFailed'] = true;
unset($user);
2019-05-24 02:27:17 +08:00
return array(false, 'Token revoked. Please re-generate');
}
$user = $saved_user_details;
2019-05-24 02:27:17 +08:00
return array($user, 'OK');
} else {
2019-05-24 02:27:17 +08:00
ZM\Error("Could not retrieve user $username details");
$_SESSION['loginFailed'] = true;
unset($user);
2019-05-24 02:27:17 +08:00
return array(false, 'No such user/credentials');
}
2019-05-24 02:27:17 +08:00
} // end function validateToken($token, $allowed_token_type='access')
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
function getAuthUser($auth, $from_api_layer = false) {
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') && !empty($auth) ) {
2018-04-07 02:31:11 +08:00
$remoteAddr = '';
if ( ZM_AUTH_HASH_IPS ) {
$remoteAddr = $_SERVER['REMOTE_ADDR'];
if ( !$remoteAddr ) {
ZM\Error("Can't determine remote address for authentication, using empty string");
2018-04-07 02:31:11 +08:00
$remoteAddr = '';
}
}
$values = array();
2018-04-13 06:43:57 +08:00
if ( isset($_SESSION['username']) ) {
2018-04-07 02:31:11 +08:00
# Most of the time we will be logged in already and the session will have our username, so we can significantly speed up our hash testing by only looking at our user.
# Only really important if you have a lot of users.
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND Username=?';
array_push($values, $_SESSION['username']);
2018-04-07 02:31:11 +08:00
} else {
$sql = 'SELECT * FROM Users WHERE Enabled = 1';
}
foreach ( dbFetchAll($sql, NULL, $values) as $user ) {
2018-04-07 02:31:11 +08:00
$now = time();
for ( $i = 0; $i < ZM_AUTH_HASH_TTL; $i++, $now -= 3600 ) { // Try for last TTL hours
2018-04-13 06:43:57 +08:00
$time = localtime($now);
2018-04-07 02:31:11 +08:00
$authKey = ZM_AUTH_HASH_SECRET.$user['Username'].$user['Password'].$remoteAddr.$time[2].$time[3].$time[4].$time[5];
2018-04-13 06:43:57 +08:00
$authHash = md5($authKey);
2018-04-07 02:31:11 +08:00
if ( $auth == $authHash ) {
Replace MySQL Password() with bcrypt, allow for alternate JWT tokens (#2598) * added sha1 and bcrypt submodules * added bcrypt and sha to src build process * added test sha1 and bcrypt code to validate working * bcrypt auth migration in PHP land * added include path * add sha source * added bcrypt to others * put link_dir ahead of add_executable * fixed typo * try add_library instead * absolute path * absolute path * build bcrypt as static * move to wrapper * move to fork * logs tweak * added lib-ssl/dev for JWT signing * Moved to openSSL SHA1, initial JWT plugin * removed vog * fixed SHA1 algo * typo * use php-jwt, use proper way to add PHP modules, via composer * fixed module path * first attempt to fix cast error * own fork * own fork * add composer vendor directory * go back to jwt-cpp as PR merged * moved to jwt-cpp after PR merge * New token= query for JWT * Add JWT token creation, move old code to a different function for future deprecation, simplified code for ZM_XX parameter reading * JWT integration, validate JWT token via validateToken * added token validation to zms/zmu/zmuser * add token to command line for zmu * move decode inside try/catch * exception handling for try/catch * fix db read, forgot to exec query * remove allowing auth_hash_ip for token * support refresh tokens as well for increased security * remove auth_hash_ip * Error out if used did not create an AUTH_HASH_SECRET * fixed type conversion * make sure refresh token login doesn't generate another refresh token * fix absolute path * move JWT/Bcrypt inside zm_crypt * move sha headers out * move out sha header * handle case when supplied password is hashed, fix wrong params in AppController * initial baby step for api tab * initial plumbing to introduce token expiry and API bans per user * remove M typo * display user table in api * added revoke all tokens code, removed test code * use strtoul for conversion * use strtoul for conversion * use strtoul for conversion * more fixes * more fixes * add mintokenexpiry to DB seek * typo * add ability to revoke tokens and enable/disable APIs per user * moved API enable back to system * comma * enable API options only if API enabled * move user creation to bcrypt * added password_compat for PHP >=5.3 <5.5 * add Password back so User object indexes don't change * move token index after adding password * demote logs * make old API auth optional, on by default * make old API auth mechanism optional * removed stale code * forgot to checkin update file * bulk overlay hash mysql encoded passwords * add back ssl_dev, got deleted * fix update script * added token support to index.php * reworked API document for new changes in 2.0 * Migrate from libdigest to crypt-eks-blowfish due to notice * merge typo * css classess for text that disappear * fixed html typo * added deps to ubuntu control files * spaces * removed extra line * when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist * add libssl1.0.0 for ubuntu 16/12 * small API fixes * clean up of API, remove redundant sections * moved to ZM fork for bcrypt * whitespace and google code style * regenerate auth hash if doing password migration * dont need AUTH HASH LOGIN to be on * Add auth hash verification to the user logged in already case * fix missing ] * reject requests if per user API disabled
2019-05-25 01:48:40 +08:00
if ($from_api_layer && $user['APIEnabled'] == 0) {
// if from_api_layer is true, an additional check will be done
// to make sure APIs are enabled for this user. This is a good place
// to do it, since we are doing a DB dip here.
ZM\Error ("API is disabled for \"".$user['Username']."\"");
unset($user);
return array(false, 'API is disabled for user');
}
else {
return $user;
}
2018-04-07 02:31:11 +08:00
}
} // end foreach hour
} // end foreach user
} // end if using auth hash
ZM\Error("Unable to authenticate user from auth hash '$auth'");
return null;
2018-04-07 02:31:11 +08:00
} // end getAuthUser($auth)
function generateAuthHash($useRemoteAddr, $force=false) {
if ( ZM_OPT_USE_AUTH and (ZM_AUTH_RELAY == 'hashed') and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) {
2018-04-07 02:31:11 +08:00
$time = time();
2019-05-05 23:24:55 +08:00
# We use 1800 so that we regenerate the hash at half the TTL
2018-04-07 02:31:11 +08:00
$mintime = $time - ( ZM_AUTH_HASH_TTL * 1800 );
if ( $force or ( !isset($_SESSION['AuthHash'.$_SESSION['remoteAddr']]) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) {
2018-04-07 02:31:11 +08:00
# Don't both regenerating Auth Hash if an hour hasn't gone by yet
$local_time = localtime();
$authKey = '';
if ( $useRemoteAddr ) {
$authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$_SESSION['remoteAddr'].$local_time[2].$local_time[3].$local_time[4].$local_time[5];
} else {
$authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$local_time[2].$local_time[3].$local_time[4].$local_time[5];
}
#ZM\Logger::Debug("Generated using hour:".$local_time[2] . ' mday:' . $local_time[3] . ' month:'.$local_time[4] . ' year: ' . $local_time[5] );
2018-05-01 01:02:53 +08:00
$auth = md5($authKey);
$close_session = 0;
if ( !is_session_started() ) {
session_start();
$close_session = 1;
}
$_SESSION['AuthHash'.$_SESSION['remoteAddr']] = $auth;
$_SESSION['AuthHashGeneratedAt'] = $time;
if ( $close_session )
session_write_close();
#ZM\Logger::Debug("Generated new auth $auth at " . $_SESSION['AuthHashGeneratedAt']. " using $authKey" );
#} else {
#ZM\Logger::Debug("Using cached auth " . $_SESSION['AuthHash'] ." beacuse generatedat:" . $_SESSION['AuthHashGeneratedAt'] . ' < now:'. $time . ' - ' . ZM_AUTH_HASH_TTL . ' * 1800 = '. $mintime);
2018-04-07 02:31:11 +08:00
} # end if AuthHash is not cached
return $_SESSION['AuthHash'.$_SESSION['remoteAddr']];
} # end if using AUTH and AUTH_RELAY
return '';
2018-04-07 02:31:11 +08:00
}
2018-05-01 01:02:53 +08:00
function visibleMonitor($mid) {
2018-04-07 02:31:11 +08:00
global $user;
2018-05-01 01:02:53 +08:00
return ( empty($user['MonitorIds']) || in_array($mid, explode(',', $user['MonitorIds'])) );
2018-04-07 02:31:11 +08:00
}
2018-05-01 01:02:53 +08:00
function canView($area, $mid=false) {
2018-04-07 02:31:11 +08:00
global $user;
2018-05-01 01:02:53 +08:00
return ( ($user[$area] == 'View' || $user[$area] == 'Edit') && ( !$mid || visibleMonitor($mid) ) );
2018-04-07 02:31:11 +08:00
}
2018-05-01 01:02:53 +08:00
function canEdit($area, $mid=false) {
2018-04-07 02:31:11 +08:00
global $user;
2018-05-01 01:02:53 +08:00
return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) ));
2018-04-07 02:31:11 +08:00
}
global $user;
if ( ZM_OPT_USE_AUTH ) {
$close_session = 0;
if ( !is_session_started() ) {
zm_session_start();
$close_session = 1;
}
if ( isset($_SESSION['username']) ) {
if ( ZM_AUTH_HASH_LOGINS and (ZM_AUTH_RELAY == 'hashed') ) {
# Extra validation, if logged in, then the auth hash will be set in the session, so we can validate it.
# This prevent session modification to switch users
if ( isset($_SESSION['AuthHash'.$_SESSION['remoteAddr']]) )
$user = getAuthUser($_SESSION['AuthHash'.$_SESSION['remoteAddr']]);
} else {
# Need to refresh permissions and validate that the user still exists
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username=?';
$user = dbFetchOne($sql, NULL, array($_SESSION['username']));
}
}
if ( ZM_AUTH_RELAY == 'plain' ) {
// Need to save this in session
$_SESSION['password'] = $user['Password'];
}
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) {
if ( $authUser = getAuthUser($_REQUEST['auth']) ) {
userLogin($authUser['Username'], $authUser['Password'], true);
}
} else if ( isset($_REQUEST['username']) and isset($_REQUEST['password']) ) {
userLogin($_REQUEST['username'], $_REQUEST['password'], false);
# Because it might have migrated the password we need to update the hash
generateAuthHash(ZM_AUTH_HASH_IPS, true);
}
2019-05-15 07:22:49 +08:00
if ( empty($user) && !empty($_REQUEST['token']) ) {
2019-05-15 07:22:49 +08:00
$ret = validateToken($_REQUEST['token'], 'access');
$user = $ret[0];
}
if ( !empty($user) ) {
// generate it once here, while session is open. Value will be cached in session and return when called later on
generateAuthHash(ZM_AUTH_HASH_IPS);
}
if ( $close_session )
session_write_close();
} else {
$user = $defaultUser;
}
2018-04-07 02:31:11 +08:00
?>