Merge branch 'pliablepixels-crypt-replacement3' into storageareas
This commit is contained in:
commit
4466ef13fd
|
@ -7,7 +7,7 @@
|
||||||
url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git
|
url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git
|
||||||
[submodule "third_party/bcrypt"]
|
[submodule "third_party/bcrypt"]
|
||||||
path = third_party/bcrypt
|
path = third_party/bcrypt
|
||||||
url = https://github.com/pliablepixels/libbcrypt
|
url = https://github.com/ZoneMinder/libbcrypt
|
||||||
[submodule "third_party/jwt-cpp"]
|
[submodule "third_party/jwt-cpp"]
|
||||||
path = third_party/jwt-cpp
|
path = third_party/jwt-cpp
|
||||||
url = https://github.com/Thalhammer/jwt-cpp
|
url = https://github.com/Thalhammer/jwt-cpp
|
||||||
|
|
|
@ -112,21 +112,23 @@ sub interpret_messages {
|
||||||
my @results;
|
my @results;
|
||||||
foreach my $response ( @responses ) {
|
foreach my $response ( @responses ) {
|
||||||
|
|
||||||
if($verbose) {
|
if ( $verbose ) {
|
||||||
print "Received message:\n" . $response . "\n";
|
print "Received message:\n" . $response . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $result = deserialize_message($svc_discover, $response);
|
my $result = deserialize_message($svc_discover, $response);
|
||||||
if(not $result) {
|
if ( not $result ) {
|
||||||
print "Error deserializing message. No message returned from deserializer.\n" if $verbose;
|
print "Error deserializing message. No message returned from deserializer.\n" if $verbose;
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $xaddr;
|
my $xaddr;
|
||||||
foreach my $l_xaddr (split ' ', $result->get_ProbeMatch()->get_XAddrs()) {
|
my $probe_match = $result->get_ProbeMatch();
|
||||||
|
next if ! $probe_match;
|
||||||
|
foreach my $l_xaddr (split ' ', $probe_match->get_XAddrs()) {
|
||||||
# find IPv4 address
|
# find IPv4 address
|
||||||
print "l_xaddr = $l_xaddr\n" if $verbose;
|
print "l_xaddr = $l_xaddr\n" if $verbose;
|
||||||
if($l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]|) {
|
if ( $l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]| ) {
|
||||||
$xaddr = $l_xaddr;
|
$xaddr = $l_xaddr;
|
||||||
last;
|
last;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -26,25 +26,23 @@ use \Firebase\JWT\JWT;
|
||||||
// this function migrates mysql hashing to bcrypt, if you are using PHP >= 5.5
|
// 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
|
// will be called after successful login, only if mysql hashing is detected
|
||||||
function migrateHash($user, $pass) {
|
function migrateHash($user, $pass) {
|
||||||
if (function_exists('password_hash')) {
|
if ( function_exists('password_hash') ) {
|
||||||
ZM\Info ("Migrating $user to bcrypt scheme");
|
ZM\Info("Migrating $user to bcrypt scheme");
|
||||||
// let it generate its own salt, and ensure bcrypt as PASSWORD_DEFAULT may change later
|
// 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
|
// we can modify this later to support argon2 etc as switch to its own password signature detection
|
||||||
$bcrypt_hash = password_hash($pass, PASSWORD_BCRYPT);
|
$bcrypt_hash = password_hash($pass, PASSWORD_BCRYPT);
|
||||||
//ZM\Info ("hased bcrypt $pass is $bcrypt_hash");
|
//ZM\Info ("hased bcrypt $pass is $bcrypt_hash");
|
||||||
$update_password_sql = 'UPDATE Users SET Password=\''.$bcrypt_hash.'\' WHERE Username=\''.$user.'\'';
|
$update_password_sql = 'UPDATE Users SET Password=\''.$bcrypt_hash.'\' WHERE Username=\''.$user.'\'';
|
||||||
ZM\Info ($update_password_sql);
|
ZM\Info($update_password_sql);
|
||||||
dbQuery($update_password_sql);
|
dbQuery($update_password_sql);
|
||||||
}
|
# Since password field has changed, existing auth_hash is no longer valid
|
||||||
else {
|
generateAuthHash(ZM_AUTH_HASH_IPS, true);
|
||||||
|
} else {
|
||||||
ZM\Info ('Cannot migrate password scheme to bcrypt, as you are using PHP < 5.3');
|
ZM\Info('Cannot migrate password scheme to bcrypt, as you are using PHP < 5.3');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// core function used to login a user to PHP. Is also used for cake sessions for the API
|
// core function used to login a user to PHP. Is also used for cake sessions for the API
|
||||||
function userLogin($username='', $password='', $passwordHashed=false, $apiLogin = false) {
|
function userLogin($username='', $password='', $passwordHashed=false, $apiLogin = false) {
|
||||||
|
|
||||||
|
@ -58,7 +56,7 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
|
||||||
// if true, a popup will display after login
|
// if true, a popup will display after login
|
||||||
// lets validate reCaptcha if it exists
|
// lets validate reCaptcha if it exists
|
||||||
// this only applies if it userLogin was not called from API layer
|
// this only applies if it userLogin was not called from API layer
|
||||||
if (!$apiLogin
|
if ( !$apiLogin
|
||||||
&& defined('ZM_OPT_USE_GOOG_RECAPTCHA')
|
&& defined('ZM_OPT_USE_GOOG_RECAPTCHA')
|
||||||
&& defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY')
|
&& defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY')
|
||||||
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
|
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
|
||||||
|
@ -73,17 +71,17 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
|
||||||
'remoteip' => $_SERVER['REMOTE_ADDR']
|
'remoteip' => $_SERVER['REMOTE_ADDR']
|
||||||
);
|
);
|
||||||
$res = do_post_request($url, http_build_query($fields));
|
$res = do_post_request($url, http_build_query($fields));
|
||||||
$responseData = json_decode($res,true);
|
$responseData = json_decode($res, true);
|
||||||
// credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php
|
// credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php
|
||||||
// if recaptcha resulted in error, we might have to deny login
|
// if recaptcha resulted in error, we might have to deny login
|
||||||
if ( isset($responseData['success']) && $responseData['success'] == false ) {
|
if ( isset($responseData['success']) && ($responseData['success'] == false) ) {
|
||||||
// PP - before we deny auth, let's make sure the error was not 'invalid secret'
|
// 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
|
// 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
|
// 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
|
// 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
|
// as it produces the same error as when you don't answer a recaptcha
|
||||||
if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
|
if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
|
||||||
if ( !in_array('invalid-input-secret',$responseData['error-codes']) ) {
|
if ( !in_array('invalid-input-secret', $responseData['error-codes']) ) {
|
||||||
Error('reCaptcha authentication failed');
|
Error('reCaptcha authentication failed');
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -102,18 +100,18 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
|
||||||
// First retrieve the stored password
|
// First retrieve the stored password
|
||||||
// and move password hashing to application space
|
// and move password hashing to application space
|
||||||
|
|
||||||
$saved_user_details = dbFetchOne ($sql, NULL, $sql_values);
|
$saved_user_details = dbFetchOne($sql, NULL, $sql_values);
|
||||||
$password_correct = false;
|
$password_correct = false;
|
||||||
$password_type = NULL;
|
$password_type = NULL;
|
||||||
|
|
||||||
if ($saved_user_details) {
|
if ( $saved_user_details ) {
|
||||||
|
|
||||||
// if the API layer asked us to login, make sure the user
|
// if the API layer asked us to login, make sure the user
|
||||||
// has API enabled (admin may have banned API for this user)
|
// has API enabled (admin may have banned API for this user)
|
||||||
|
|
||||||
if ($apiLogin) {
|
if ( $apiLogin ) {
|
||||||
if ($saved_user_details['APIEnabled'] != 1) {
|
if ( $saved_user_details['APIEnabled'] != 1 ) {
|
||||||
ZM\Error ("API disabled for: $username");
|
ZM\Error("API disabled for: $username");
|
||||||
$_SESSION['loginFailed'] = true;
|
$_SESSION['loginFailed'] = true;
|
||||||
unset($user);
|
unset($user);
|
||||||
return false;
|
return false;
|
||||||
|
@ -121,45 +119,39 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
|
||||||
}
|
}
|
||||||
|
|
||||||
$saved_password = $saved_user_details['Password'];
|
$saved_password = $saved_user_details['Password'];
|
||||||
if ($saved_password[0] == '*') {
|
if ( $saved_password[0] == '*' ) {
|
||||||
// We assume we don't need to support mysql < 4.1
|
// 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
|
// Starting MY SQL 4.1, mysql concats a '*' in front of its password hash
|
||||||
// https://blog.pythian.com/hashing-algorithm-in-mysql-password-2/
|
// https://blog.pythian.com/hashing-algorithm-in-mysql-password-2/
|
||||||
ZM\Logger::Debug ('Saved password is using MYSQL password function');
|
ZM\Logger::Debug('Saved password is using MYSQL password function');
|
||||||
$input_password_hash ='*'.strtoupper(sha1(sha1($password, true)));
|
$input_password_hash = '*'.strtoupper(sha1(sha1($password, true)));
|
||||||
$password_correct = ($saved_password == $input_password_hash);
|
$password_correct = ($saved_password == $input_password_hash);
|
||||||
$password_type = 'mysql';
|
$password_type = 'mysql';
|
||||||
|
|
||||||
}
|
} else if ( preg_match('/^\$2[ayb]\$.+$/', $saved_password) ) {
|
||||||
elseif (preg_match('/^\$2[ayb]\$.+$/', $saved_password)) {
|
|
||||||
ZM\Logger::Debug('bcrypt signature found, assumed bcrypt password');
|
ZM\Logger::Debug('bcrypt signature found, assumed bcrypt password');
|
||||||
$password_type='bcrypt';
|
$password_type = 'bcrypt';
|
||||||
$password_correct = $passwordHashed? ($password == $saved_password) : password_verify($password, $saved_password);
|
$password_correct = $passwordHashed ? ($password == $saved_password) : password_verify($password, $saved_password);
|
||||||
}
|
}
|
||||||
// zmupdate.pl adds a '-ZM-' prefix to overlay encrypted passwords
|
// 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
|
// 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
|
// for every wrong password entered. This will only be invoked for passwords zmupdate.pl has
|
||||||
// overlay hashed
|
// overlay hashed
|
||||||
elseif (substr($saved_password, 0,4) == '-ZM-') {
|
else if ( substr($saved_password, 0,4) == '-ZM-' ) {
|
||||||
ZM\Logger::Debug("Detected bcrypt overlay hashing for $username");
|
ZM\Logger::Debug("Detected bcrypt overlay hashing for $username");
|
||||||
ZM\Info("Detected bcrypt overlay hashing for $username");
|
$bcrypt_hash = substr($saved_password, 4);
|
||||||
$bcrypt_hash = substr ($saved_password, 4);
|
$mysql_encoded_password = '*'.strtoupper(sha1(sha1($password, true)));
|
||||||
$mysql_encoded_password ='*'.strtoupper(sha1(sha1($password, true)));
|
|
||||||
ZM\Logger::Debug("Comparing password $mysql_encoded_password to bcrypt hash: $bcrypt_hash");
|
ZM\Logger::Debug("Comparing password $mysql_encoded_password to bcrypt hash: $bcrypt_hash");
|
||||||
ZM\Info("Comparing password $mysql_encoded_password to bcrypt hash: $bcrypt_hash");
|
|
||||||
$password_correct = password_verify($mysql_encoded_password, $bcrypt_hash);
|
$password_correct = password_verify($mysql_encoded_password, $bcrypt_hash);
|
||||||
$password_type = "mysql"; // so we can migrate later down
|
$password_type = 'mysql'; // so we can migrate later down
|
||||||
|
} else {
|
||||||
}
|
// we really should nag the user not to use plain
|
||||||
else {
|
ZM\Warning ('assuming plain text password as signature is not known. Please do not use plain, it is very insecure');
|
||||||
// we really should nag the user not to use plain
|
$password_type = 'plain';
|
||||||
ZM\Warning ('assuming plain text password as signature is not known. Please do not use plain, it is very insecure');
|
$password_correct = ($saved_password == $password);
|
||||||
$password_type = 'plain';
|
|
||||||
$password_correct = ($saved_password == $password);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ZM\Error ("Could not retrieve user $username details");
|
ZM\Error("Could not retrieve user $username details");
|
||||||
$_SESSION['loginFailed'] = true;
|
$_SESSION['loginFailed'] = true;
|
||||||
unset($user);
|
unset($user);
|
||||||
return false;
|
return false;
|
||||||
|
@ -172,10 +164,10 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
|
||||||
}
|
}
|
||||||
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
|
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
|
||||||
|
|
||||||
if ($password_correct) {
|
if ( $password_correct ) {
|
||||||
ZM\Info("Login successful for user \"$username\"");
|
ZM\Info("Login successful for user \"$username\"");
|
||||||
$user = $saved_user_details;
|
$user = $saved_user_details;
|
||||||
if ($password_type == 'mysql') {
|
if ( $password_type == 'mysql' ) {
|
||||||
ZM\Info('Migrating password, if possible for future logins');
|
ZM\Info('Migrating password, if possible for future logins');
|
||||||
migrateHash($username, $password);
|
migrateHash($username, $password);
|
||||||
}
|
}
|
||||||
|
@ -224,42 +216,41 @@ function validateToken ($token, $allowed_token_type='access') {
|
||||||
$jwt_payload = json_decode(json_encode($decoded_token), true);
|
$jwt_payload = json_decode(json_encode($decoded_token), true);
|
||||||
|
|
||||||
$type = $jwt_payload['type'];
|
$type = $jwt_payload['type'];
|
||||||
if ($type != $allowed_token_type) {
|
if ( $type != $allowed_token_type ) {
|
||||||
if ($allowed_token_type == 'access') {
|
if ( $allowed_token_type == 'access' ) {
|
||||||
// give a hint that the user is not doing it right
|
// give a hint that the user is not doing it right
|
||||||
ZM\Error ('Please do not use refresh tokens for this operation');
|
ZM\Error('Please do not use refresh tokens for this operation');
|
||||||
}
|
}
|
||||||
return array (false, "Incorrect token type");
|
return array (false, 'Incorrect token type');
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $jwt_payload['user'];
|
$username = $jwt_payload['user'];
|
||||||
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username = ?';
|
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username = ?';
|
||||||
$sql_values = array($username);
|
$sql_values = array($username);
|
||||||
|
|
||||||
$saved_user_details = dbFetchOne ($sql, NULL, $sql_values);
|
$saved_user_details = dbFetchOne($sql, NULL, $sql_values);
|
||||||
|
|
||||||
if ($saved_user_details) {
|
if ( $saved_user_details ) {
|
||||||
|
|
||||||
$issuedAt = $jwt_payload['iat'];
|
$issuedAt = $jwt_payload['iat'];
|
||||||
$minIssuedAt = $saved_user_details['TokenMinExpiry'];
|
$minIssuedAt = $saved_user_details['TokenMinExpiry'];
|
||||||
|
|
||||||
if ($issuedAt < $minIssuedAt) {
|
if ( $issuedAt < $minIssuedAt ) {
|
||||||
ZM\Error ("Token revoked for \"$username\". Please generate a new token");
|
ZM\Error("Token revoked for $username. Please generate a new token");
|
||||||
$_SESSION['loginFailed'] = true;
|
$_SESSION['loginFailed'] = true;
|
||||||
unset($user);
|
unset($user);
|
||||||
return array(false, "Token revoked. Please re-generate");
|
return array(false, 'Token revoked. Please re-generate');
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $saved_user_details;
|
$user = $saved_user_details;
|
||||||
return array($user, "OK");
|
return array($user, 'OK');
|
||||||
} else {
|
} else {
|
||||||
ZM\Error ("Could not retrieve user \"$username\" details");
|
ZM\Error("Could not retrieve user $username details");
|
||||||
$_SESSION['loginFailed'] = true;
|
$_SESSION['loginFailed'] = true;
|
||||||
unset($user);
|
unset($user);
|
||||||
return array(false, "No such user/credentials");
|
return array(false, 'No such user/credentials');
|
||||||
}
|
}
|
||||||
|
} // end function validateToken($token, $allowed_token_type='access')
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthUser($auth) {
|
function getAuthUser($auth) {
|
||||||
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) {
|
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) {
|
||||||
|
@ -299,8 +290,6 @@ function getAuthUser($auth) {
|
||||||
return false;
|
return false;
|
||||||
} // end getAuthUser($auth)
|
} // end getAuthUser($auth)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function generateAuthHash($useRemoteAddr, $force=false) {
|
function generateAuthHash($useRemoteAddr, $force=false) {
|
||||||
if ( ZM_OPT_USE_AUTH and ZM_AUTH_RELAY == 'hashed' and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) {
|
if ( ZM_OPT_USE_AUTH and ZM_AUTH_RELAY == 'hashed' and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) {
|
||||||
$time = time();
|
$time = time();
|
||||||
|
@ -367,9 +356,15 @@ if ( ZM_OPT_USE_AUTH ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset($_SESSION['username']) ) {
|
if ( isset($_SESSION['username']) ) {
|
||||||
# Need to refresh permissions and validate that the user still exists
|
if ( ZM_AUTH_HASH_LOGINS ) {
|
||||||
$sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username=?';
|
# Extra validation, if logged in, then the auth hash will be set in the session, so we can validate it.
|
||||||
$user = dbFetchOne($sql, NULL, array($_SESSION['username']));
|
# This prevent session modification to switch users
|
||||||
|
$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' ) {
|
if ( ZM_AUTH_RELAY == 'plain' ) {
|
||||||
|
@ -379,7 +374,6 @@ if ( ZM_OPT_USE_AUTH ) {
|
||||||
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
|
$_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking
|
||||||
|
|
||||||
if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) {
|
if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) {
|
||||||
|
|
||||||
if ( $authUser = getAuthUser($_REQUEST['auth']) ) {
|
if ( $authUser = getAuthUser($_REQUEST['auth']) ) {
|
||||||
userLogin($authUser['Username'], $authUser['Password'], true);
|
userLogin($authUser['Username'], $authUser['Password'], true);
|
||||||
}
|
}
|
||||||
|
@ -387,7 +381,7 @@ if ( ZM_OPT_USE_AUTH ) {
|
||||||
userLogin($_REQUEST['username'], $_REQUEST['password'], false);
|
userLogin($_REQUEST['username'], $_REQUEST['password'], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['token']) ) {
|
if (empty($user) && !empty($_REQUEST['token']) ) {
|
||||||
|
|
||||||
$ret = validateToken($_REQUEST['token'], 'access');
|
$ret = validateToken($_REQUEST['token'], 'access');
|
||||||
$user = $ret[0];
|
$user = $ret[0];
|
||||||
|
|
Loading…
Reference in New Issue