Compare commits

...

10 Commits

12 changed files with 594 additions and 13 deletions

View File

@@ -15,7 +15,9 @@
"php": ">=5.5.0",
"slim/slim": "^3.1",
"slim/php-view": "^2.0",
"monolog/monolog": "^1.17"
"monolog/monolog": "^1.17",
"respect/validation": "^1.1",
"tuupola/cors-middleware": "^0.5.2"
},
"require-dev": {
"phpunit/phpunit": ">=4.8 < 6.0"

View File

@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "9f4397e11cb2603e7754216c4f59c7ad",
"content-hash": "5e16cb7781829836a704bd8767830833",
"hash": "93a9656f4e6eb0e25be1bad59ac6f487",
"content-hash": "a3fc18885cc45d2733b77fa2081bdc72",
"packages": [
{
"name": "container-interop/container-interop",
@@ -116,6 +116,61 @@
],
"time": "2017-06-19 01:22:40"
},
{
"name": "neomerx/cors-psr7",
"version": "v1.0.13",
"source": {
"type": "git",
"url": "https://github.com/neomerx/cors-psr7.git",
"reference": "2556e2013f16a55532c95928455257d5b6bbc6e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/2556e2013f16a55532c95928455257d5b6bbc6e2",
"reference": "2556e2013f16a55532c95928455257d5b6bbc6e2",
"shasum": ""
},
"require": {
"php": ">=5.6.0",
"psr/http-message": "^1.0",
"psr/log": "^1.0"
},
"require-dev": {
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^5.7",
"scrutinizer/ocular": "^1.1",
"squizlabs/php_codesniffer": "^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Neomerx\\Cors\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "neomerx",
"email": "info@neomerx.com"
}
],
"description": "Framework agnostic (PSR-7) CORS implementation (www.w3.org/TR/cors/)",
"homepage": "https://github.com/neomerx/cors-psr7",
"keywords": [
"Cross Origin Resource Sharing",
"Cross-Origin Resource Sharing",
"cors",
"neomerx",
"psr-7",
"psr7",
"w3.org",
"www.w3.org"
],
"time": "2018-05-23 16:10:11"
},
{
"name": "nikic/fast-route",
"version": "v1.3.0",
@@ -358,6 +413,69 @@
],
"time": "2016-10-10 12:19:37"
},
{
"name": "respect/validation",
"version": "1.1.22",
"source": {
"type": "git",
"url": "https://github.com/Respect/Validation.git",
"reference": "19d6ec893994912d21b390c43d287816ab070772"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Respect/Validation/zipball/19d6ec893994912d21b390c43d287816ab070772",
"reference": "19d6ec893994912d21b390c43d287816ab070772",
"shasum": ""
},
"require": {
"php": ">=5.4",
"symfony/polyfill-mbstring": "^1.2"
},
"require-dev": {
"egulias/email-validator": "~1.2",
"mikey179/vfsstream": "^1.5",
"phpunit/phpunit": "~4.0",
"symfony/validator": "~2.6.9",
"zendframework/zend-validator": "~2.3"
},
"suggest": {
"egulias/email-validator": "Strict (RFC compliant) email validation",
"ext-bcmath": "Arbitrary Precision Mathematics",
"ext-mbstring": "Multibyte String Functions",
"friendsofphp/php-cs-fixer": "Fix PSR2 and other coding style issues",
"symfony/validator": "Use Symfony validator through Respect\\Validation",
"zendframework/zend-validator": "Use Zend Framework validator through Respect\\Validation"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"Respect\\Validation\\": "library/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD Style"
],
"authors": [
{
"name": "Respect/Validation Contributors",
"homepage": "https://github.com/Respect/Validation/graphs/contributors"
}
],
"description": "The most awesome validation engine ever created for PHP",
"homepage": "http://respect.github.io/Validation/",
"keywords": [
"respect",
"validation",
"validator"
],
"time": "2018-08-01 13:06:54"
},
{
"name": "slim/php-view",
"version": "2.2.0",
@@ -477,6 +595,115 @@
"router"
],
"time": "2018-04-19 19:29:08"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "3296adf6a6454a050679cde90f95350ad604b171"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171",
"reference": "3296adf6a6454a050679cde90f95350ad604b171",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2018-04-26 10:06:28"
},
{
"name": "tuupola/cors-middleware",
"version": "0.5.2",
"source": {
"type": "git",
"url": "https://github.com/tuupola/cors-middleware.git",
"reference": "db69d8e67b99570b16e8cd5f78c423ed1167cb21"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/db69d8e67b99570b16e8cd5f78c423ed1167cb21",
"reference": "db69d8e67b99570b16e8cd5f78c423ed1167cb21",
"shasum": ""
},
"require": {
"neomerx/cors-psr7": "^1.0",
"php": "^5.5 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"squizlabs/php_codesniffer": "^2.5",
"zendframework/zend-diactoros": "^1.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Tuupola\\Middleware\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mika Tuupola",
"email": "tuupola@appelsiini.net",
"homepage": "http://www.appelsiini.net/",
"role": "Developer"
}
],
"description": "PSR-7 CORS Middleware",
"homepage": "https://github.com/tuupola/cors-middleware",
"keywords": [
"cors",
"middleware",
"slim"
],
"time": "2016-08-12 13:12:58"
}
],
"packages-dev": [

View File

@@ -17,6 +17,20 @@ session_start();
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);
// Custom error handling
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
$data = [
'status' => 'error',
'message' => $exception->getMessage()
];
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'application/json')
->write(json_encode($data));
};
};
// Set up dependencies
require __DIR__ . '/../src/dependencies.php';

