<?php
/**
* This class can be used to authenticate Web users securely.
* It creates a key encoded with SHA1 based on the IP address and the current time when an user accesses the login page.
* The key is used to generate a salt value that is used to encrypt the password that the user enters in the login form.
* On the server side the class uses the same salt to verify whether the password entered by the user matches the password stored in a database.
* If the authentication is successful, the class starts an authenticated user session.
* The login keys, user and session information is stored in a database.
* @author Marcelo Soares da Costa
* @email phpmafia at yahoo dot com dot br
* @copyright Marcelo Soares da Costa © 2007/2008.
* @license FreeBSD http://www.freebsd.org/copyright/freebsd-license.html
* @version 3.0
* @access public
* @package OOE
* @chagelog 2009-07-02 Port for OOE Framework V2.0
* @data 2009-07-02
*/
################################################################################
class ooeSecuritySession extends ooeInheritance {
private static $objSql = false;
/**
* Cria uma referencia para o objeto de conexão do banco de dados
* @access private
*/
private static function __sqlQuery() {
return parent :: callClassParent('ooePDO');
}
/**
* Verifica a referencia para o objeto sqlQuery
* @access private
*/
private function __checkObjSql() {
if ($this->objSql == false) {
$this->objSql = self :: __sqlQuery();
}
}
/**
* Importa o metodo prepareSQL
* @access public
*/
public function prepareSQL($String) {
self :: __checkObjSql();
return $this->objSql->prepareSQL($String);
}
/**
* Importa o metodo getRow
* @access public
*/
public function getRow() {
return $this->objSql->getRow();
}
/**
* Importa o metodo getLineArray
* @access public
*/
public function getLineArray() {
return $this->objSql->getLineArray();
}
/**
* Importa o metodo getAssocArray
* @access public
*/
public function getAssocArray() {
return $this->objSql->getAssocArray();
}
/**
* Importa o metodo beginTransaction
* @access public
*/
public function beginTransaction() {
self :: __checkObjSql();
return $this->objSql->beginTransaction();
}
/**
* Importa o metodo commit
* @access public
*/
public function commit() {
return $this->objSql->commit();
}
/**
* Importa o metodo rollBack
* @access public
*/
public function rollBack() {
return $this->objSql->rollBack();
}
/**
* Importa o metodo executeSql
* @access public
*/
public function executeSql($String) {
self :: __checkObjSql();
return $this->objSql->executeSql($String);
}
/**
* seta o tempo limite para login
* returnt time for login
* @access private
* @input time {string}
* @return
*/
private function __setTimeLogin($timeIni) {
unset ($this->timeLogin);
$this->timeLogin = $timeIni;
}
/**
* seta o tempo limite para a verificacao de sessao em segundos
* return time session limit
* @access private
* @input time {string}
* @return
*/
private function __setTimeLimit($lifeTime) {
unset ($this->timeLimit);
$this->timeLimit = $lifeTime;
}
/**
* Retorna uma nova chave de autenticacao
* return public form key for login
* @access public
* @input
* @return array keys
*/
public function newServerKey() {
self :: createServerKey(self :: insertKeydata());
return array (
"idKey" => $this->idKey,
"serverKey" => $this->serverKey
);
}
/**
* insere no banco de dados as informacoes para criar uma chave de autenticacao
* insert public key form login
* retorna o id inserido
* @access private
* @input
* @return insertid
*/
private function insertKeyData() {
$this->keyTime = time();
$this->REMOTE_ADDR = str_replace(".", '', $_SERVER["REMOTE_ADDR"]);
if ($this->REMOTE_ADDR == "::1") {
$this->REMOTE_ADDR = 127001;
}
if ($_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] == str_replace("http://", '', LOGINURL)) {
session_unset();
session_destroy();
$this->REQUEST_URI = $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
$this->clienteData = $this->REMOTE_ADDR . $this->REQUEST_URI . $this->keyTime;
$SQL_CLEAN = "DELETE FROM acs_sessao WHERE KEYTIME<=" . ($this->keyTime - $this->timeLogin);
$SQL_CLEAN .= " AND KEEPALIVE=0";
self :: executeSql($SQL_CLEAN);
$SQL_KEY = "INSERT INTO acs_sessao (REMOTE_ADDR,KEYTIME,KEEPALIVE) VALUES ('" . $this->REMOTE_ADDR . "'," . $this->keyTime . ",0)";
$this->idKey = self :: executeSql($SQL_KEY);
return $this->idKey;
} else {
throw new Exception("Invalid form login", 404);
}
}
/**
* cria a chave sha256 de autenticação a partir dos dados gravados no bd
* create a sha256 key server
* @access private
* @input id session
* @return bolean
*/
private function createServerKey($id) {
$SQL_KEY = "SELECT * FROM acs_sessao WHERE ID_SESSION=" . $id;
self :: prepareSQL($SQL_KEY);
extract(self :: getLineArray(), EXTR_OVERWRITE);
$this->serverKey = hash('sha256', $REMOTE_ADDR . str_replace("http://", '', LOGINURL) . $KEYTIME);
return true;
}
/**
* Ataualiza a tabela quando a chave é validada
* update time session for valid key
* retorna o id inserido
* @access privado
* @input id session
* @return bolean
*/
private function validKey($id) {
$this->timeCurrent = time();
$SQL = "UPDATE acs_sessao set KEEPALIVE=" . $this->timeCurrent . " WHERE ID_SESSION=" . $id;
self :: executeSql($SQL);
return true;
}
/**
* Verifica se a chave de autenticação eh identica
* check if key server and client key is valid, identical
* @access private
* @input id , key
* @return bolean
*/
private function checkServerKey($id, $keyServer) {
try {
$SQL_KEY = "SELECT * FROM acs_sessao WHERE ID_SESSION=" . $id;
self :: prepareSQL($SQL_KEY);
extract(self :: getLineArray(), EXTR_OVERWRITE);
} catch (Exception $e) {
throw new Exception("Sessão Inválida ", 4001);
}
$this->keyDb = hash('sha256', $REMOTE_ADDR . str_replace("http://", '', LOGINURL) . $KEYTIME);
if (($keyServer === $this->keyDb)) {
return true;
} else {
throw new Exception("Chave de autenticação inválida ", 4001);
}
}
/**
* Registra no banco de dados a chave de keepaive
* register key for keepalive
* @access private
* @input
* @return array keys
*/
private function registerKey($id, $keyServer) {
if (self :: checkServerKey($id, $keyServer) == true) {
return self :: validKey($id);
} else {
return false;
}
}
/**
* Registra em sessao os dados para verificacao
* register session for valid hmac keys
* @access public
* @input $_POST form
* @return redirect url
*/
public function registerSession() {
if (is_array($_POST)) {
if (self :: registerKey($_POST['id'], $_POST['salt']) == true) {
list ($senhaMD5, $senhaSHA1) = explode("|", base64_decode($_POST['password']));
if (self :: checkPasswd(base64_decode($_POST['username']), $_POST['salt'], $senhaMD5, $senhaSHA1) == true) {
$_SESSION['SS_IDKEY'] = $_POST['id'];
$_SESSION['SS_KEYLOGIN'] = $_POST['salt'];
$_SESSION['SS_TIME'] = $this->timeCurrent;
$_SESSION['SS_KEYSSESSION'] = hash_hmac('sha256', $_SERVER["REMOTE_ADDR"] . $_POST['username'] . $this->timeCurrent, SESSIONKEY);
$_SESSION['SS_REMOTE_ADDR'] = $_SERVER["REMOTE_ADDR"];
$_SESSION['SS_USER_AGENT'] = $_SERVER["HTTP_USER_AGENT"];
$_SESSION['SS_IDUSER'] = $this->idUser;
$_SESSION['SS_LOGIN'] = $this->login;
$_SESSION['SS_QTACESS'] = $this->qtAcess + 1;
$_SESSION['SS_TRY'] = $this->try;
;
$_SESSION['SS_DTACESS'] = $this->lastAcess;
$_SESSION['SS_NAME'] = $this->name;
$_SESSION['SS_MAIL'] = $this->mail;
//self :: logRegister($_SERVER["REMOTE_ADDR"], $this->idUser, 0, "S");
header("Location: " . LOGINOKURL . "");
exit; /* Redirect browser */
} else {
header("Location: " . LOGINURL . "");
exit; /* Redirect browser */
}
}
}
}
/**
* Verifica por hmac se a senha eh valida
* check hmac keys is identical
* @access private
* @input user,login,passwd md5, passwd sha1
* @return id user
*/
private function checkPasswd($user, $salt, $passwdMD5, $passwdSHA1) {
$SQL_PASS = "SELECT
A.CD_USUARIO,
A.TX_LOGIN,
A.TX_SENHA_MD5,
A.TX_SENHA_SHA1,
A.QT_ACESSOS,
A.ST_REG,
A.QT_TENTATIVAS,
DATE_FORMAT(A.DT_ULT_ACESSO,'%d/%m/%Y %H:%i:%s') as LASTACESS,
A.TX_IP,
B.NM_USUARIO,
B.TX_EMAIL
FROM acs_usuarios_login A
JOIN acs_usuarios B ON B.CD_USUARIO=A.CD_USUARIO
WHERE A.TX_LOGIN='" . $user . "'";
//echo $SQL_PASS;die();
// OBS se for data do acesso anterior seria "DATE_FORMAT(A.DT_ULT_ACESSO,'%d/%m/%Y %H:%i:%s') as LASTACESS,"
// Se for o acesso atual DATE_FORMAT(CURRENT_TIMESTAMP,'%d/%m/%Y %H:%i:%s') as LASTACESS,
try {
self :: prepareSQL($SQL_PASS);
extract(self :: getLineArray(), EXTR_OVERWRITE);
} catch (Exception $e) {
throw new Exception("Senha ou Login Inválido", 4002);
}
if ($ST_REG != "A") {
throw new Exception("Acesso negado", 4003);
}
if ($QT_TENTATIVAS > LIMITLOGIN) {
throw new Exception("Numero de tentativas excedido", 4004);
}
if ($TX_IP != null) {
if ($TX_IP != str_replace(".", '', $_SERVER["REMOTE_ADDR"])) {
throw new Exception("Usuario sem logoff", 4009);
}
}
//aplico hmac para verificar se as senhas correpondem
$hashMD5 = hash_hmac('md5', $salt, $TX_SENHA_MD5);
$hashSHA1 = hash_hmac('sha1', $salt, $TX_SENHA_SHA1);
if (($passwdMD5 === $hashMD5) AND ($passwdSHA1 === $hashSHA1)) {
try {
$SQL_OK = "UPDATE acs_usuarios_login SET QT_ACESSOS=QT_ACESSOS+1,QT_TENTATIVAS=0,DT_ULT_ACESSO=NOW(),TX_IP='" . str_replace(".", '', $_SERVER["REMOTE_ADDR"]) . "' WHERE CD_USUARIO=" . $CD_USUARIO;
$update = self :: executeSql($SQL_OK);
//echo $SQL_OK;die();
} catch (Exception $e) {
throw new Exception("Falha em atualizar a quantidade de acessos", 4002);
}
$this->idUser = $CD_USUARIO;
$this->login = $TX_LOGIN;
$this->qtAcess = $QT_ACESSOS;
$this->try= $QT_TENTATIVAS;
$this->lastAcess = $LASTACESS;
$this->name = $NM_USUARIO;
$this->mail = $TX_EMAIL;
return $this->idUser;
} else {
$SQL_FAIL = "UPDATE acs_usuarios_login SET QT_TENTATIVAS=QT_TENTATIVAS+1 WHERE CD_USUARIO=" . $CD_USUARIO;
$update = self :: executeSql($SQL_FAIL);
throw new Exception("Usuário ou senha Inválido", 4005);
}
}
/**
* Grava na tabela de log
* Register log in database
* @access private
* @input ip,id user,exception,sucess or not
* @return id user
*/
private function logRegister($TX_IP, $CD_USUARIO, $CD_EXCEPTION=0, $IN_AUT_OK = "N") {
try {
$SQL = "INSERT INTO acs_log_acesso";
$SQL .= " (TX_IP,CD_USUARIO,CD_EXCEPTION,IN_AUT_OK)";
$SQL .= " VALUES ";
$SQL .= " ('" . $TX_IP . "'," . $CD_USUARIO . "," . $CD_EXCEPTION . ",'" . $IN_AUT_OK . "')";
$INSERT = self :: executeSql($SQL);
return true;
} catch (Exception $e) {
throw new Exception("Falha na gravação do log".$e->getMessage(), 4999);
return false;
}
}
/**
* Verifica se a sessão corrente ainda eh valida
* Atualiza a chave da sessao ou destroi a sessao
* Check if keys in session are valid
* @access public
* @input
* @return redirect url , bolean
*/
public function checkSession() {
if (($_SESSION['SS_REMOTE_ADDR'] === $_SERVER["REMOTE_ADDR"]) AND ($_SESSION['SS_USER_AGENT'] === $_SERVER["HTTP_USER_AGENT"])) {
if (time() > ($_SESSION['SS_TIME'] + (TIMESESSION * 60) + 1)) {
header("Location: " . LOGINURL . "");
exit; /* Redirect browser */
}
if (time() > $_SESSION['SS_TIME'] + TIMEKEY) {
if (self :: keepAlive() == false) {
self :: logRegister($_SERVER["REMOTE_ADDR"], $_SESSION['SS_IDUSER'], 4006, "S");
throw new Exception("Desconectado por Inatividade", 4006);
}
} else {
return true;
}
} else {
header("Location: " . KICKURL . ""); /* Redirect browser */
}
}
/**
* Verifica se a chave da sessão eh valida e atualiza
* update key in session on keealive
* @access private
* @input
* @return bolean
*/
private function keepAlive() {
$keepAlive = false;
$SQL_PASS = "SELECT TX_LOGIN FROM acs_usuarios_login WHERE CD_USUARIO=" . $_SESSION['SS_IDUSER'];
self :: prepareSQL($SQL_PASS);
$login = self :: getRow();
if ($_SESSION['SS_KEYSSESSION'] === hash_hmac('sha256', $_SERVER["REMOTE_ADDR"] . base64_encode($login) . $_SESSION['SS_TIME'], SESSIONKEY)) {
if (self :: checkServerKey($_SESSION['SS_IDKEY'], $_SESSION['SS_KEYLOGIN']) == true) {
if (self :: validKey($_SESSION['SS_IDKEY']) == true) {
$_SESSION['SS_TIME'] = $this->timeCurrent;
$_SESSION['SS_KEYSSESSION'] = hash_hmac('sha256', $_SERVER["REMOTE_ADDR"] . base64_encode($login) . $this->timeCurrent, SESSIONKEY);
$keepAlive = true;
}
}
}
return $keepAlive;
}
# fim da classe
}
?>