From 708138db5f95f09812ef93ca5d12848c980f7c54 Mon Sep 17 00:00:00 2001 From: PavelBegunkov <asml.silence@gmail.com> Date: Wed, 10 Aug 2016 13:49:32 +0300 Subject: [PATCH] #120 add auth tokens to db & model --- db/constraints/base.sql | 6 + db/migrations/V12_3__auth_token.sql | 100 +++++++++++++++ db/stored/functions.sql | 114 +++++++++++++---- db/stored/procedures.sql | 20 +++ db/structure/base.sql | 18 ++- .../application/classes/FileParser.php | 2 +- .../application/classes/Model/Account.php | 121 +++++++++++------- .../application/classes/Model/Student.php | 23 ++++ 8 files changed, 330 insertions(+), 74 deletions(-) create mode 100644 db/migrations/V12_3__auth_token.sql diff --git a/db/constraints/base.sql b/db/constraints/base.sql index a06dc655c..ba1384cbd 100644 --- a/db/constraints/base.sql +++ b/db/constraints/base.sql @@ -143,3 +143,9 @@ ALTER TABLE `exam_period_options` ALTER TABLE `compound_disciplines` ADD CONSTRAINT `compound_disciplines_ibfk_1` FOREIGN KEY (`SpecializationID`) REFERENCES `specializations` (`ID`), ADD CONSTRAINT `compound_disciplines_ibfk_2` FOREIGN KEY (`GradeID`) REFERENCES `grades` (`ID`); + +-- +-- Ограничения внешнего ключа таблицы `auth_tokens` +-- +ALTER TABLE `auth_tokens` + ADD CONSTRAINT `auth_tokens_ibfk_1` FOREIGN KEY (`AccountID`) REFERENCES `accounts` (`ID`); diff --git a/db/migrations/V12_3__auth_token.sql b/db/migrations/V12_3__auth_token.sql new file mode 100644 index 000000000..0ae0625eb --- /dev/null +++ b/db/migrations/V12_3__auth_token.sql @@ -0,0 +1,100 @@ +START TRANSACTION; + +CREATE TABLE `auth_tokens` ( + `Token` char(40) charset ascii NOT NULL, + `AccountID` int(11) NOT NULL, + `Created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `Accessed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `Mask` int(11) NOT NULL DEFAULT 0, + UNIQUE KEY `Token` (`Token`), + KEY `AccountID` (`AccountID`) +); + +ALTER TABLE `auth_tokens` + ADD CONSTRAINT `auth_tokens_ibfk_1` FOREIGN KEY (`AccountID`) REFERENCES `accounts` (`ID`); + + +DELIMITER // + +-- returns: +-- -1 : invalid token +-- >0 : accountID +DROP FUNCTION IF EXISTS SignInByToken// +CREATE FUNCTION SignInByToken (pToken char(40) charset ascii) RETURNS int(11) # account id +NO SQL +BEGIN + DECLARE vAccountID INT DEFAULT -1; + + SELECT auth_tokens.AccountID INTO vAccountID + FROM auth_tokens + WHERE auth_tokens.Token = pToken + LIMIT 1; + + IF vAccountID = -1 THEN + RETURN -1; -- token not found + END IF; + + UPDATE auth_tokens + SET Accessed = CURRENT_TIMESTAMP + WHERE auth_tokens.Token = pToken + LIMIT 1; + + INSERT INTO logs_signin(AccountID) VALUES (vAccountID); + RETURN vAccountID; +END // + +DROP FUNCTION IF EXISTS DeleteAuthToken// +CREATE FUNCTION DeleteAuthToken(pToken char(40) CHARSET ascii) RETURNS int(11) +NO SQL +BEGIN + DELETE FROM auth_tokens + WHERE auth_tokens.Token = pToken; + RETURN ROW_COUNT()-1; +END// + +DROP FUNCTION IF EXISTS CreateAuthToken// +CREATE FUNCTION CreateAuthToken( + pAccountID int(11), + pRightMask int(11) +) RETURNS char(40) charset ascii +NO SQL +BEGIN + DECLARE vCounter int(11) DEFAULT 666; + DECLARE vCreated boolean DEFAULT FALSE; + DECLARE vSeed int(11) DEFAULT FLOOR(4294967296 * RAND(CURRENT_TIMESTAMP ^ LAST_INSERT_ID() ^ (pAccountID << 10))); + DECLARE vToken char(40) charset ascii DEFAULT SHA1(vSeed); + + WHILE NOT vCreated AND vCounter > 0 DO + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET vCreated = FALSE; + SET vToken = SHA1(RAND()); + INSERT INTO auth_tokens(Token, AccountID, Mask) VALUES (vToken, pAccountID, pRightMask); + + SET vCreated = TRUE; + SET vCounter = vCounter - 1; + END WHILE; + + RETURN IF(vCreated, vToken, ''); +END// + +-- pAccountID == 0 - get tokens of all users +DROP PROCEDURE IF EXISTS GetAuthTokens// +CREATE PROCEDURE GetAuthTokens( + IN pAccountID int(11)) +BEGIN + -- accountID, Created, Accessed, Mask + IF pAccountID = 0 THEN + SELECT auth_tokens.* + FROM auth_tokens; + ELSE + SELECT auth_tokens.* + FROM auth_tokens + WHERE auth_tokens.AccountID = pAccountID; + END IF; +END// + + + + +DELIMITER ; + +COMMIT ; diff --git a/db/stored/functions.sql b/db/stored/functions.sql index a642ed2ff..d638079e5 100644 --- a/db/stored/functions.sql +++ b/db/stored/functions.sql @@ -429,32 +429,6 @@ BEGIN END // - -DROP FUNCTION IF EXISTS SignIn// -CREATE FUNCTION SignIn ( - pLoginOrMail VARCHAR(255) CHARSET utf8, - pPassword VARCHAR(64) CHARSET utf8 -) RETURNS int(11) # account id -NO SQL -BEGIN - DECLARE vAccountID INT DEFAULT -1; - - #check account existence - SELECT accounts.ID INTO vAccountID FROM accounts - WHERE accounts.Password = SHA1(pPassword) AND - (accounts.Login = pLoginOrMail OR accounts.EMail = pLoginOrMail) - LIMIT 1; - IF vAccountID <= 0 THEN - RETURN -1; - END IF; - - INSERT INTO logs_signin (AccountID) VALUES (vAccountID); - RETURN vAccountID; -END // - - - - # ------------------------------------------------------------------------------------------- # Label: teachers # ------------------------------------------------------------------------------------------- @@ -2259,4 +2233,92 @@ BEGIN RETURN ROW_COUNT()-1; END// + + +# ------------------------------------------------------------------------------------------- +# Label: authorization +# ------------------------------------------------------------------------------------------- + +DROP FUNCTION IF EXISTS SignIn// +CREATE FUNCTION SignIn ( + pLoginOrMail VARCHAR(255) CHARSET utf8, + pPassword VARCHAR(64) CHARSET utf8 +) RETURNS int(11) # account id +NO SQL +BEGIN + DECLARE vAccountID INT DEFAULT -1; + + #check account existence + SELECT accounts.ID INTO vAccountID FROM accounts + WHERE accounts.Password = SHA1(pPassword) AND + (accounts.Login = pLoginOrMail OR accounts.EMail = pLoginOrMail) + LIMIT 1; + IF vAccountID <= 0 THEN + RETURN -1; + END IF; + + INSERT INTO logs_signin (AccountID) VALUES (vAccountID); + RETURN vAccountID; +END // + +-- returns: +-- -1 : invalid token +-- >0 : accountID +DROP FUNCTION IF EXISTS SignInByToken// +CREATE FUNCTION SignInByToken (pToken char(40) charset ascii) RETURNS int(11) # account id +NO SQL +BEGIN + DECLARE vAccountID INT DEFAULT -1; + + SELECT auth_tokens.AccountID INTO vAccountID + FROM auth_tokens + WHERE auth_tokens.Token = pToken + LIMIT 1; + + IF vAccountID = -1 THEN + RETURN -1; -- token not found + END IF; + + UPDATE auth_tokens + SET Accessed = CURRENT_TIMESTAMP + WHERE auth_tokens.Token = pToken + LIMIT 1; + + INSERT INTO logs_signin(AccountID) VALUES (vAccountID); + RETURN vAccountID; +END // + +DROP FUNCTION IF EXISTS DeleteAuthToken// +CREATE FUNCTION DeleteAuthToken(pToken char(40) CHARSET ascii) RETURNS int(11) +NO SQL +BEGIN + DELETE FROM auth_tokens + WHERE auth_tokens.Token = pToken; + RETURN ROW_COUNT()-1; +END// + +DROP FUNCTION IF EXISTS CreateAuthToken// +CREATE FUNCTION CreateAuthToken( + pAccountID int(11), + pRightMask int(11) +) RETURNS char(40) charset ascii +NO SQL +BEGIN + DECLARE vCounter int(11) DEFAULT 666; + DECLARE vCreated boolean DEFAULT FALSE; + DECLARE vSeed int(11) DEFAULT FLOOR(4294967296 * RAND(CURRENT_TIMESTAMP ^ LAST_INSERT_ID() ^ (pAccountID << 10))); + DECLARE vToken char(40) charset ascii DEFAULT SHA1(vSeed); + + WHILE NOT vCreated AND vCounter > 0 DO + DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET vCreated = FALSE; + SET vToken = SHA1(RAND()); + INSERT INTO auth_tokens(Token, AccountID, Mask) VALUES (vToken, pAccountID, pRightMask); + + SET vCreated = TRUE; + SET vCounter = vCounter - 1; + END WHILE; + + RETURN IF(vCreated, vToken, ''); +END// + DELIMITER ; diff --git a/db/stored/procedures.sql b/db/stored/procedures.sql index 0cf44e2ed..ede44a093 100644 --- a/db/stored/procedures.sql +++ b/db/stored/procedures.sql @@ -1459,4 +1459,24 @@ BEGIN requests.Description != ''; END// +# ------------------------------------------------------------------------------------------- +# Label: authorization +# ------------------------------------------------------------------------------------------- + +-- pAccountID == 0 - get tokens of all users +DROP PROCEDURE IF EXISTS GetAuthTokens// +CREATE PROCEDURE GetAuthTokens( + IN pAccountID int(11)) +BEGIN + -- accountID, Created, Accessed, Mask + IF pAccountID = 0 THEN + SELECT auth_tokens.* + FROM auth_tokens; + ELSE + SELECT auth_tokens.* + FROM auth_tokens + WHERE auth_tokens.AccountID = pAccountID; + END IF; +END// + DELIMITER ; diff --git a/db/structure/base.sql b/db/structure/base.sql index a6bf2b6bc..45e998558 100644 --- a/db/structure/base.sql +++ b/db/structure/base.sql @@ -402,7 +402,6 @@ CREATE TABLE IF NOT EXISTS `teachers` ( -- Структура таблицы `user_roles` -- -# TODO: just name CREATE TABLE IF NOT EXISTS `user_roles` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `Type` enum('student','teacher') NOT NULL, @@ -469,3 +468,20 @@ CREATE TABLE IF NOT EXISTS `exam_period_options` ( KEY `StudentID_2` (`StudentID`), KEY `StudentID_3` (`StudentID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + + +-- -------------------------------------------------------- + +-- +-- Структура таблицы `auth_tokens` +-- + +CREATE TABLE IF NOT EXISTS `auth_tokens` ( + `Token` char(40) charset ascii NOT NULL, + `AccountID` int(11) NOT NULL, + `Created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `Accessed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `Mask` int(11) NOT NULL DEFAULT 0, + UNIQUE KEY `Token` (`Token`), + KEY `AccountID` (`AccountID`) +); diff --git a/~dev_rating/application/classes/FileParser.php b/~dev_rating/application/classes/FileParser.php index 7f7c1d910..e881ca733 100644 --- a/~dev_rating/application/classes/FileParser.php +++ b/~dev_rating/application/classes/FileParser.php @@ -35,7 +35,7 @@ class FileParser $degree = self::getDegreeType($degree); - $attempt = Model_Account::createStudentEx( + $attempt = Model_Student::createStudentEx( $lastName, $firstName, $secondName, $gradeNum, $groupNum, $degree, $spec, $facultyID ); diff --git a/~dev_rating/application/classes/Model/Account.php b/~dev_rating/application/classes/Model/Account.php index aba9b2938..22bd45375 100644 --- a/~dev_rating/application/classes/Model/Account.php +++ b/~dev_rating/application/classes/Model/Account.php @@ -14,43 +14,6 @@ class Model_Account extends Model return $result; } - public static function checkAuth($login, $password) { - $sql = 'SELECT `SignIn`(:login, :pass) AS `ID`'; - return DB::query(Database::SELECT, $sql) - ->param(':login', $login) - ->param(':pass', $password) - ->execute()->get('ID'); - } - - - public static function generateActivationCode() { - $activationCode = Text::random('ABDCEFGHJKLMNPQRSTUVWXYZ123456789', 10); - return UTF8::strtoupper($activationCode); - } - - public static function createStudentEx($lastName, $firstName, $secondName, $gradeNum, $groupNum, $degree, $specialization, $facultyID, $semesterID = null) { - $code = self::generateActivationCode(); - - if (!$semesterID) $semesterID = Model_System::$SemesterID; - - $sql = 'SELECT `CreateStudentEx`(:last, :first, :second, :grade, :group, :degree, :spec, :faculty, :code, :semester) AS `UserID`;'; - $response = DB::query(Database::SELECT, $sql) - ->parameters([ - ':last' => trim($lastName), - ':first' => trim($firstName), - ':second' => trim($secondName), - ':grade' => $gradeNum, - ':group' => $groupNum, - ':degree' => $degree, - ':spec' => $specialization, - ':faculty' => $facultyID, - ':code' => $code, - ':semester' => $semesterID - ])->execute()->get('UserID'); - - return $response == -1 ? -1 : $code; - } - public static function with($id, $semesterID = null) { if ($id <= 0) throw new LogicException(Error::ID_IS_INCORRECT); @@ -107,6 +70,16 @@ class Model_Account extends Model ->execute()->get('Num'); } + + // ===================================== + // sign up + // ===================================== + + public static function generateActivationCode() { + $activationCode = Text::random('ABDCEFGHJKLMNPQRSTUVWXYZ123456789', 10); + return UTF8::strtoupper($activationCode); + } + public static function isActivationCodeValid($code) { $sql = "SELECT `CheckAccountExistence`(:acode, 'code') AS Num"; $res = DB::query(Database::SELECT, $sql) @@ -114,6 +87,41 @@ class Model_Account extends Model ->execute()->get('Num'); return $res == 1; } + + public static function activateAccount($login, $password, $email, $code) { + $sql = 'SELECT `ActivateAccount`(:code, :login, :email, :pass) AS `Num`'; + return DB::query(Database::SELECT, $sql) + ->parameters([ + ':code' => $code, + ':login' => $login, + ':email' => $email, + ':pass' => $password, + ])->execute()->get('Num'); + } + + // ===================================== + // authentication + // ===================================== + + public static function checkAuth($login, $password) { + $sql = 'SELECT `SignIn`(:login, :pass) AS `ID`'; + return DB::query(Database::SELECT, $sql) + ->param(':login', $login) + ->param(':pass', $password) + ->execute()->get('ID'); + } + + public static function checkAuthToken($token) { + $sql = 'SELECT `SignInByToken`(:token) AS `ID`'; + return DB::query(Database::SELECT, $sql) + ->param(':token', $token) + ->execute()->get('ID'); + } + + + // ===================================== + // password recovery + // ===================================== public static function createRecoveryToken($email, $token) { $sql = 'SELECT `CreateRecoveryToken`(:email, :token) AS UserName'; @@ -143,19 +151,40 @@ class Model_Account extends Model ->execute()->get('Num'); } - public static function activateAccount($login, $password, $email, $code) { - $sql = 'SELECT `ActivateAccount`(:code, :login, :email, :pass) AS `Num`'; + // ===================================== + // auth tokens management + // ===================================== + + public static function deleteAuthToken($token) { + $sql = 'SELECT `deleteAuthToken`(:token) AS `res`'; return DB::query(Database::SELECT, $sql) - ->parameters([ - ':code' => $code, - ':login' => $login, - ':email' => $email, - ':pass' => $password, - ])->execute()->get('Num'); + ->param(':token', $token) + ->execute()->get('res'); } - public function getActivationCode() { + /** + * @param int $accountID + * @param int $mask bit mask with access rights + * @return string token, if it was created. Empty string otherwise + */ + public static function createAuthToken($accountID, $mask = 0) { + $sql = 'SELECT `CreateAuthToken`(:user, :mask) AS `token`'; + return DB::query(Database::SELECT, $sql) + ->parameters([ + ':user' => (int) $accountID, + ':mask' => (int) $mask, + ])->execute()->get('token'); + } + /** + * @param int $accountID - 0 to get auth tokens of all users + * @return mixed + */ + public static function getAuthTokens($accountID = 0) { + $sql = 'CALL `GetAuthTokens`(:user)`'; + return DB::query(Database::SELECT, $sql) + ->param(':user', (int) $accountID) + ->execute(); } } diff --git a/~dev_rating/application/classes/Model/Student.php b/~dev_rating/application/classes/Model/Student.php index 2d41e7e23..dcc1fa262 100644 --- a/~dev_rating/application/classes/Model/Student.php +++ b/~dev_rating/application/classes/Model/Student.php @@ -51,6 +51,29 @@ class Model_Student extends Model_Container throw new InvalidArgumentException(Error::INVALID_PARAMETERS); } + public static function createStudentEx($lastName, $firstName, $secondName, $gradeNum, $groupNum, $degree, $specialization, $facultyID, $semesterID = null) { + $code = Model_Account::generateActivationCode(); + + if (!$semesterID) $semesterID = Model_System::$SemesterID; + + $sql = 'SELECT `CreateStudentEx`(:last, :first, :second, :grade, :group, :degree, :spec, :faculty, :code, :semester) AS `UserID`;'; + $response = DB::query(Database::SELECT, $sql) + ->parameters([ + ':last' => trim($lastName), + ':first' => trim($firstName), + ':second' => trim($secondName), + ':grade' => $gradeNum, + ':group' => $groupNum, + ':degree' => $degree, + ':spec' => $specialization, + ':faculty' => $facultyID, + ':code' => $code, + ':semester' => $semesterID + ])->execute()->get('UserID'); + + return $response == -1 ? -1 : $code; + } + /** @return Model_Discipline[] */ public function getDisciplines($semesterID = null) { $semesterID = $semesterID ?: User::instance()->SemesterID; -- GitLab