initial plumbing to introduce token expiry and API bans per user
This commit is contained in:
parent
e6b7af4583
commit
ae14be916c
|
@ -245,7 +245,7 @@ DROP TABLE IF EXISTS `Events_Week`;
|
||||||
CREATE TABLE `Events_Week` (
|
CREATE TABLE `Events_Week` (
|
||||||
`EventId` BIGINT unsigned NOT NULL,
|
`EventId` BIGINT unsigned NOT NULL,
|
||||||
`MonitorId` int(10) unsigned NOT NULL,
|
`MonitorId` int(10) unsigned NOT NULL,
|
||||||
`StartTime` datetime default NULL,
|
`StartTime` datetime default NULL,M
|
||||||
`DiskSpace` bigint default NULL,
|
`DiskSpace` bigint default NULL,
|
||||||
PRIMARY KEY (`EventId`),
|
PRIMARY KEY (`EventId`),
|
||||||
KEY `Events_Week_MonitorId_idx` (`MonitorId`),
|
KEY `Events_Week_MonitorId_idx` (`MonitorId`),
|
||||||
|
@ -640,6 +640,8 @@ CREATE TABLE `Users` (
|
||||||
`System` enum('None','View','Edit') NOT NULL default 'None',
|
`System` enum('None','View','Edit') NOT NULL default 'None',
|
||||||
`MaxBandwidth` varchar(16),
|
`MaxBandwidth` varchar(16),
|
||||||
`MonitorIds` text,
|
`MonitorIds` text,
|
||||||
|
`TokenMinExpiry` BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`APIEnabled` tinyint(3) UNSIGNED NOT NULL default 1,
|
||||||
PRIMARY KEY (`Id`),
|
PRIMARY KEY (`Id`),
|
||||||
UNIQUE KEY `UC_Username` (`Username`)
|
UNIQUE KEY `UC_Username` (`Username`)
|
||||||
) ENGINE=@ZM_MYSQL_ENGINE@;
|
) ENGINE=@ZM_MYSQL_ENGINE@;
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
|
|
||||||
// returns username if valid, "" if not
|
// returns username if valid, "" if not
|
||||||
std::string verifyToken(std::string jwt_token_str, std::string key) {
|
std::pair <std::string, unsigned int> verifyToken(std::string jwt_token_str, std::string key) {
|
||||||
std::string username = "";
|
std::string username = "";
|
||||||
|
int token_issued_at = 0;
|
||||||
try {
|
try {
|
||||||
// is it decodable?
|
// is it decodable?
|
||||||
auto decoded = jwt::decode(jwt_token_str);
|
auto decoded = jwt::decode(jwt_token_str);
|
||||||
|
@ -24,13 +25,13 @@ std::string verifyToken(std::string jwt_token_str, std::string key) {
|
||||||
std::string type = decoded.get_payload_claim("type").as_string();
|
std::string type = decoded.get_payload_claim("type").as_string();
|
||||||
if (type != "access") {
|
if (type != "access") {
|
||||||
Error ("Only access tokens are allowed. Please do not use refresh tokens");
|
Error ("Only access tokens are allowed. Please do not use refresh tokens");
|
||||||
return "";
|
return std::make_pair("",0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// something is wrong. All ZM tokens have type
|
// something is wrong. All ZM tokens have type
|
||||||
Error ("Missing token type. This should not happen");
|
Error ("Missing token type. This should not happen");
|
||||||
return "";
|
return std::make_pair("",0);
|
||||||
}
|
}
|
||||||
if (decoded.has_payload_claim("user")) {
|
if (decoded.has_payload_claim("user")) {
|
||||||
username = decoded.get_payload_claim("user").as_string();
|
username = decoded.get_payload_claim("user").as_string();
|
||||||
|
@ -38,19 +39,27 @@ std::string verifyToken(std::string jwt_token_str, std::string key) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Error ("User not found in claim");
|
Error ("User not found in claim");
|
||||||
return "";
|
return std::make_pair("",0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.has_payload_claim("iat")) {
|
||||||
|
token_issued_at = (unsigned int) (decoded.get_payload_claim("iat").as_int());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Error ("IAT not found in claim. This should not happen");
|
||||||
|
return std::make_pair("",0);
|
||||||
}
|
}
|
||||||
} // try
|
} // try
|
||||||
catch (const std::exception &e) {
|
catch (const std::exception &e) {
|
||||||
Error("Unable to verify token: %s", e.what());
|
Error("Unable to verify token: %s", e.what());
|
||||||
return "";
|
return std::make_pair("",0);
|
||||||
}
|
}
|
||||||
catch (...) {
|
catch (...) {
|
||||||
Error ("unknown exception");
|
Error ("unknown exception");
|
||||||
return "";
|
return std::make_pair("",0);
|
||||||
|
|
||||||
}
|
}
|
||||||
return username;
|
return std::make_pair(username,token_issued_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) {
|
bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) {
|
||||||
|
|
|
@ -27,5 +27,5 @@
|
||||||
|
|
||||||
bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash);
|
bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash);
|
||||||
|
|
||||||
std::string verifyToken(std::string token, std::string key);
|
std::pair <std::string, unsigned int> verifyToken(std::string token, std::string key);
|
||||||
#endif // ZM_CRYPT_H
|
#endif // ZM_CRYPT_H
|
|
@ -154,7 +154,10 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) {
|
||||||
|
|
||||||
Info ("Inside zmLoadTokenUser, formed key=%s", key.c_str());
|
Info ("Inside zmLoadTokenUser, formed key=%s", key.c_str());
|
||||||
|
|
||||||
std::string username = verifyToken(jwt_token_str, key);
|
std::pair<std::string, unsigned int> ans = verifyToken(jwt_token_str, key);
|
||||||
|
std::string username = ans.first;
|
||||||
|
unsigned int iat = ans.second;
|
||||||
|
|
||||||
if (username != "") {
|
if (username != "") {
|
||||||
char sql[ZM_SQL_MED_BUFSIZ] = "";
|
char sql[ZM_SQL_MED_BUFSIZ] = "";
|
||||||
snprintf(sql, sizeof(sql),
|
snprintf(sql, sizeof(sql),
|
||||||
|
@ -175,12 +178,21 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) {
|
||||||
|
|
||||||
if ( n_users != 1 ) {
|
if ( n_users != 1 ) {
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
Warning("Unable to authenticate user %s", username.c_str());
|
Error("Unable to authenticate user %s", username.c_str());
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
MYSQL_ROW dbrow = mysql_fetch_row(result);
|
MYSQL_ROW dbrow = mysql_fetch_row(result);
|
||||||
User *user = new User(dbrow);
|
User *user = new User(dbrow);
|
||||||
|
unsigned int stored_iat = strtoul(dbrow[14], NULL,0 );
|
||||||
|
|
||||||
|
if (stored_iat > iat ) { // admin revoked tokens
|
||||||
|
mysql_free_result(result);
|
||||||
|
Error("Token was revoked for %s", username.c_str());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Info ("Got stored expiry time of %u",stored_iat);
|
||||||
Info ("Authenticated user '%s' via token", username.c_str());
|
Info ("Authenticated user '%s' via token", username.c_str());
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
return user;
|
return user;
|
||||||
|
|
|
@ -109,6 +109,19 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin
|
||||||
$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
|
||||||
|
// has API enabled (admin may have banned API for this user)
|
||||||
|
|
||||||
|
if ($apiLogin) {
|
||||||
|
if ($saved_user_details['APIEnabled'] != 1) {
|
||||||
|
ZM\Error ("API disabled for: $username");
|
||||||
|
$_SESSION['loginFailed'] = true;
|
||||||
|
unset($user);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$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
|
||||||
|
@ -217,6 +230,17 @@ function validateToken ($token, $allowed_token_type='access') {
|
||||||
$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'];
|
||||||
|
$minIssuedAt = $saved_user_details['TokenMinExpiry'];
|
||||||
|
|
||||||
|
if ($issuedAt < $minIssuedAt) {
|
||||||
|
ZM\Error ("Token revoked for $username. Please generate a new token");
|
||||||
|
$_SESSION['loginFailed'] = true;
|
||||||
|
unset($user);
|
||||||
|
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 {
|
||||||
|
|
|
@ -421,6 +421,7 @@ $SLANG = array(
|
||||||
'Images' => 'Images',
|
'Images' => 'Images',
|
||||||
'Include' => 'Include',
|
'Include' => 'Include',
|
||||||
'In' => 'In',
|
'In' => 'In',
|
||||||
|
'InvalidateTokens' => 'Invalidate all generated tokens',
|
||||||
'Inverted' => 'Inverted',
|
'Inverted' => 'Inverted',
|
||||||
'Iris' => 'Iris',
|
'Iris' => 'Iris',
|
||||||
'KeyString' => 'Key String',
|
'KeyString' => 'Key String',
|
||||||
|
|
|
@ -29,7 +29,7 @@ $tabs = array();
|
||||||
$tabs['skins'] = translate('Display');
|
$tabs['skins'] = translate('Display');
|
||||||
$tabs['system'] = translate('System');
|
$tabs['system'] = translate('System');
|
||||||
$tabs['config'] = translate('Config');
|
$tabs['config'] = translate('Config');
|
||||||
$tabs['config'] = translate('API');
|
$tabs['API'] = translate('API');
|
||||||
$tabs['servers'] = translate('Servers');
|
$tabs['servers'] = translate('Servers');
|
||||||
$tabs['storage'] = translate('Storage');
|
$tabs['storage'] = translate('Storage');
|
||||||
$tabs['web'] = translate('Web');
|
$tabs['web'] = translate('Web');
|
||||||
|
@ -134,7 +134,8 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<?php
|
|
||||||
|
<?php
|
||||||
} else if ( $tab == 'users' ) {
|
} else if ( $tab == 'users' ) {
|
||||||
?>
|
?>
|
||||||
<form name="userForm" method="post" action="?">
|
<form name="userForm" method="post" action="?">
|
||||||
|
@ -424,6 +425,32 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($tab == 'API') {
|
||||||
|
?>
|
||||||
|
|
||||||
|
HELLO BABY!
|
||||||
|
<form method="post">
|
||||||
|
<input type="submit" name="test" id="test"
|
||||||
|
value=<?php echo translate("Image") ?> ><br/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
function testfun()
|
||||||
|
{
|
||||||
|
echo "Your test function on button click is working";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(array_key_exists('test',$_POST)){
|
||||||
|
testfun();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="contentButtons">
|
<div id="contentButtons">
|
||||||
<button type="submit" value="Save"<?php echo $canEdit?'':' disabled="disabled"' ?>><?php echo translate('Save') ?></button>
|
<button type="submit" value="Save"<?php echo $canEdit?'':' disabled="disabled"' ?>><?php echo translate('Save') ?></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -432,6 +459,8 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div><!-- end #options -->
|
</div><!-- end #options -->
|
||||||
</div>
|
</div>
|
||||||
</div> <!-- end row -->
|
</div> <!-- end row -->
|
||||||
|
|
Loading…
Reference in New Issue