View File

@@ -0,0 +1,154 @@
<?php
namespace App\Application;
class EmployeeApplication{
private $pdo;
private $cryptographyService;
private $asserts;
function __construct($employeeSettings, $mysql, $cryptographyService, $asserts){
$this->settings = $employeeSettings;
$this->cryptographyService = $cryptographyService;
$this->pdo = $mysql;
$this->asserts = $asserts;
$this->databaseSelectQueryErrorMessage = 'There was an error inserting the record.';
}
/**
* @return array
*/
function listEmployeeTypes(){
$stmt = $this->pdo->prepare("SELECT id, name FROM employeeType WHERE status = 'ACTIVE'");
$stmt->execute();
$results = $stmt->fetchAll();
if(!$results){
exit($this->databaseSelectQueryErrorMessage);
}
$stmt = null;
return $results;
}
/**
* @param $firstName varbinary
* @param $middleName varbinary
* @param $lastName varbinary or null
* @param $birthDate date yyyy-mm-dd
* @param $email string
* @param $phone string
* @return integer
*/
function saveNewPerson($firstName, $middleName, $lastName, $birthDate, $email, $phone){
$this->asserts->firstName($firstName);
$this->asserts->middleName($middleName);
$this->asserts->birthDate($birthDate);
$this->asserts->email($email);
$this->asserts->phone($phone);
try {
$stmt = $this->pdo->prepare("INSERT INTO persons (firstName, middleName, lastName, birthDate, email, phone)
VALUES (:firstName, :middleName, :lastName, :birthDate, :email, :phone)");
$this->pdo->beginTransaction();
$stmt->execute(array(':firstName' => $firstName, ':middleName' => $middleName, ':lastName' => $lastName,
':birthDate' => $birthDate, ':email' => $email, ':phone' => $phone));
$id = $this->pdo->lastInsertId();
$this->pdo->commit();
return $id;
$stmt = null;
} catch( PDOExecption $e ) {
$this->pdo->rollback();
throw new Exception('There was an error while trying to save a new person.');
$this->logger->warning("There was an error in the EmployeeApplication->saveNewPerson caused by: $e ");
}
}
/**
* @param $idEmployeeType integer
* @param $idPerson integer
* @param $code string
* @param $contractType string
* @return mixed
*/
function savePersonAsEmployee($idEmployeeType, $idPerson, $code, $contractType){
try {
$stmt = $this->pdo->prepare("INSERT INTO employees (idEmployeeType, idPerson, code, contractType)
VALUES (:idEmployeeType, :idPerson, :code, :contractType)");
$this->pdo->beginTransaction();
$stmt->execute(array(':idEmployeeType' => $idEmployeeType, ':idPerson' => $idPerson, ':code' => $code,
':contractType' => $contractType));
$id = $this->pdo->lastInsertId();
$this->pdo->commit();
return $id;
$stmt = null;
} catch( PDOExecption $e ) {
$this->pdo->rollback();
throw new Exception('There was an error while trying to save a new employee.');
$this->logger->warning("There was an error in the EmployeeApplication->savePersonAsEmployee caused by: $e ");
}
}
/**
* @param $requestData object
* @return array
*/
function saveNewEmployee($requestData){
// Getting and validating the data
$firstName = $requestData['firstName'];
$this->asserts->firstName($firstName);
$middleName = $requestData['middleName'];
$this->asserts->middleName($middleName);
$lastName = isset($requestData['lastName']) ? $requestData['lastName'] : null;
$birthDate = $requestData['birthDate'];
$this->asserts->birthDate($birthDate);
$email = $requestData['email'];
$this->asserts->email($email);
$phone = $requestData['phone'];
$this->asserts->phone($phone);
$idEmployeeType = $requestData{'idEmployeeType'};
$contractType = $requestData{'contractType'};
// Encrypting the sensitive data
$securedFirstName = $this->cryptographyService->encryptString($firstName);
$securedMiddleName = $this->cryptographyService->encryptString($middleName);
if (isset($lastName)) {
$securedLastName = $this->cryptographyService->encryptString($lastName);
} else {
$securedLastName = null;
}
$securedEmail = $this->cryptographyService->encryptString($email);
// Here begins the saving process
$idNewPerson = $this->saveNewPerson($securedFirstName, $securedMiddleName, $securedLastName,
$birthDate, $securedEmail, $phone);
$employeeCode = $this->cryptographyService->pseudoRandomStringOpenssl($this->settings['codeLength']);
$idEmployee = $this->savePersonAsEmployee($idEmployeeType, $idNewPerson, $employeeCode, $contractType);
$response = array(
"fullName" => "$firstName $middleName $lastName",
"employeeCode" => $employeeCode,
"idEmployee" => $idEmployee,
"email" => $email,
"phone" => $phone
);
return $response;
}
}
?>

View File

@@ -1,13 +1,17 @@
<?php
namespace App\Application;
use Exception;
class SessionApplication{
private $pdo;
private $cryptographyService;
private $asserts;
function __construct($mysql, $cryptographyService){
function __construct($mysql, $cryptographyService, $asserts){
$this->cryptographyService = $cryptographyService;
$this->pdo = $mysql;
$this->asserts = $asserts;
$this->databaseSelectQueryErrorMessage = 'There was an error inserting the record.';
}
@@ -39,6 +43,8 @@ class SessionApplication{
* @return mixed
*/
function getPassword($userName){
$this->asserts->userName($userName);
$stmt = $this->pdo->prepare("SELECT password FROM users WHERE name = :userName");
$stmt->execute(array(':userName' => $userName));
$results = $stmt->fetchAll();
@@ -56,6 +62,9 @@ class SessionApplication{
* @throws Exception
*/
function newSession($userName, $password){
$this->asserts->userName($userName);
$this->asserts->password($password);
$storedPassword = $this->getPassword($userName);
// If the credentials don't match anything in the the records

View File

@@ -18,7 +18,7 @@ $container['logger'] = function ($c) {
return $logger;
};
// Mysql connecrion
// Mysql connection
$container['mysql'] = function ($c) {
$mysqlSettings = $c->get('settings')['mysql'];
@@ -32,8 +32,6 @@ $container['mysql'] = function ($c) {
// Generic error messages
$databaseConnectionErrorMessage = $mysqlSettings['databaseConnectionErrorMessage'];
$databaseSelectQueryErrorMessage = $mysqlSettings['databaseSelectQueryErrorMessage'];
$databaseInsertQueryErrorMessage = $mysqlSettings['databaseInsertQueryErrorMessage'];
// Initiate the connection
$dsn = "mysql:host=$host;dbname=$database;charset=$charset";
@@ -53,8 +51,22 @@ $container['cryptographyService'] = function ($c) {
return $cryptographyService;
};
// Assert functions
$container['asserts'] = function ($c) {
$asserts = new App\Service\Asserts();
return $asserts;
};
// The session application
$container['sessionApplication'] = function ($c) {
$sessionApplication = new App\Application\SessionApplication($c['mysql'], $c['cryptographyService']);
$sessionApplication = new App\Application\SessionApplication($c['mysql'], $c['cryptographyService'], $c['asserts']);
return $sessionApplication;
};
// The employee application
$container['employeeApplication'] = function ($c) {
$employeeSettings = $c->get('settings')['employee'];
$employeeApplication = new App\Application\EmployeeApplication($employeeSettings,
$c['mysql'], $c['cryptographyService'], $c['asserts']);
return $employeeApplication;
};

View File

@@ -2,3 +2,21 @@
// Application middleware
// e.g: $app->add(new \Slim\Csrf\Guard);
// Enable cors
$app->add(new \Tuupola\Middleware\Cors([
"origin" => ["*"],
"methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
"headers.allow" => ["Accept", "Content-Type"],
"headers.expose" => [],
"credentials" => false,
"cache" => 0,
"logger" => $container['logger'],
"error" => function ($request, $response, $arguments) {
$data["status"] = "error";
$data["message"] = $arguments["message"];
return $response
->withHeader("Content-Type", "application/json")
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
]));

View File

@@ -20,9 +20,9 @@ $app->get('/api/session', function (Request $request, Response $response, array
});
$app->post('/api/session/login', function ($request, $response) {
$RequestData = $request->getParsedBody();
$requestData = $request->getParsedBody();
$data = $this->sessionApplication->newSession($RequestData['userName'], $RequestData['password']);
$data = $this->sessionApplication->newSession($requestData['userName'], $requestData['password']);
return $response->withStatus(200)
->withHeader('Content-Type', 'application/json')
@@ -33,4 +33,18 @@ $app->post('/api/session/logout', function (Request $request, Response $response
return $response->withStatus(200)
->withHeader('Content-Type', 'application/json')
->write(json_encode($this->sessionApplication->destroySession()));
});
});
$app->get('/api/employee/types', function (Request $request, Response $response, array $args) {
return $response->withStatus(200)
->withHeader('Content-Type', 'application/json')
->write(json_encode($this->employeeApplication->listEmployeeTypes()));
});
$app->post('/api/employee', function ($request, $response) {
$requestData = $request->getParsedBody();
return $response->withStatus(200)
->withHeader('Content-Type', 'application/json')
->write(json_encode($this->employeeApplication->saveNewEmployee($requestData)));
});

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Service;
use Exception;
use Respect\Validation\Validator as v;
class Asserts{
/**
* @param $string
* @throws Exception
*/
function userName($string){
$validateFirstName = v::stringType()->notEmpty()->length(1, 50)->validate($string);
if(!$validateFirstName){
throw new Exception('The user name must be a string between 1 and 50 characters');
}
}
/**
* @param $string
* @throws Exception
*/
function password($string){
$validateFirstName = v::stringType()->notEmpty()->length(1, 50)->validate($string);
if(!$validateFirstName){
throw new Exception('The password must be a string between 1 and 50 characters');
}
}
/**
* @param $string
* @throws Exception
*/
function firstName($string){
$validateFirstName = v::stringType()->notEmpty()->length(1, 100)->validate($string);
if(!$validateFirstName){
throw new Exception('The first name must be a string between 1 and 100 characters');
}
}
/**
* @param $string
* @throws Exception
*/
function middleName($string){
if(!v::stringType()->notEmpty()->length(1, 100)->validate($string)){
throw new Exception('The middle name must be a string between 1 and 100 characters');
}
}
/**
* @param $string
* @throws Exception
*/
function birthDate($string){
if(!v::date('Y-m-d')->notEmpty()->validate($string)){
throw new Exception('The birth date must be in the yyyy-mm-dd format');
}
}
/**
* @param $string
* @throws Exception
*/
function email($string){
if(!v::stringType()->notEmpty()->length(1, 100)->validate($string)){
throw new Exception('The email must be a string between 1 and 100 characters');
}
}
/**
* @param $string
* @throws Exception
*/
function phone($string){
if(!v::digit()->notEmpty()->length(10, 10)->validate($string)){
throw new Exception('The phone must be a numeric value of 10 digits');
}
}
}
?>

View File

@@ -85,4 +85,18 @@ class CryptographyService{
function decryptPassword($plainPassword, $encryptedPassword) {
return password_verify($plainPassword, $encryptedPassword);
}
/**
* Generates a psudo random string using openssl
*
* @param $length integer
* @return string
*/
function pseudoRandomStringOpenssl($length){
$string = openssl_random_pseudo_bytes($length);
$string = bin2hex($string);
return substr($string, 0, $length);
}
}

View File

@@ -40,5 +40,10 @@ return [
'databaseSelectQueryErrorMessage' => 'There was an error fetching the data.',
'databaseInsertQueryErrorMessage' => 'There was an error inserting the record.',
],
// Employee settings
'employee' => [
'codeLength' => '5',
],
],
];

View File

@@ -16,8 +16,7 @@ CREATE TABLE IF NOT EXISTS `persons` (
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP comment 'The date on which the registry was created',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment 'The date of the last time the row was modified',
PRIMARY KEY (`id`),
UNIQUE (`phone`),
UNIQUE (`firstName`,`middleName`,`lastName`,`birthDate`)
UNIQUE (`phone`)
);
INSERT INTO persons (firstName, middleName, lastName, birthDate, email, phone)
@@ -45,3 +44,32 @@ CREATE TABLE IF NOT EXISTS `users` (
INSERT INTO users (idPerson, name, password)
VALUES (1, 'sloth', '$2y$12$51mfESaLEGXDT4u9Bd9kiOHEpaJ1Bx4SEcVwsU5K6jVPMNkrnpJAa');
DROP TABLE IF EXISTS employeeType;
CREATE TABLE IF NOT EXISTS `employeeType` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL comment 'Type or rol that the employee can be',
`status` ENUM('ACTIVE', 'INACTIVE') NOT NULL DEFAULT 'ACTIVE',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP comment 'The date on which the registry was created',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment 'The date of the last time the row was modified',
PRIMARY KEY (`id`),
UNIQUE (`name`)
);
INSERT INTO employeeType (name) VALUES ('Chofer'),
('Cargador'),
('Auxiliar');
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS `employees` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`idEmployeeType` INT UNSIGNED NOT NULL comment 'Defines the rol within the company',
`idPerson` INT UNSIGNED NOT NULL comment 'Defines the rol within the company',
`code` VARCHAR(100) NOT NULL comment 'A code to reference the employee',
`contractType` ENUM('INTERNO', 'EXTERNO') NOT NULL comment 'The type of contract',
`status` ENUM('ACTIVE', 'INACTIVE') NOT NULL DEFAULT 'ACTIVE',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP comment 'The date on which the registry was created',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment 'The date of the last time the row was modified',
PRIMARY KEY (`id`),
UNIQUE (`code`)
);