diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 787854091..e0af53f1f 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -245,7 +245,7 @@ DROP TABLE IF EXISTS `Events_Week`; CREATE TABLE `Events_Week` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartTime` datetime default NULL,M `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Week_MonitorId_idx` (`MonitorId`), @@ -640,6 +640,8 @@ CREATE TABLE `Users` ( `System` enum('None','View','Edit') NOT NULL default 'None', `MaxBandwidth` varchar(16), `MonitorIds` text, + `TokenMinExpiry` BIGINT UNSIGNED NOT NULL DEFAULT 0, + `APIEnabled` tinyint(3) UNSIGNED NOT NULL default 1, PRIMARY KEY (`Id`), UNIQUE KEY `UC_Username` (`Username`) ) ENGINE=@ZM_MYSQL_ENGINE@; diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 6d9af1460..845582137 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -7,8 +7,9 @@ // returns username if valid, "" if not -std::string verifyToken(std::string jwt_token_str, std::string key) { +std::pair verifyToken(std::string jwt_token_str, std::string key) { std::string username = ""; + int token_issued_at = 0; try { // is it decodable? 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(); if (type != "access") { Error ("Only access tokens are allowed. Please do not use refresh tokens"); - return ""; + return std::make_pair("",0); } } else { // something is wrong. All ZM tokens have type Error ("Missing token type. This should not happen"); - return ""; + return std::make_pair("",0); } if (decoded.has_payload_claim("user")) { username = decoded.get_payload_claim("user").as_string(); @@ -38,19 +39,27 @@ std::string verifyToken(std::string jwt_token_str, std::string key) { } else { 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 catch (const std::exception &e) { Error("Unable to verify token: %s", e.what()); - return ""; + return std::make_pair("",0); } catch (...) { 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) { diff --git a/src/zm_crypt.h b/src/zm_crypt.h index fd3bd7e85..340abc36c 100644 --- a/src/zm_crypt.h +++ b/src/zm_crypt.h @@ -27,5 +27,5 @@ 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 verifyToken(std::string token, std::string key); #endif // ZM_CRYPT_H \ No newline at end of file diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 7ac934d2a..b8009c221 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -154,7 +154,10 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { Info ("Inside zmLoadTokenUser, formed key=%s", key.c_str()); - std::string username = verifyToken(jwt_token_str, key); + std::pair ans = verifyToken(jwt_token_str, key); + std::string username = ans.first; + unsigned int iat = ans.second; + if (username != "") { char sql[ZM_SQL_MED_BUFSIZ] = ""; snprintf(sql, sizeof(sql), @@ -175,12 +178,21 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { if ( n_users != 1 ) { mysql_free_result(result); - Warning("Unable to authenticate user %s", username.c_str()); + Error("Unable to authenticate user %s", username.c_str()); return NULL; } MYSQL_ROW dbrow = mysql_fetch_row(result); 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()); mysql_free_result(result); return user; diff --git a/version b/version index 692c2e30d..c64ec5337 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.8 +1.33.9 diff --git a/web/includes/auth.php b/web/includes/auth.php index 6a076ec5d..559b478ce 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -109,6 +109,19 @@ function userLogin($username='', $password='', $passwordHashed=false, $apiLogin $password_type = NULL; 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']; if ($saved_password[0] == '*') { // 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); 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; return array($user, "OK"); } else { diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 09f3c09a9..e81f7fe6f 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -421,6 +421,7 @@ $SLANG = array( 'Images' => 'Images', 'Include' => 'Include', 'In' => 'In', + 'InvalidateTokens' => 'Invalidate all generated tokens', 'Inverted' => 'Inverted', 'Iris' => 'Iris', 'KeyString' => 'Key String', diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index 561964a85..90d9a5b02 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -29,7 +29,7 @@ $tabs = array(); $tabs['skins'] = translate('Display'); $tabs['system'] = translate('System'); $tabs['config'] = translate('Config'); -$tabs['config'] = translate('API'); +$tabs['API'] = translate('API'); $tabs['servers'] = translate('Servers'); $tabs['storage'] = translate('Storage'); $tabs['web'] = translate('Web'); @@ -134,7 +134,8 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI -
@@ -424,6 +425,32 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI + + + + HELLO BABY! + + >
+
+ + + + + +
@@ -432,6 +459,8 @@ foreach ( array_map('basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI } ?> + +