Created File* backends

This commit is contained in:
Gergely POLONKAI 2012-10-06 23:17:20 +02:00
parent 57a2da32cb
commit 498e00b7d2
10 changed files with 431 additions and 28 deletions

6
composer.lock generated
View File

@ -22,7 +22,7 @@
"require-dev": { "require-dev": {
"symfony/finder": "2.1.*" "symfony/finder": "2.1.*"
}, },
"time": "2012-09-01 01:02:36", "time": "2012-09-01 07:02:36",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -69,7 +69,7 @@
"require": { "require": {
"php": ">=5.3.3" "php": ">=5.3.3"
}, },
"time": "2012-09-18 12:09:52", "time": "2012-09-18 16:09:52",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -128,7 +128,7 @@
"symfony/yaml": "2.1.*", "symfony/yaml": "2.1.*",
"doctrine/common": ">=2.2,<2.4-dev" "doctrine/common": ">=2.2,<2.4-dev"
}, },
"time": "2012-09-10 08:53:42", "time": "2012-09-10 10:53:42",
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {

View File

@ -10,9 +10,9 @@ use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use SmsGateway\Sender\GnokiiSender; use SmsGateway\Sender\FileSender;
use SmsGateway\Logger\DatabaseLogger; use SmsGateway\Logger\FileLogger;
use SmsGateway\Backend\DatabaseBackend; use SmsGateway\Auth\FileAuth;
use SmsGateway\RpcServer; use SmsGateway\RpcServer;
$request = Request::createFromGlobals(); $request = Request::createFromGlobals();
@ -56,7 +56,7 @@ if (!array_key_exists('method', $jsonData) || !array_key_exists('params', $jsonD
$wantResponse = (!empty($jsonData['id'])); $wantResponse = (!empty($jsonData['id']));
try { try {
$sender = new GnokiiSender(); $sender = new FileSender('/var/www/html/smsgateway/app/cache/sms_spool');
} catch (Exception $e) { } catch (Exception $e) {
$response = new Response('Internal Server Error: Sender cannot be instantiated.', 500); $response = new Response('Internal Server Error: Sender cannot be instantiated.', 500);
$response->send(); $response->send();
@ -64,22 +64,26 @@ try {
} }
try { try {
$logger = new DatabaseLogger('pgsql:host=127.0.0.1;dbname=smsgateway', 'smsgateway', 'Imuiwai8'); $logger = new FileLogger('/var/www/html/smsgateway/app/logs/sms-message.log', '/var/www/html/smsgateway/app/logs/sms-audit.log');
} catch (PDOException $e) { } catch (LogicException $e) {
$response = new Response('Internal Server Error: Logger cannot be instantiated.', 500); $response = new Response('Internal Server Error: Logger cannot be instantiated.', 500);
$response->send(); $response->send();
exit; exit;
} }
try { try {
$backend = new DatabaseBackend(); $auth = new FileAuth('/var/www/html/smsgateway/senders', '/var/www/html/smsgateway/app/cache/tokens');
} catch (Exception $e) { } catch (Exception $e) {
$response = new Response('Internal Server Error: Backend cannot be instantiated.', 500); $response = new Response('Internal Server Error: Authenticator cannot be instantiated.', 500);
$response->send(); $response->send();
exit; exit;
} }
$handler = new RpcServer($backend, $logger, $sender); $auth->setLogger($logger);
$sender->setLogger($logger);
$handler = new RpcServer($auth, $logger, $sender);
try { try {
$result = $handler->handle($request, $jsonData); $result = $handler->handle($request, $jsonData);
} catch (Exception $e) { } catch (Exception $e) {

View File

@ -1,7 +1,8 @@
<?php <?php
namespace SmsGateway\Backend; namespace SmsGateway\Auth;
use SmsGateway\AuthInterface; use SmsGateway\AuthInterface;
use SmsGateway\LoggerInterface;
class DatabaseAuth implements AuthInterface class DatabaseAuth implements AuthInterface
{ {

View File

@ -0,0 +1,142 @@
<?php
namespace SmsGateway\Auth;
use SmsGateway\AuthInterface;
use SmsGateway\LoggerInterface;
/**
* Description of FileAuth
*
* @author Gergely Polonkai
*/
class FileAuth implements AuthInterface
{
private $logger;
private $sendersFile;
private $tokenFile;
public function __construct($sendersFile, $tokenFile) {
if ($sendersFile == null) {
throw new \InvalidArgumentException('A senders file path must be passed to the authenticator!');
}
if (!is_readable($sendersFile)) {
throw new \RuntimeException('senders file not readable!');
}
if ($tokenFile == null) {
throw new \InvalidArgumentException('A token file path must be passed to the authenticator!');
}
if (
(
file_exists($tokenFile)
&& !is_writable($tokenFile)
)
|| (
!file_exists($tokenFile)
&& !is_writable(dirname($tokenFile))
)
) {
throw new \RuntimeException('Token file is not writable!');
}
$this->sendersFile = $sendersFile;
$this->tokenFile = $tokenFile;
}
public function authenticate($username, $password, $ip, $sessionId)
{
$this->logger->auditLog(LoggerInterface::LOG_AUDIT_LOGIN, $username, "trying to authenticate");
$lines = file($this->sendersFile);
foreach ($lines as $line) {
list($user, $cPassword) = explode(':', trim($line), 2);
if ($user == $username) {
if (crypt($password, $cPassword) == $cPassword) {
$this->logger->auditLog(LoggerInterface::LOG_AUDIT_LOGIN, $username, "authenticated successfully");
return $this->getToken($username, $ip, $sessionId);
} else {
$this->logger->auditLog(LoggerInterface::LOG_AUDIT_LOGIN, $username, "authentication failed: bad password");
return false;
}
}
}
return false;
}
public function getTokenUsername($token, $ip, $sessionId)
{
$lines = file($this->tokenFile);
foreach ($lines as $line) {
list($tokenUser, $tokenIp, $tokenSession, $tokenToken) = explode(':', trim($line), 4);
if (($tokenToken == $token) && ($tokenIp == $ip) && ($tokenSession == $sessionId)) {
return $tokenUser;
}
}
return null;
}
public function isTokenValid($token, $ip, $sessionId)
{
$this->logger->auditLog(LoggerInterface::LOG_AUDIT_LOGIN, null, 'Checking token validity');
$lines = file($this->tokenFile);
foreach ($lines as $line) {
list($tokenUser, $tokenIp, $tokenSession, $tokenToken) = explode(':', trim($line), 4);
if (($tokenToken == $token) && ($tokenIp == $ip) && ($tokenSession == $sessionId)) {
return true;
}
}
return false;
}
public function getToken($username, $ip, $sessionId) {
$this->logger->auditLog(LoggerInterface::LOG_AUDIT_LOGIN, $username, "Getting token");
$lines = file($this->tokenFile);
foreach ($lines as $line) {
list($tokenUser, $tokenIp, $tokenSession, $tokenToken) = explode(':', trim($line), 4);
if (($tokenUser == $username) && ($tokenIp == $ip) && ($tokenSession == $sessionId)) {
return $tokenToken;
}
}
$token = str_replace(':', '', uniqid('', true));
$fd = fopen($this->tokenFile, 'a');
fwrite($fd, sprintf("%s:%s:%s:%s\n", $username, $ip, $sessionId, $token));
fclose($fd);
return $token;
}
public function removeToken($token, $ip, $sessionId) {
$username = $this->getTokenUsername($token, $ip, $sessionId);
$this->logger->auditLog(LoggerInterface::LOG_AUDIT_LOGIN, $username, "Removing token");
$lines = file($this->tokenFile);
$fd = fopen($this->tokenFile, 'w');
foreach ($lines as $line) {
list($tokenUser, $tokenIp, $tokenSession, $tokenToken) = explode(':', trim($line), 4);
if (($tokenToken != $token) || ($tokenIp != $ip) || ($tokenSession != $sessionId)) {
fwrite($fd, sprintf("%s:%s:%s:%s\n", $tokenUser, $tokenIp, $tokenSession, $tokenToken));
}
}
fclose($fd);
return false;
return true;
}
public function getLogger() {
return $this->logger;
}
public function setLogger(LoggerInterface $logger) {
$this->logger = $logger;
}
}

View File

@ -9,9 +9,13 @@ interface AuthInterface
public function getLogger(); public function getLogger();
public function authenticate($username, $password, $ip, $sessionId);
public function getToken($username, $ip, $sessionId); public function getToken($username, $ip, $sessionId);
public function isTokenValid($token, $ip, $sessionId); public function isTokenValid($token, $ip, $sessionId);
public function removeToken($token, $ip, $sessionId); public function removeToken($token, $ip, $sessionId);
public function getTokenUsername($token, $ip, $sessionId);
} }

View File

@ -0,0 +1,130 @@
<?php
namespace SmsGateway\Logger;
use SmsGateway\LoggerInterface;
/**
* Description of FileLogger
*
* @author Gergely Polonkai
*/
class FileLogger implements LoggerInterface
{
const PASSWORD_MASK = '[password]';
/**
* The message log file
*
* @var resource $messageLogHandle
*/
private $messageLogHandle;
/**
* The audit log file
*
* @var resource $auditLogHandle
*/
private $auditLogHandle;
/**
*
* @param string $messageLogFile Name of the message log file
* @param string $auditLogFile Name of the audit log file
* @throws \LogicException Upon file opening error
*/
public function __construct($messageLogFile, $auditLogFile)
{
if ($messageLogFile == null) {
throw new \LogicException('Message log file can not be null!');
}
if ($auditLogFile == null) {
throw new \LogicException('Audit log file can not be null!');
}
if (
(
file_exists($messageLogFile)
&& !is_writable($messageLogFile)
)
|| (
!file_exists($messageLogFile)
&& !is_writable(dirname($messageLogFile))
)
) {
throw new \LogicException('Message log file is not writable!');
}
if (
(
file_exists($auditLogFile)
&& !is_writable($auditLogFile)
)
|| (
!file_exists($auditLogFile)
&& !is_writable(dirname($auditLogFile))
)
) {
throw new \LogicException('Audit log file is not writable!');
}
if (($this->messageLogHandle = fopen($messageLogFile, 'a')) === false) {
throw new \LogicException('Message log file could not be opened!');
}
if (($this->auditLogHandle = fopen($auditLogFile, 'a')) === false) {
throw new \LogicException('Audit log file could not be opened!');
}
}
public function __destruct() {
fclose($this->messageLogHandle);
fclose($this->auditLogHandle);
}
private function orderPasswordLocations($a, $b)
{
if ($a[0] == $b[0]) {
return 0;
} elseif ($a[0] < $b[0]) {
return -1;
}
return 1;
}
public function messageLog($username, $recipient, $message, array $passwordLocations)
{
usort($passwordLocations, array($this, 'orderPasswordLocations'));
$encodedMessage = $message;
$mod = 0;
foreach ($passwordLocations as $loc) {
list($pos, $length) = $loc;
$encodedMessage = substr_replace($encodedMessage, self::PASSWORD_MASK, $pos + $mod, $length);
$mod += (strlen(self::PASSWORD_MASK) - $length);
}
$logMessage = "From $username
From: $username
To: $recipient
$encodedMessage\n\n";
fwrite($this->messageLogHandle, $logMessage);
fflush($this->messageLogHandle);
return true;
}
public function auditLog($type, $username, $message)
{
if ($username === null) {
$logMessage = "$message\n";
} else {
$logMessage = "$username: $message\n";
}
fwrite($this->auditLogHandle, $logMessage);
fflush($this->auditLogHandle);
return true;
}
}

View File

@ -3,7 +3,7 @@ namespace SmsGateway;
interface LoggerInterface interface LoggerInterface
{ {
const SMSSENDER_AUDIT_LOGIN = 1; const LOG_AUDIT_LOGIN = 1;
/** /**
* Log an audit event * Log an audit event
@ -23,5 +23,5 @@ interface LoggerInterface
* @param string $recipient The recipient of the message * @param string $recipient The recipient of the message
* @param string $message The message itself * @param string $message The message itself
*/ */
public function messageLog($username, $recipient, $message); public function messageLog($username, $recipient, $message, array $passwordLocations);
} }

View File

@ -3,7 +3,7 @@ namespace SmsGateway;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use SmsGateway\BackendInterface; use SmsGateway\AuthInterface;
use SmsGateway\LoggerInterface; use SmsGateway\LoggerInterface;
use SmsGateway\SenderInterface; use SmsGateway\SenderInterface;
@ -12,9 +12,9 @@ class RpcServer
/** /**
* The user backend * The user backend
* *
* @var SmsGateway\BackendInterface $backend * @var SmsGateway\AuthInterface $backend
*/ */
private $backend; private $auth;
/** /**
* The logger * The logger
@ -30,23 +30,46 @@ class RpcServer
*/ */
private $sender; private $sender;
public function __construct(BackendInterface $backend, LoggerInterface $logger, SenderInterface $sender) public function __construct(AuthInterface $auth, LoggerInterface $logger, SenderInterface $sender)
{ {
$this->auth = $auth;
$this->logger = $logger;
$this->sender = $sender;
} }
protected function login(array $params) protected function login($username, $password)
{ {
return true; $token = $this->auth->authenticate($username, $password, $_SERVER['REMOTE_ADDR'], null);
if ($token === false) {
throw new \Exception('Could not create token.');
}
return $token;
} }
protected function send(array $params) protected function send($token, $recipient, $message, array $passwordLocations)
{ {
return true; if (!$this->auth->isTokenValid($token, $_SERVER['REMOTE_ADDR'], null)) {
throw new \Exception('Invalid token!');
}
$this->sender->send($this->auth->getTokenUsername($token, $_SERVER['REMOTE_ADDR'], null), $recipient, $message, $passwordLocations);
// TODO: Send the message!
return 'success';
} }
protected function logout(array $params) protected function logout($token)
{ {
return true; if (!$this->auth->isTokenValid($token, $_SERVER['REMOTE_ADDR'], null)) {
throw new \Exception('Invalid token!');
}
$this->auth->removeToken($token, $_SERVER['REMOTE_ADDR'], null);
return 'success';
} }
public function handle(Request $request, array $jsonData) public function handle(Request $request, array $jsonData)
@ -54,14 +77,34 @@ class RpcServer
$params = $jsonData['params']; $params = $jsonData['params'];
switch ($jsonData['method']) { switch ($jsonData['method']) {
case 'login': case 'login':
if (count($params) != 2) {
throw new \InvalidArgumentException('Bad parameter count!');
}
return $this->login($params[0], $params[1]);
break; break;
case 'send': case 'send':
if (count($params) != 4) {
throw new \InvalidArgumentException('Bad parameter count!');
}
if (!is_array($params[3])) {
throw new \InvalidArgumentException('Invalid 4th parameter!');
}
return $this->send($params[0], $params[1], $params[2], $params[3]);
break; break;
case 'logout': case 'logout':
if (count($params) != 1) {
throw new \InvalidArgumentException('Bad parameter count!');
}
return $this->logout($params[0]);
break; break;
default: default:
throw new \Exception('Invalid request'); throw new \BadMethodCallException('Unknown method ' . $jsonData['method']);
break;
} }
return 'ajaj';
} }
} }

View File

@ -0,0 +1,74 @@
<?php
namespace SmsGateway\Sender;
use SmsGateway\SenderInterface;
use SmsGateway\LoggerInterface;
/**
* Description of FileSender
*
* @author Gergely Polonkai
*/
class FileSender implements SenderInterface
{
private $messageDir;
/**
* @var SmsGateway\LoggerInterface $logger
*/
private $logger;
public function __construct($messageDir)
{
if (file_exists($messageDir) && !is_dir($messageDir)) {
throw new \InvalidArgumentException('Message directory specified is not a directory!');
}
if (!file_exists($messageDir) && !is_writable(dirname($messageDir))) {
throw new \RuntimeException('Message directory cannot be created');
}
if (!file_exists($messageDir)) {
mkdir($messageDir, 0777, true);
}
if (!is_writable($messageDir)) {
throw new \RuntimeException('Message directory is not writable!');
}
$this->messageDir = $messageDir;
}
public function setLogger(LoggerInterface $logger) {
if ($logger === null) {
throw new \InvalidArgumentException('A logger must be passed to the authenticator!');
}
$this->logger = $logger;
}
public function getLogger() {
return $this->logger;
}
public function send($username, $recipient, $message, $passwordLocations)
{
$rcptDir = $this->messageDir . '/' . $recipient;
if (file_exists($rcptDir) && (!is_writable($rcptDir) || !is_dir($rcptDir))) {
throw new \RuntimeException('Message directory is not writable!');
}
if (!file_exists($rcptDir)) {
mkdir($rcptDir);
}
$messageFileName = date('YmdHis') . '-' . uniqid() . '.sms';
$fd = fopen($rcptDir . '/' . $messageFileName, 'w');
fwrite($fd, $message);
fclose($fd);
$this->logger->messageLog($username, $recipient, $message, $passwordLocations);
return true;
}
}

View File

@ -3,6 +3,10 @@ namespace SmsGateway;
interface SenderInterface interface SenderInterface
{ {
public function setLogger(LoggerInterface $logger);
public function getLogger();
/** /**
* *
* @param string $recipient * @param string $recipient
@ -10,5 +14,6 @@ interface SenderInterface
* @return boolean true upon success. On error, throws exceptions. * @return boolean true upon success. On error, throws exceptions.
* @throws Exception Upon sending error. Gnokii output will be * @throws Exception Upon sending error. Gnokii output will be
* stored in $e->message * stored in $e->message
public function send($recipient, $message); */
public function send($username, $recipient, $message, $passwordLocations);
} }