diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d63cd90 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.gtm/ diff --git a/al.php b/autoloader.php similarity index 60% rename from al.php rename to autoloader.php index 09b8d6f..95c4156 100755 --- a/al.php +++ b/autoloader.php @@ -10,32 +10,6 @@ - - /* [1] On définit __SERVER_HOST__ et __SERVER_ROOT__ si c'est pas déja fait - =========================================================*/ - if( !defined('__SERVER_HOST__') || !defined('__SERVER_ROOT') ){ - /* (1) On charge le fichier de configuration */ - $json = json_decode( file_get_contents(__CONFIG__.'/server.json'), true ); - - // Si pas d'erreur, on définit - if( !is_null($json) ){ - - /* (2) Gestion de la config si server local ou remote */ - if( !isset($_SERVER['SERVER_NAME']) || !checkdnsrr($_SERVER['SERVER_NAME'], 'NS') ) - $config = $json['local']; - else - $config = $json['remote']; - - /* (3) Création des constantes */ - define('__SERVER_HOST__', $config['host']); - define('__SERVER_ROOT__', $config['root']); - - } - - } - - - /* ACTIVE LE DEBUGGAGE (WARNING + EXCEPTION) * */ diff --git a/build/api/chs/hash b/build/api/chs/hash deleted file mode 100644 index 4030334..0000000 --- a/build/api/chs/hash +++ /dev/null @@ -1 +0,0 @@ -52add802518cc5e81705e05f44abb920bc0cbf674bba0166e4c229022f4301bb diff --git a/build/api/core/AuthSystem.php b/build/api/core/AuthSystem.php new file mode 100755 index 0000000..67010b1 --- /dev/null +++ b/build/api/core/AuthSystem.php @@ -0,0 +1,29 @@ + Liste des permissions attendues + * + * @return error Erreur associée à la permission (Success/PermissionError/TokenError/etc) + * + */ + public static function permission($expected); + } + +?> diff --git a/build/api/core/AuthSystemDefault.php b/build/api/core/AuthSystemDefault.php new file mode 100755 index 0000000..52e0dbb --- /dev/null +++ b/build/api/core/AuthSystemDefault.php @@ -0,0 +1,262 @@ + no récupère le token */ + elseif( isset($_SESSION['TOKEN']) && is_string($_SESSION['TOKEN']) ) + $AUTH = $_SESSION['TOKEN']; + + + /* (3) Gestion de AUTH en fonction des tokens + ---------------------------------------------------------*/ + /* (1) Token Authentication: ADMIN */ + if( preg_match('/^a([a-f0-9]{128})$/', $AUTH, $match) ) + $_SESSION['AUTH'] = [ 'token' => $match[1], 'type' => 'admin' ]; + + /* (2) Token Authentication: USER */ + elseif( preg_match('/^u([a-f0-9]{128})$/', $AUTH, $match) ) + $_SESSION['AUTH'] = [ 'token' => $match[1], 'type' => 'user' ]; + + /* (2) Aucune authentification */ + else{ + $_SESSION['TOKEN'] = []; + $_SESSION['AUTH'] = []; + $_SESSION['USER'] = []; + $_SESSION['ADMIN'] = []; + } + + /* (4) On vérifie l'authentification par BDD + ---------------------------------------------------------*/ + if( !self::deepCheck() ){ + $_SESSION['TOKEN'] = []; + $_SESSION['AUTH'] = []; + $_SESSION['USER'] = []; + $_SESSION['ADMIN'] = []; + } + } + + + + + /* VERIFICATION DE L'AUTHENTIFICATION + * + * + */ + private static function deepCheck(){ + + /* [1] Si aucune authentification + =========================================================*/ + if( self::auth_level() == 0 ) + return false; + + + /* [2] Si authentification token -> ADMIN + =========================================================*/ + if( self::auth_level() == 2 ){ + + /* (1) Fetch admin by token */ + $fetched_admin = Repo::request('admin', 'getByToken', $_SESSION['AUTH']['token']); + + /* (2) If does not exist -> no auth */ + if( !is_array($fetched_admin) ) + return false; + + /* (3) Update global admin informations */ + $_SESSION['ADMIN'] = [ + 'id' => $fetched_admin['id_admin'], + 'username' => $fetched_admin['username'], + 'mail' => $fetched_admin['mail'] + ]; + + } + + + /* [3] Si authentification token -> USER + =========================================================*/ + if( self::auth_level() == 1 ){ + + /* (1) Fetch user by token */ + $fetched_user = Repo::request('user', 'getByToken', $_SESSION['AUTH']['token']); + + /* (2) If does not exist -> no auth */ + if( !is_array($fetched_user) ) + return false; + + /* (3) Update global user informations */ + $_SESSION['USER'] = [ + 'id' => $fetched_user['id_user'], + 'username' => $fetched_user['username'], + 'mail' => $fetched_user['mail'] + ]; + + } + + + /* [5] Si pas d'erreur d'authentification, on retourne TRUE + =========================================================*/ + return true; + } + + + + + + + + /* VERIFICATION DES ACCES EN FONCTION DE PERMISSIONS ATTENDUES + * + * @expected Liste de listes de combinaisons de permissions attendues + * + * @return error Si FALSE, pas la permission, sinon si + * + */ + public static function permission($expected){ + + $error_propag = []; + + /* [1] Check format -> if not array of array(s) -> ERROR + =========================================================*/ + /* (1) If not array -> ERROR */ + if( !is_array($expected) ) + return new Error(Err::FormatError); + + /* (2) If not array of array(s) -> ERROR */ + foreach($expected as $permissions) + if( !is_array($permissions) ) + return new Error(Err::FormatError); + + + /* [2] Foreach each set of permission + =========================================================*/ + foreach($expected as $permission_group){ + + /* If granted -> don't go further */ + $error_propag[] = self::check_permission_group($permission_group); + + if( $error_propag[count($error_propag)-1]->get() == Err::Success ) + return new Error(Err::Success); + + } + + + /* [3] By default return `PermissionError` + =========================================================*/ + if( count($error_propag) > 0 ) + return $error_propag[count($error_propag)-1]; + + return new Error(Err::PermissionError); + } + + + + + + + + /* VERIFICATION DES ACCES EN FONCTION DE PERMISSIONS ATTENDUES + * + * @expected Liste des permissions attendues + * + * @return error Err:: error constants + * + */ + private static function check_permission_group($expected){ + + + /* [1] Gestion de l'AUTH (authentification) + =========================================================*/ + + /* (1) Si entrepot requis, mais manquant + ---------------------------------------------------------*/ + if( in_array('admin', $expected) && ( self::auth_level() < 2 || !isset($_SESSION['ADMIN']['id']) ) ) + return new Error(Err::PermissionError); + + /* (2) Si admin requis, mais manquant + ---------------------------------------------------------*/ + if( in_array('user', $expected) && ( self::auth_level() < 1 || !isset($_SESSION['USER']['id']) ) ) + return new Error(Err::PermissionError); + + /* (3) On retire 'admin', et 'user' de @expected + ---------------------------------------------------------*/ + $adminIndex = array_search('admin', $expected); + $userIndex = array_search('user', $expected); + if( is_int($adminIndex) ) unset($expected[$adminIndex]); + if( is_int($userIndex) ) unset($expected[$userIndex]); + + + /* [2] Gestion des permissions CUSTOM + =========================================================*/ + + /* (1) Vérification de toutes les permissions requises */ + foreach($expected as $permission) + + // Si il manque au minimum une permission, on retourne FALSE + if( !in_array($permission, $_SESSION['PERM']) ) + return new Error(Err::PermissionError, $permission); + + + /* [4] Si on a toutes les permissions requises + =========================================================*/ + return new Error(Err::Success); + } + + + + + + /* RENVOIE LE NIVEAU D'AUTHENTIFICATION + * + * @return auth Niveau d'authentification (0 à 2) + * + */ + public static function auth_level(){ + + /* (1) Not set */ + if( !is_array($_SESSION['AUTH']) || !isset($_SESSION['AUTH']['token']) || !isset($_SESSION['AUTH']['type']) ) + return 0; + + /* (2) Admin / User */ + return ($_SESSION['AUTH']['type'] == 'admin') ? 2 : 1; + + } + + } + +?> diff --git a/build/api/core/Authentification.php b/build/api/core/Authentification.php deleted file mode 100644 index dda610c..0000000 --- a/build/api/core/Authentification.php +++ /dev/null @@ -1,177 +0,0 @@ -\[..|{@#))'.hash('sha256', $hash.'_)Q@#((%*_$%(@#') ); - $c++; - } - - - /* [2] Return result - =========================================================*/ - return $hash; - } - - - - - /* INITIALISATION DU SYSTEME ET MISE A JOUR CONSTANTES D'AUTHENTIFICATION - * - * - */ - public static function check(){ - /* (1) Initialisation des variables - ---------------------------------------------------------*/ - /* (1) Token de header */ - if( !isset($GLOBALS['TOKEN']) ) - $GLOBALS['TOKEN'] = null; - - /* (1) Liste des permissions */ - if( !isset($GLOBALS['PERM']) ) - $GLOBALS['PERM'] = []; - - - /* (2) Gestion de AUTH (authentification) dans HEADER - ---------------------------------------------------------*/ - $GLOBALS['TOKEN'] = isset($_SERVER['PHP_AUTH_DIGEST']) ? $_SERVER['PHP_AUTH_DIGEST'] : ''; - - /* (3) Gestion de AUTH en fonction du token - ---------------------------------------------------------*/ - $GLOBALS['TOKEN'] = preg_match('/^[a-f0-9]{64}$/', $GLOBALS['TOKEN'], $match) ? $match[0] : null; - - echo "regexp- ".$GLOBALS['TOKEN']."\n"; - - /* (4) On vérifie l'authentification par BDD - ---------------------------------------------------------*/ - if( !self::deepCheck() ) - $GLOBALS['TOKEN'] = null; - } - - - - - /* VERIFICATION DE L'AUTHENTIFICATION - * - * - */ - public static function deepCheck(){ - /* [1] Si aucun token - =========================================================*/ - if( is_null($GLOBALS['TOKEN']) ) - return false; - - /* [2] Vérification du système - =========================================================*/ - /* (1) Fetch cyclic-hashing-system -> check file */ - $fn = __BUILD__.'/api/chs/hash'; - - if( !is_file($fn) ) - return false; - - /* (2) Read file -> check content */ - $fc = trim( file_get_contents($fn) ); - - if( strlen($fc) !== 64 ) - return false; - - /* [3] Hash comparison - =========================================================*/ - /* (1) Compares content */ - $hashed = self::secure_hash($GLOBALS['TOKEN']); - - if( $hashed !== $fc ) - return false; - - /* (2) Stores new content */ - file_put_contents($fn, $GLOBALS['TOKEN']); - - /* (3) Stores permission */ - if( !in_array('cyclic-hash', $GLOBALS['PERM']) ) - $GLOBALS['PERM'][] = 'cyclic-hash'; - - - /* [4] Returns true if no error - =========================================================*/ - return true; - - } - - - - - /* VERIFICATION DES ACCES EN FONCTION DE PERMISSIONS ATTENDUES - * - * @module Module concerné - * @expected Liste des permissions attendues - * - * @return status Si FALSE, pas la permission, sinon si - * - */ - public static function permission($module, $expected){ - /* [1] Setup - =========================================================*/ - /* (1) If no expected, return true */ - if( !is_array($expected) || count($expected) === 0 ) - return true; - - /* (2) Mise à jour de l'authentification */ - self::check(); - - - var_dump('expected'); - var_dump($expected); - var_dump('yours'); - var_dump($GLOBALS['PERM']); - - /* [2] Gestion des permissions - =========================================================*/ - /* (1) Vérification de toutes les permissions requises */ - foreach($expected as $permission) - // Si il manque au minimum une permission, on retourne FALSE - if( !in_array($permission, $GLOBALS['PERM']) ) - return Error::PermissionError; - - - /* [3] Si on a toutes les permissions requises - =========================================================*/ - return Error::Success; - } - - - - - - /* RENVOIE LE NIVEAU D'AUTHENTIFICATION - * - * @return auth Niveau d'authentification (0 à 2) - * - */ - public static function auth(){ - return !is_array($GLOBALS['PERM']) ? 0 : count($GLOBALS['PERM']); - } - - } - -?> diff --git a/build/api/core/Checker.php b/build/api/core/Checker.php index 9dfeb0d..02d631b 100755 --- a/build/api/core/Checker.php +++ b/build/api/core/Checker.php @@ -1,5 +1,15 @@ = 0; break; - // Code RFID - case 'rfid': - return $checker && is_string($value) && preg_match('/^[\dA-F]{2}(\-[\dA-F]{2}){3,5}$/i', $value); - break; - // String quelconque (peut etre vide) case 'text': return $checker && is_string($value); @@ -97,7 +102,7 @@ // Hash sha1/md5 case 'hash': - return $checker && is_string($value) && preg_match('/^[\da-f]+$/i', $value) && (strlen($value) == 40 || strlen($value) == 64); + return $checker && is_string($value) && preg_match('/^[\da-f]{128}$/', $value); break; case 'alphanumeric': @@ -132,6 +137,14 @@ return $checker && is_string($value) && json_decode($value, true) !== NULL; break; + case 'numeric': + return $checker && (is_numeric($value) || $value == null || $value == 'null'); + break; + + case "float": + return $checker && is_float($value); + break; + default: return false; break; diff --git a/build/api/core/Loader.php b/build/api/core/Loader.php new file mode 100755 index 0000000..df9da92 --- /dev/null +++ b/build/api/core/Loader.php @@ -0,0 +1,53 @@ + URI + * + * @return outName outDesc + * + ---------------------------------------------------------*/ + public static function remote($uri){ + + /* (1) Fetch HttpRequest correct data + ---------------------------------------------------------*/ + /* (1) Parse HttpRequest data because php doesn't parse it for non-POST HTTP method */ + $httprequest = new HttpRequest(); + + /* (2) For later use -> replace default @_POST global */ + $_POST = $httprequest->POST(); + + /* (3) Get @data from @_POST values */ + $data = $_POST; + + + /* (2) Build request + ---------------------------------------------------------*/ + return new Request($uri, $data); + } + + + + + } \ No newline at end of file diff --git a/build/api/core/ModuleFactory.php b/build/api/core/ModuleFactory.php new file mode 100755 index 0000000..1c9109d --- /dev/null +++ b/build/api/core/ModuleFactory.php @@ -0,0 +1,45 @@ + Nom du module + * @arguments [OPTIONNEL] Arguments à passer au constructeur + * + * @return instance Instance du module en question + * + */ + public static function getModule($module, $arguments=[]){ + /* (1) On gère les arguments */ + $arguments = is_array($arguments) ? $arguments : []; + + /* (2) On transforme @module en namespace */ + $module_ns = str_replace('/', '\\', $module); + + + /* (1) On vérifie que la classe existe */ + if( !file_exists(__BUILD__."/api/module/$module.php") ) + return false; + + /* (2) On récupère la classe */ + $class_name = "\\api\\module\\$module_ns"; + + /* (3) On retourne une instance */ + return new $class_name($arguments); + } + + } diff --git a/build/api/core/ModuleRequest.php b/build/api/core/ModuleRequest.php deleted file mode 100755 index c64b2bf..0000000 --- a/build/api/core/ModuleRequest.php +++ /dev/null @@ -1,513 +0,0 @@ - false - ]; - - // Attributs prives utiles (initialisation) - private $path; - private $params; - private $modules; - private $options; - - // Contiendra la reponse a la requete - public $answer; - - // Contiendra l'etat de la requete - public $error; - - - - - - /* CONSTRUCTEUR D'UNE REQUETE DE MODULE - * - * @path Chemin de delegation ("module/methode") - * @param Tableau associatif contenant les parametres utiles au traitement - * - * @return status Retourne si oui ou non tout s'est bien passe - * - */ - public function __construct($path=null, $params=null){ - // Si pas parametre manquant, on quitte - if( $path == null ){ - $this->error = Error::MissingPath; - return false; - } - - /* [0] On met a jour la configuration - =========================================================*/ - // Modules specifies - $this->modules = json_decode( file_get_contents(self::config_path()), true ); - - // Gestion de l'erreur de parsage - if( $this->modules == null ){ - $this->error = Error::ParsingFailed; - return false; - } - - - - /* [1] Verification des types des parametres - =========================================================*/ - // Type de @path - if( !is_string($path) ){ // Si le type est incorrect - $this->error = Error::WrongPathModule; - return false; // On retourne FALSE, si erreur - } - - // Type de @data (optionnel) - $params = (is_array($params)) ? $params : []; - - - /* [2] Verification du chemin (existence module+methode) - =========================================================*/ - if( !$this->checkPath($path) ) // Verification de la coherence du chemin + attribution - return false; - - - - /* [3] Verification des droits - =========================================================*/ - if( !$this->checkPermission() ) // Si on a pas les droits - return false; - - - /* [4] Verification des parametres (si @type est defini) - =========================================================*/ - if( !$this->checkParams($params) ){ // Verification de tous les types - $this->error = Error::ParamError; - return false; - } - - /* [5] Récupèration des options - =========================================================*/ - $this->buildOptions(); - - - /* [6] Construction de l'objet - =========================================================*/ - $this->params = $params; - $this->error = Error::Success; - - return true; // On retourne que tout s'est bien passe - - } - - - - /* EXECUTE LE TRAITEMENT ASSOCIE ET REMPLIE LA REPONSE - * - * @return answer Retourne une reponse de type si tout s'est bien passe - * - */ - public function dispatch(){ - /* [0] Si c'est un download, on lance la methode `download()` - =========================================================*/ - if( $this->options['download'] === true ) - return $this->download(); - - /* [1] On verifie qu'aucune erreur n'a ete signalee - =========================================================*/ - if( $this->error != Error::Success ) // si il y a une erreur - return new ModuleResponse($this->error); // on la passe a la reponse - - - /* [2] On verifie que la methode est amorcable - =========================================================*/ - if( !is_callable($this->getFunctionCaller()) ){ - $this->error = Error::UncallableMethod; - return new ModuleResponse($this->error); - } - - - /* [3] On amorce la methode - =========================================================*/ - $returned = call_user_func( $this->getFunctionCaller(), $this->params ); - - - /* [4] Gestion de la reponse - =========================================================*/ - $response = new ModuleResponse($this->error); - $response->appendAll($returned); - - return $response; - } - - - - - - - - - - /* EXECUTE LE TRAITEMENT ASSOCIE ET RENVOIE UN FICHIER AVEC LE HEADER ET LE BODY SPECIFIE - * - */ - public function download(){ - /* [1] On verifie qu'aucune erreur n'a ete signalee - =========================================================*/ - if( $this->error != Error::Success ) // si il y a une erreur - return new ModuleResponse($this->error); // on la passe a la reponse - - - /* [2] On verifie que la methode est amorcable - =========================================================*/ - if( !is_callable($this->getFunctionCaller()) ){ - $this->error = Error::UncallableMethod; - return new ModuleResponse($this->error); - } - - - /* [3] On amorce la methode - =========================================================*/ - $returned = call_user_func( $this->getFunctionCaller(), $this->params ); - - - /* [4] Vérification des erreurs et paramètres - =========================================================*/ - /* (1) Vérification de l'erreur retournée, si pas Success, on retourne l'erreur */ - if( isset($returned['ModuleError']) && $returned['ModuleError'] != Error::Success ){ - $this->error = $returned['ModuleError']; - return new ModuleResponse($this->error); - } - - /* (2) Vérification du contenu, si pas défini */ - if( !isset($returned['body']) ){ - $this->error = Error::ParamError; - return new ModuleResponse($this->error); - } - - /* (3) Si @headers n'est pas défini on met par défaut */ - if( !isset($returned['headers']) || !is_array($returned['headers']) ) - $returned['headers'] = []; - - - $fromAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; - - /* [5] Si la requête vient d'ajax on crée un fichier temporaire et on renvoie son URL - =========================================================*/ - if( $fromAjax ){ - - - $tmpfname = '/tmp/download_'.uniqid().'.php'; - $bodyfname = __ROOT__.'/tmp/content_'.uniqid().'.php'; - - /* (1) On crée le fichier temporaire */ - $tmpfnameroot = __ROOT__.$tmpfname; - $tmpfile = fopen($tmpfnameroot, 'w'); - - fwrite($tmpfile, '$value) - fwrite($tmpfile, "header(\"$header: $value\");".PHP_EOL); - - /* (3) Script qui écrira le contenu */ - // 1) On écrit le contenu dans un fichier temporaire (et oui encore) - $bodyfile = fopen($bodyfname, 'w'); - fwrite($bodyfile, $returned['body']); - fclose($bodyfile); - chmod($bodyfname, 0775); - - fwrite($tmpfile, "readfile('$bodyfname');".PHP_EOL); - - /* (4) Script qui supprimera les fichiers temporaires */ - fwrite($tmpfile, "unlink('$bodyfname');".PHP_EOL); - fwrite($tmpfile, "unlink(__FILE__);".PHP_EOL); - - fwrite($tmpfile, '?>'.PHP_EOL); - - /* (5) On ferme le fichier */ - fclose($tmpfile); - chmod($tmpfnameroot, 0775); - - $response = new ModuleResponse(Error::Success); - $response->append('link', $tmpfname); - - return $response; - - /* [6] Gestion du download direct si pas AJAX - =========================================================*/ - }else{ - /* (1) On définit les headers */ - foreach($returned['headers'] as $header=>$value) - header($header.': '.$value); - - /* (2) On affiche le contenu */ - echo $returned['body']; - - return true; - } - } - - - /* DESERIALISATION A PARTIR DES DONNEES POST - * - * @url Contenu de l'url après api/ (si existe) - * @post Tableau des donnes $_POST => @path + @data (opt) - * - * @return instance Retourne un objet de type - * - */ - public static function fromPost($url, $post){ - - /* [1] On verifie que le @path est renseigne - =========================================================*/ - /* (1) Si le path est dans @url */ - $pathInUrl = count($url) > 0 && is_string($url[0]) && strlen($url[0]) > 0 && preg_match('#^([\w_-]+)/([\w_-]+)/?$#', $url[0], $urlMatches); - - // On l'utilise pour le chemin - if( $pathInUrl ) - $post['path'] = $urlMatches[1].'/'.$urlMatches[2]; - - /* (2) On vérifie dans tous les cas si le path existe */ - if( !isset($post['path']) ) - return new ModuleRequest(); - - - - /* [2] On verifie que @data est renseigne - =========================================================*/ - // Si variable n'existe pas, on cree un tableau vide - $params = $post; - - // On retire le @path de @params - unset($params['path']); - - - - /* [3] On met les paramètres JSON en JSON (si ils décodent sans erreur) - =========================================================*/ - foreach($params as $name=>$value){ - $json = json_decode( $value, true ); - // Si aucune erreur, on affecte la valeur - if( !is_null($json) ) - $params[$name] = $json; - } - /* [4] On retourne une instance de - =========================================================*/ - // On cree notre requete avec le token - return new ModuleRequest($post['path'], $params); - } - - - - - - /* VERIFICATION DU FORMAT ET DE LA COHERENCE DU CHEMIN SPECIFIE - * - * @path String correspondant au chemin de delegation ("module/methode") - * - * @return validity Retourne si oui ou non l'objet est correct - * - */ - private function checkPath($path){ - /* [1] Verification format general - =========================================================*/ - if( !preg_match('#^([\w_-]+)/([\w_-]+)$#i', $path, $matches) ){ // Si mauvais format - $this->error = Error::WrongPathModule; - return false; - } - - // On recupere les donnes de la regex - $module = $matches[1]; - $method = $matches[2]; - - - /* [2] Verification de l'existence du module (conf) - =========================================================*/ - if( !array_key_exists($module, $this->modules) ){ // Si le module n'est pas specifie dans la conf - $this->error = Error::UnknownModule; - return false; // On retourne FALSE, si erreur - } - - - /* [3] Verification de l'existence de la methode (conf) - =========================================================*/ - if( array_key_exists($method, $this->modules[$module]) === false ){ // Si la methode n'est pas specifie dans la conf - $this->error = Error::UnknownMethod; - return false; // On retourne FALSE, si erreur - } - - - - /* [4] Enregistrement du chemin et renvoi de SUCCESS - =========================================================*/ - $this->path = [ - 'module' => $module, - 'method' => $method - ]; - - return true; - } - - - - - - - /* RETOURNE SI ON A LA PERMISSION D'EXECUTER CETTE METHODE - * - * @return permission Retourne si on a les droits ou pas pour executer cette methode - * - */ - private function checkPermission(){ - /* [1] On recupere les informations utiles - =========================================================*/ - // On recupere le nom de la methode - $method = $this->modules[$this->path['module']][$this->path['method']]; - - // Si aucune permission n'est definie - if( !isset($method['permissions']) ) return true; - - - /* [2] Vérification des permissions et de l'authentification - =========================================================*/ - $granted = Authentification::permission($this->path['module'], $method['permissions']); - - /* (1) On retourne FAUX si aucun droit n'a ete trouve */ - if( $granted !== Error::Success ){ - $this->error = $granted; - return false; - } - - - /* [3] On retourne VRAI si la permission est ok - =========================================================*/ - return true; - } - - - - - /* VERIFICATION DU TYPE DES PARAMETRES ENVOYES - * - * @params Tableau associatif contenant les parametres - * @params peut se voir rajouter les paramètres optionnels s'ils ne sont pas renseignés (initialisés à NULL) - * - * @return correct Retourne si oui ou non les parametres ont le bon type - * - */ - private function checkParams(&$params){ - /* [1] On verifie qu'il ne manque aucun parametre - =========================================================*/ - // Si @params n'est pas un tableau - if( !is_array($params) ) return false; - - $method = $this->modules[$this->path['module']][$this->path['method']]; - - - /* [2] Si le type est defini, pour chaque param, on teste - =========================================================*/ - foreach($method['parameters'] as $name=>$paramsdata){ - /* (1) On récupère si le paramètre est optionnel ou pas */ - $optional = isset($paramsdata['optional']) && $paramsdata['optional'] === true; - - /* (2) Récupère si le paramètre est un fichier et définit comme de type 'FILE' */ - $isFile = isset($paramsdata['type']) && $paramsdata['type'] == 'FILE' && isset($_FILES[$name]); - - /* (3) Si le paramètre est obligatoire et qu'il n'est pas donné -> erreur */ - if( !isset($params[$name]) && !$optional && !$isFile ) - return false; - - /* (4) Si le type n'est pas defini, on a pas besoin de le vérifier */ - if( !isset($paramsdata['type']) ) - continue; - - /* (5) Si le paramètre est optionnel et n'est pas donné */ - if( $isFile || $optional && (!isset($params[$name]) || is_null($params[$name])) ){ - // On le crée le param optionnel avec la valeur NULL - $params[$name] = null; - - // On donne une référence vers le fichier, si c'en est un - if( $isFile ) - $params[$name] = &$_FILES[$name]; - - continue; // On passe au paramètre suivant - - - /* (6) Si le paramètre est renseigné */ - }else - // Si la verification est fausse, on retourne faux - if( !Checker::run($paramsdata['type'], $params[$name]) ) - return false; - - } - - /* [3] Gestion du retour, si tout s'est bien passe - =========================================================*/ - return true; - } - - - - - - /* AJOUT DES OPTIONS A PARTIR DE LA CONFIGURATION - * - */ - private function buildOptions(){ - /* [0] On récupère les options de la méthode en cours - =========================================================*/ - $method = $this->modules[$this->path['module']][$this->path['method']]; - - /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ - if( !isset($method['options']) || !is_array($method['options']) ) - return true; - - /* (2) Par défaut on définit les options par défaut */ - $this->options = self::$default_options; - - - /* (3) On récupère les options données */ - $options = $method['options']; - - - /* [1] Gestion des différentes options - =========================================================*/ - foreach($options as $option=>$value){ - /* (1) On ne prend en compte l'option que si elle est dans les options par défaut */ - if( !isset(self::$default_options[$option]) ) - continue; - - /* (2) Le type de la valeur doit être le même que celui de la valeur par défaut */ - if( gettype($value) != gettype(self::$default_options[$option]) ) - continue; - - /* (3) Si tout est bon, on définit la valeur */ - $this->options[$option] = $value; - } - - return true; - - } - - - - - - /* RENVOI LE CHEMIN D'AMORCAGE DE LA METHODE - * - * @return path Retourne le chemin d'amorcage de la requete - * - */ - private function getFunctionCaller(){ - return [ '\\api\\module\\'.$this->path['module'], $this->path['method'] ]; - } - - - } - -?> diff --git a/build/api/core/Request.php b/build/api/core/Request.php new file mode 100755 index 0000000..305861b --- /dev/null +++ b/build/api/core/Request.php @@ -0,0 +1,620 @@ + false ]; + private static $authsystem = null; + // liste des methodes HTTP autorisées + private static $allowed_http_methods = [ "GET", "POST", "PUT", "DELETE" ]; + + + // Attributs prives utiles (initialisation) + private $path; // chemin de base (uri) + private $raw_params; // paramètres reçus + private $params; // paramètres donnés à la fonction + private $schema; // schema configuration + private $options; // options + private $http_method; // methode HTTP appelante + + // Contiendra la reponse a la requete + public $answer; + + // Contiendra l'etat de la requete + public $error; + + + /* (0) Constructeur d'une requete de module + * + * @uri URI relative de l'appel + * @param Tableau associatif contenant les parametres utiles au traitement + * @forced_method Méthode demandée (optionnel) + * + * @return instance Instance crée + * + ---------------------------------------------------------*/ + public function __construct($uri=null, $params=null, $forced_method=null){ + + return $this->buildRequestObject($uri, $params, $forced_method); + + } + + + + /* (1) Constructeur d'une requete de module (delegation) + * + * @uri URI relative de l'appel + * @param Tableau associatif contenant les parametres utiles au traitement + * @forced_method Méthode demandée (optionnel) + * + * @return status Retourne si oui ou non tout s'est bien passe + * + ---------------------------------------------------------*/ + private function buildRequestObject($uri=null, $params=null, $forced_method=null){ + + /* (1) Initialisation + ---------------------------------------------------------*/ + /* (1) Erreur par défaut */ + $this->error = new Error(Err::Success); + + /* (2) Si pas parametre manquant, on quitte */ + if( $uri == null ) + return $this->error->set(Err::MissingPath); + + + /* (2) On met a jour la configuration + ---------------------------------------------------------*/ + /* (1) Build from configuration */ + $this->buildConfig(); + + /* (2) Dispatch if error */ + if( $this->error->get() != Err::Success ) + return; + + + /* (3) Verification des types des parametres + ---------------------------------------------------------*/ + /* (1) Si path est une */ + if( !is_string($uri) ) // Si le type est incorrect + return $this->error->set(Err::WrongPathModule); + + /* (2) Formattage @params en tableau */ + $this->raw_params = (is_array($params)) ? $params : []; + + /* (3) On définit en constante la méthode HTTP */ + if( !isset($_SERVER['REQUEST_METHOD']) && !is_string($forced_method) ) + return $this->error->set(Err::UnknownHttpMethod); + + $this->http_method = is_string($forced_method) ? strtoupper($forced_method) : strtoupper($_SERVER['REQUEST_METHOD']); + + + /* (4) Verification du chemin (existence module+methode) + ---------------------------------------------------------*/ + if( !$this->checkURI($uri) ) // Verification de la coherence du chemin + attribution + return false; // checkURI() sets the error itself + + + /* (5) Verification des permissions + ---------------------------------------------------------*/ + if( !$this->checkPermission() ) // Si on a pas les droits + return false; // checkPermission() sets the error itself + + + /* (6) Verification des parametres (si @type est defini) + ---------------------------------------------------------*/ + if( !$this->checkParams() ) // Verification de tous les types + return false; // checkParams() sets the error itself + + + /* (7) Récupèration des options + ---------------------------------------------------------*/ + $this->buildOptions(); + + + /* (8) Construction de l'objet (add http method to params) + ---------------------------------------------------------*/ + $this->params['HTTP_METHOD'] = $this->http_method; + $this->error->set(Err::Success); + + return true; // On retourne que tout s'est bien passe + } + + + + /* (2) Definit le systeme d'authentification + * + * @instance Instance de type AuthSystem + * + * @return success Whether the AuthSystem is valid or not + * + ---------------------------------------------------------*/ + public static function setAuthSystem($instance=null){ + /* (1) Check instance type */ + if( !($instance instanceof AuthSystem) ) + return false; + + /* (2) Store instance */ + self::$authsystem = $instance; + + return true; + } + + + + /* (3) Construction du schéma à partir de la configuration + * + ---------------------------------------------------------*/ + private function buildConfig(){ + + /* (1) Access file content + ---------------------------------------------------------*/ + /* (1) Vérification existence fichier config */ + if( !file_exists(self::config_path()) ) + return $this->error->set(Err::UnreachableResource); + + /* (2) Lecture fichier config */ + $conf = @file_get_contents(self::config_path()); + + /* (3) Si erreur lecture */ + if( $conf === false ) + return $this->error->set(Err::UnreachableResource); + + /* (4) Parsage json */ + $this->schema['raw'] = json_decode( $conf, true ); + + /* (5) Gestion de l'erreur de parsage */ + if( $this->schema['raw'] == null ) + return $this->error->set(Err::ParsingFailed, 'json'); + + + /* (2) Construction des outils d'accès + ---------------------------------------------------------*/ + /* (1) Initialisation */ + $this->schema['index'] = []; + + /* (2) Pour chaque chemin */ + foreach($this->schema['raw'] as $path=>$methods){ + + /* (2.1) Pour chaque méthode */ + foreach($methods as $method=>$data){ + + /* (2.1.1) Suppression si pas dans les méthodes autorisées */ + if( !in_array($method, self::$allowed_http_methods) ){ + unset($this->schema[$path][$method]); + continue; + } + + /* (2.1.2) Création de l'index pour le chemin si n'existe pas déja */ + if( !isset($this->schema['index'][$path]) ) + $this->schema['index'][$path] = []; + + /* (2.1.3) Ajout de la méthode à l'index */ + $this->schema['index'][$path][] = $method; + + } + + } + + } + + + + /* (4) Verification du format et de la coherence du chemin specifie + * + * @path String correspondant au chemin de delegation ("module/methode") + * + * @return validity Retourne si oui ou non l'objet est correct + * + ---------------------------------------------------------*/ + private function checkURI($uri){ + + /* (1) Verification format general + ---------------------------------------------------------*/ + /* (1) If wrong format -> exit */ + if( !preg_match('@^\w+(\/\w+)*\/?$@', $uri, $matches) ) + return $this->error->set(Err::WrongPathModule); + + + /* (2) Verification de l'existence du chemin (conf) + ---------------------------------------------------------*/ + /* (1) Check if begins with each indexed @path */ + $exists_size = 0; + $path = null; + + foreach($this->schema['index'] as $key=>$void){ + $match_size = strlen($key); + + /* (1.1) Look for the longer match */ + if( $match_size > $exists_size && substr($uri, 0, $match_size) == $key ){ + $exists_size = $match_size; + $path = $key; + } + + } + + /* (2) If @path not found -> exit */ + if( is_null($path) ) + return $this->error->set(Err::UnknownModule); + + + /* (3) Extract URI parameters + ---------------------------------------------------------*/ + /* (1) Extract URI string after @path */ + $uri_end = substr($uri, $exists_size); + + /* (2) If invalid format, return error */ + if( !preg_match('@^((?:\/[^\/]+)*)\/?$@', $uri_end, $uri_match) ) + return $this->error->set(Err::InvalidURI); + + /* (3) Add each URI parameter to the parameter store */ + $uri_args = array_slice( explode('/', $uri_match[1]), 1); + + foreach($uri_args as $index=>$value) + $this->raw_params["URL$index"] = $value; + + /* (4) Verification de l'existence de la methode (conf) + ---------------------------------------------------------*/ + /* (1) Check if HTTP method is in allowed methods */ + if( !in_array($this->http_method, self::$allowed_http_methods) ) + return $this->error->set(Err::UnknownHttpMethod, $this->http_method); + + /* (2) Check if HTTP method is defined for this @path */ + if( !in_array($this->http_method, $this->schema['index'][$path]) ) + return $this->error->set(Err::UnknownMethod, $this->http_method); + + + + /* (5) Enregistrement du chemin et renvoi de SUCCESS + ---------------------------------------------------------*/ + $this->path = [ + 'path'=> $path, + 'method'=> $this->http_method + ]; + + return true; + } + + + + /* (5) Retourne si on a la permission d'executer cette methode + * + * @return permission Retourne si on a les droits ou pas pour executer cette methode + * + ---------------------------------------------------------*/ + private function checkPermission(){ + + /* (1) On recupere les informations utiles + ---------------------------------------------------------*/ + // On recupere le nom de la methode + $method = $this->schema['raw'][$this->path['path']][$this->path['method']]; + + // Si aucune permission n'est definie + if( !isset($method['permissions']) || !is_array($method['permissions']) || count($method['permissions']) < 1 ) + return true; + + /* (2) Vérification des permissions et de l'authentification + ---------------------------------------------------------*/ + // if no AuthSystem set up, use the default one + if( !is_object(self::$authsystem) || !self::$authsystem instanceof AuthSystem ){ + + // try to load default AuthSystem + if( !file_exists(__BUILD__.'/api/core/AuthSystemDefault.php') ) + return $this->error->set(Err::UnreachableResource); + + // load default AuthSystem class + $classname = '\\api\\core\\AuthSystemDefault'; + self::$authsystem = new $classname(); + } + + // Check permission using user-implemented AuthSystem + $granted = self::$authsystem::permission( $method['permissions'] ); + + /* (1) On retourne FAUX si aucun droit n'a ete trouve */ + if( $granted->get() !== Err::Success ){ + $this->error = $granted; + return false; + } + + + /* On retourne VRAI si la permission est ok */ + return true; + + } + + + + /* (6) Verification du type des parametres envoyes + * + * @return correct Retourne si oui ou non les parametres ont le bon type + * + ---------------------------------------------------------*/ + private function checkParams(){ + + /* (1) On verifie qu'il ne manque aucun parametre + ---------------------------------------------------------*/ + /* (1) Si @params n'est pas un tableau */ + if( !is_array($this->raw_params) ) + return $this->error->set(Err::MissingParam); + + /* (2) On récupère les données de la méthode */ + $method = $this->schema['raw'][$this->path['path']][$this->path['method']]; + + /* (3) Si pas 'parameters' dans la config */ + if( !isset($method['parameters']) || !is_array($method['parameters']) ) + return $this->error->set(Err::ConfigError); + + + /* (2) Si le type est defini, pour chaque param, on teste + ---------------------------------------------------------*/ + foreach($method['parameters'] as $name=>$config){ + + /* (2.1) Vérification des données + ---------------------------------------------------------*/ + /* (1) Si @name n'est pas une string */ + if( !is_string($name) ) + return $this->error->set(Err::ConfigError); + + /* (2) Si @config n'est pas un tableau */ + if( !is_array($config) ) + return $this->error->set(Err::ConfigError); + + /* (3) So @config['type] manquant ou incorrect */ + if( !isset($config['type']) || !is_string($config['type']) ) + return $this->error->set(Err::ConfigError); + + + /* (2.2) Gestion des spécifications + ---------------------------------------------------------*/ + /* (1) On récupère le paramètre RENAME */ + $rename = $name; + if( isset($config['rename']) && is_string($config['rename']) && preg_match('@^\w+$@', $config['rename']) ) + $rename = $config['rename']; + + /* (1) On récupère si le paramètre est optionnel ou pas */ + $optional = isset($config['optional']) && $config['optional'] === true; + + /* (2) Si de type 'FILE' + fichier existe => on enregistre la ref. */ + if( $config['type'] == 'FILE' && isset($_FILES[$name]) ) + $this->params[$rename] = &$_FILES[$name]; + + /* (3) Si param obligatoire et manquant -> erreur */ + if( !isset($this->raw_params[$name]) && !$optional ) + return $this->error->set(Err::MissingParam, $name); + + + /* (2.3) Gestion des valeurs + ---------------------------------------------------------*/ + /* (1) Si le paramètre est optionnel et manquant */ + if( $optional && !isset($this->raw_params[$name]) ){ + + // On le crée le param optionnel avec la valeur NULL + $this->params[$rename] = null; + + /* (2) Si le paramètre est renseigné (sauf FILE) */ + }elseif( $config['type'] != 'FILE'){ + + // Si la verification est fausse, on retourne faux + if( !Checker::run($config['type'], $this->raw_params[$name]) ) + return $this->error->set(Err::WrongParam, $name, $config['type']); + + // Sinon, on ajoute aux params qu'on enverra à l'appel + else + $this->params[$rename] = $this->raw_params[$name]; + + } + + } + + + /* (3) Gestion du retour, si tout s'est bien passe + ---------------------------------------------------------*/ + return true; + + } + + + + /* (7) Ajout des options a partir de la configuration + * + * @return correct Retourne FAUS en cas d'erreur + * + ---------------------------------------------------------*/ + private function buildOptions(){ + + /* (1) On récupère les options de la méthode en cours + ---------------------------------------------------------*/ + $method = $this->schema['raw'][$this->path['path']][$this->path['method']]; + + /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ + if( !isset($method['options']) || !is_array($method['options']) ) + return true; + + /* (2) Par défaut on définit les options par défaut */ + $this->options = self::$default_options; + + /* (3) On récupère les options données */ + $options = $method['options']; + + + /* (2) Gestion des différentes options + ---------------------------------------------------------*/ + foreach($options as $option=>$value){ + + /* (1) On ne prend en compte l'option que si elle est dans les options par défaut */ + if( !isset(self::$default_options[$option]) ) + continue; + + /* (2) Le type de la valeur doit être le même que celui de la valeur par défaut */ + if( gettype($value) != gettype(self::$default_options[$option]) ) + continue; + + /* (3) Si tout est bon, on définit la valeur */ + + $this->options[$option] = $value; + } + + return true; + + } + + + + /* (8) Execute le traitement associe et remplie la reponse + * + * @return answer Retourne une reponse de type si tout s'est bien passe + * + ---------------------------------------------------------*/ + public function dispatch(){ + + /* (1) On verifie qu'aucune erreur n'a ete signalee + ---------------------------------------------------------*/ + if( $this->error->get() !== Err::Success ) // si il y a une erreur + return new Response($this->error); // on la passe a la reponse + + + /* (2) On essaie d'instancier le module + ---------------------------------------------------------*/ + $instance = ModuleFactory::getModule($this->path['path']); + + if( $instance instanceof Error ){ + $this->error->set(Err::UncallableModule, $this->path['path']); + return new Response($this->error); + } + + + /* (3) On verifie que la methode est amorcable + ---------------------------------------------------------*/ + if( !is_callable([$instance, $this->path['method']]) ){ + $this->error->set(Err::UncallableMethod, $this->path['method']); + return new Response($this->error); + } + + + /* (4) On amorce la methode + ---------------------------------------------------------*/ + /* (1) On lance la fonction */ + $returned = call_user_func( [$instance, $this->path['method']], $this->params ); + + /* (2) On appelle le destructeur (si défini) */ + $instance = null; + + + /* (5) Gestion de la reponse + ---------------------------------------------------------*/ + /* (1) S'il s'agit d'un téléchargement -> on dispatch */ + if( $this->options['download'] === true ) + return $this->download(); + + /* (2) On construit la réponse avec l'erreur */ + $response = new Response($this->error); + + /* (3) On ajoute les données */ + $response->appendAll($returned); + + /* (4) On retourne la réponse */ + return $response; + + } + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET RENVOIE UN FICHIER AVEC LE HEADER ET LE BODY SPECIFIE + * + */ + public function download(){ + + /* (1) Vérification des erreurs et paramètres + ---------------------------------------------------------*/ + /* (1) Si retourne 'error' et n'est pas SUCCESS -> error */ + if( isset($returned['error']) && $returned['error'] instanceof Error && $returned['error']->get() != Err::Success ){ + $this->error = $returned['error']; + return new Response($this->error); + } + + /* (2) Vérification du contenu, si pas défini */ + if( !isset($returned['body']) ){ + $this->error->set(Err::MissingBody); + return new Response($this->error); + } + + /* (3) Si @headers n'est pas défini on met par défaut */ + if( !isset($returned['headers']) || !is_array($returned['headers']) ){ + $this->error->set(Err::MissingHeaders); + return new Response($this->error); + } + + /* (4) Détermine si téléchargement AJAX/DIRECT */ + $from_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + + + /* (2) Gestion du téléchargement direct (HTTP) + ---------------------------------------------------------*/ + if( !$from_ajax ){ + + /* (1) On définit les headers */ + foreach($returned['headers'] as $header=>$value) + header($header.': '.$value); + + /* (2) On affiche le contenu */ + echo $returned['body']; + + return true; + + } + + + /* (3) Gestion du téléchargement différé (AJAX) + ---------------------------------------------------------*/ + /* (1) On génère les noms de fichiers utiles */ + $target_fname = '/tmp/download_'.uniqid().'.php'; // cible + $buffer_fname = __ROOT__.'/tmp/content_'.uniqid().'.php'; // buffer + + /* (2) On écrit le BODY dans un fichier buffer */ + $buffer_file = fopen($buffer_fname, 'w'); + fwrite($buffer_file, $returned['body']); + fclose($buffer_file); + + /* (3) On crée le fichier cible */ + $target_fnameroot = __PUBLIC__.$target_fname; + $taret_file = fopen($target_fnameroot, 'w'); + fwrite($taret_file, '$value) + fwrite($taret_file, "header(\"$header: $value\");".PHP_EOL); + + /* (5) Script qui écrira le contenu du buffer */ + chmod($buffer_fname, 0775); + fwrite($taret_file, "readfile('$buffer_fname');".PHP_EOL); + + /* (6) Script qui supprimera les fichiers: buffer+target */ + fwrite($taret_file, "unlink('$buffer_fname');".PHP_EOL); + fwrite($taret_file, "unlink(__FILE__);".PHP_EOL); + fwrite($taret_file, '?>'.PHP_EOL); + + /* (7) On ferme le fichier cible */ + fclose($taret_file); + chmod($target_fnameroot, 0775); + + /* (8) On envoie la réponse contenant le lien du fichier cible */ + $response = new Response($this->error); + $response->append('link', $target_fname); + + return $response; + + } + + + } + +?> diff --git a/build/api/core/ModuleResponse.php b/build/api/core/Response.php similarity index 70% rename from build/api/core/ModuleResponse.php rename to build/api/core/Response.php index 256a7ee..5cd3e82 100755 --- a/build/api/core/ModuleResponse.php +++ b/build/api/core/Response.php @@ -1,48 +1,41 @@ Erreur passee par la requete (si existe) * */ - public function __construct($error=Error::Success){ + public function __construct($error=null){ + if( !( $error instanceof Error ) ) + $error = new Error(Err::Success); + $this->data = []; $this->error = $error; } + /* AJOUTE UNE DONNEE A LA REPONSE - - - - - - /* AJOUTE UNE DONNEE A LA REPONSE * * @key Le nom de la valeur a ajouter * @value La valeur a ajouter @@ -56,12 +49,6 @@ } - - - - - - /* AJOUTE TOUTES LES DONNEES A LA REPONSE * * @dataset Le tableau associatif correspondant a la reponse @@ -69,14 +56,15 @@ */ public function appendAll($dataset){ // Si ce n'est pas un tableau, on ne fais rien - if( !is_array($dataset) ) return $this; + if( !is_array($dataset) ) + return $this; // Si une valeur contient une erreur - if( array_key_exists('ModuleError', $dataset) ){ + if( array_key_exists('error', $dataset) && $dataset['error'] instanceof Error){ // On definit cette erreur - $this->error = $dataset['ModuleError']; + $this->error = $dataset['error']; // On enleve cette entree des donnees - unset($dataset['ModuleError']); + unset($dataset['error']); } // Ajoute une entree pour la cle @key et de valeur @value @@ -84,14 +72,9 @@ return $this; } + /* RECUPERE UNE DONNEE DE LA REPONSE - - - - - - /* RECUPERE UNE DONNEE DE LA REPONSE * * @key Le nom de la valeur a recuperer * @@ -109,12 +92,6 @@ } - - - - - - /* RECUPERE TOUTES LES DONNEES DE LA REPONSE * * @return data Les donnees de la reponse @@ -126,10 +103,6 @@ } - - - - /* SERIALISATION A PARTIR DES DONNEES * * @return json Retourne les donnees serialisees @@ -138,16 +111,15 @@ public function serialize(){ // Code Http - Error::setHttpCode($this->error); + $this->error->setHttpCode(); // Type de contenu header('Content-Type: application/json; charset=utf-8'); // On rajoute l'erreur au message - $returnData = array_merge( - [ - 'ModuleError' => $this->error, - 'ErrorDescription' => Error::explicit($this->error) + $returnData = array_merge([ + 'error' => $this->error->get(), + 'ErrorDescription' => $this->error->explicit() ], $this->data ); @@ -156,8 +128,6 @@ } - - } ?> diff --git a/build/api/module/admin.php b/build/api/module/admin.php new file mode 100644 index 0000000..66cd973 --- /dev/null +++ b/build/api/module/admin.php @@ -0,0 +1,17 @@ + 'post' ]; + } + + } diff --git a/build/database/core/DatabaseDriver.php b/build/database/core/DatabaseDriver.php new file mode 100755 index 0000000..83007eb --- /dev/null +++ b/build/database/core/DatabaseDriver.php @@ -0,0 +1,197 @@ + Database Server's host + * @dbname Database name + * @username Database username + * @password Database password + * + */ + private function __construct($host, $dbname, $username, $password){ + /* (2) Stores configuration */ + $this->host = $host; + $this->dbname = $dbname; + $this->username = $username; + $this->password = $password; + + try{ + + $this->pdo = new \PDO('mysql:host='.$this->host.';dbname='.$this->dbname, $this->username, $this->password, [ + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + \PDO::ATTR_TIMEOUT => 5 + ]); + + // On signale que tout s'est bien passe + $this->error = new Error(Err::Success); + + }catch(Exception $e){ + // On signale qu'il y a une erreur + $this->error = new Error(Err::PDOConnection); + } + } + + + + /************************************************ + **** Multiton Management (static) **** + ************************************************/ + + /* ADDS A NEW CONNECTION + * + * @label [optional] Database Label + * + * @return status If added successfully + * + */ + private static function add($label=null){ + $conf = self::conf(); + + /* [1] Default values + =========================================================*/ + /* (1) If label isn't given */ + is_null($label) && ($label = 'default'); + + /* (2) If label and no path */ + if( $label !== 'default' && !isset($conf[$label]) ) + return false; + + + /* [3] Instanciates the driver + =========================================================*/ + try{ + + /* (1) If local -> instanciates with local configuration */ + // if( !checkdnsrr($_SERVER['SERVER_NAME'], 'NS') ) + self::$instance[$label] = new DatabaseDriver($conf[$label]['local']['host'], $conf[$label]['local']['dbname'], $conf[$label]['local']['user'], $conf[$label]['local']['password']); + /* (2) If Remote -> instanciates with Remote configuration */ + // else + // self::$instance[$label] = new DatabaseDriver($conf[$label]['remote']['host'], $conf[$label]['remote']['dbname'], $conf[$label]['remote']['user'], $conf[$label]['remote']['password']); + + return true; + + }catch(\Exception $e){ + + /* (3) If fails */ + return false; + + } + + } + + + /* GET A DATABASE DRIVER INSTANCE + * + * @label [optional] Driver's label + * + * @return driver Multiton + * + */ + public static function get($label=null){ + $conf = self::conf(); + + /* [1] Checks arguments + =========================================================*/ + /* (1) Label default value */ + is_null($label) && ($label = 'default'); + + /* (2) If no label, or unknown label */ + if( is_null($label) || !isset(self::$instance[$label]) ){ + + /* (2.1) Try to add the configuration if exists */ + if( isset($conf[$label]) ){ + self::add($label); + return self::get($label); + } + + + throw new \Exception('Database @label is incorrect.'); + } + + + /* [2] Returns instance + =========================================================*/ + return self::$instance[$label]; + } + + + /** retourne la connection statique + * @param null $label + * @return \PDO + */ + public static function getPDO($label=null){ + $instance = self::get($label); + + return $instance->pdo; + } + + + public function pdo(){ + return $this->pdo; + } + + + public function getConfig(){ + return [ + 'host' => $this->host, + 'dbname' => $this->dbname, + 'username' => $this->username + ]; + } + + + + } +?> diff --git a/build/database/core/Repo.php b/build/database/core/Repo.php new file mode 100644 index 0000000..d591b3d --- /dev/null +++ b/build/database/core/Repo.php @@ -0,0 +1,79 @@ +pdo()); + + + /* (3) Check if the method exists */ + if( !\method_exists($instance, $method) ) + throw new \Exception("Repo '$repo' has no public method '$method'"); + + /* (4) Fetch response (send arguments as well) */ + $response = call_user_func_array([$instance, $method], array_slice(func_get_args(), 2)); + + /* (5) Call post-script */ + $instance = null; + + /* (6) Dispatch response */ + return $response; + + } + + + + + } \ No newline at end of file diff --git a/build/database/core/Repo_i.php b/build/database/core/Repo_i.php new file mode 100644 index 0000000..5b9aad5 --- /dev/null +++ b/build/database/core/Repo_i.php @@ -0,0 +1,18 @@ +pdo = $pdo; + + } + + } \ No newline at end of file diff --git a/build/database/repo/admin.php b/build/database/repo/admin.php new file mode 100644 index 0000000..0053474 --- /dev/null +++ b/build/database/repo/admin.php @@ -0,0 +1,250 @@ + The admin list + * FALSE on error + * + ---------------------------------------------------------*/ + public function getAll(){ + + /* (1) Statement */ + $st = $this->pdo->query("SELECT * FROM `admin` ORDER BY `username` ASC"); + + /* (2) Fetched data */ + return $st->fetchAll(); + + } + + + /* (2) Return a admin by its `id_admin` + * + * @id_admin The admin UID + * + * @return admin The admin if found + * FALSE on error + * + ---------------------------------------------------------*/ + public function getById(int $id_admin){ + + /* (1) Prepare Statement */ + $pst = $this->pdo->prepare("SELECT * FROM `admin` WHERE `id_admin` = :id_admin LIMIT 1"); + + /* (2) Bind variables */ + $pst->bindParam(':id_admin', $id_admin, \PDO::PARAM_INT); + + /* (3) Execute */ + if( !$pst->execute() ) return false; // if error -> send FALSE + + /* (4) Fetched data */ + return $pst->fetch(); + + } + + + /* (3) Return a admin by its `mail` + * + * @mail The admin mail address + * + * @return admin The admin if found + * FALSE on error + * + ---------------------------------------------------------*/ + public function getByMail(String $mail){ + + /* (1) Prepare Statement */ + $pst = $this->pdo->prepare("SELECT * FROM `admin` WHERE `mail` = :mail LIMIT 1"); + + /* (2) Bind variables */ + $pst->bindParam(':mail', $mail, \PDO::PARAM_STR, 50); + + /* (3) Execute */ + if( !$pst->execute() ) return false; // if error -> send FALSE + + /* (4) Fetched data */ + return $pst->fetch(); + + } + + + /* (4) Return a admin by its `username` + * + * @username The admin username + * + * @return admin The admin if found + * FALSE on error + * + ---------------------------------------------------------*/ + public function getByUsername(String $username){ + + /* (1) Prepare Statement */ + $pst = $this->pdo->prepare("SELECT * FROM `admin` WHERE `username` = :username LIMIT 1"); + + /* (2) Bind variables */ + $pst->bindParam(':username', $username, \PDO::PARAM_STR, 20); + + /* (3) Execute */ + if( !$pst->execute() ) return false; // if error -> send FALSE + + /* (4) Fetched data */ + return $pst->fetch(); + + } + + + /* (5) Return a admin by its `token` + * + * @token The admin token + * + * @return admin The admin if found + * FALSE on error + * + ---------------------------------------------------------*/ + public function getByToken(String $token){ + + /* (1) Prepare Statement */ + $pst = $this->pdo->prepare("SELECT * FROM `admin` WHERE `token` is not NULL AND `token` = :token LIMIT 1"); + + /* (2) Bind variables */ + $pst->bindParam(':token', $token, \PDO::PARAM_STR, 128); + + /* (3) Execute */ + if( !$pst->execute() ) return false; // if error -> send FALSE + + /* (4) Fetched data */ + return $pst->fetch(); + + } + + + /* (6) Check the password of a admin + * + * @id_admin The admin UID + * @password The password to test + * + * @return valid Whether the password is valid or not + * + ---------------------------------------------------------*/ + public function checkPassword(int $id_admin, String $password){ + + /* (1) Hash the password */ + $hash = \secure_hash($password, $id_admin, 'admin-pass'); + + /* (2) Prepare Statement */ + $pst = $this->pdo->prepare("SELECT * FROM `admin` WHERE `id_admin` = :id_admin AND `pass` = :pass LIMIT 1"); + + /* (3) Bind variables */ + $pst->bindParam(':id_admin', $id_admin, \PDO::PARAM_INT); + $pst->bindParam(':pass', $hash, \PDO::PARAM_STR, 128); + + /* (4) Execute */ + if( !$pst->execute() ) return false; // if error -> send FALSE + + /* (5) If no data -> means invalid password */ + if( !is_array($pst->fetch()) ) + return false; + + /* (6) If here -> means password is ok */ + return true; + + } + + + /* (6) Set the password for a admin + * + * @id_admin The admin UID + * @password The password to set + * + * @return set Whether the password has been set or not + * + ---------------------------------------------------------*/ + public function setPassword(int $id_admin, String $password){ + + /* (1) Hash the password */ + $hash = \secure_hash($password, $id_admin, 'admin-pass'); + + /* (2) Prepare Statement */ + $pst = $this->pdo->prepare("UPDATE `admin` SET `pass` = :pass WHERE `id_admin` = :id_admin"); + + /* (3) Bind variables */ + $pst->bindParam(':pass', $hash, \PDO::PARAM_STR, 128); + $pst->bindParam(':id_admin', $id_admin, \PDO::PARAM_INT); + + /* (4) Execute -> dispatch status */ + return $pst->execute(); + + } + + /* (7) Creates a new admin + * + * @username The username (must be unique) + * @mail The mail address (must be unique) + * @password The password + * + * @return id_created UID of the created admin + * FALSE on error + * + ---------------------------------------------------------*/ + public function create(String $username, String $mail, String $password){ + + + /* (1) Check @username + @mail are unique + ---------------------------------------------------------*/ + /* (1) If @username already exists -> abort */ + if( is_array($this->getByUsername($username)) ) + return false; + + /* (2) If @mail already exists -> abort */ + if( is_array($this->getByMail($mail)) ) + return false; + + + + /* (2) Create the admin (without password) + ---------------------------------------------------------*/ + /* (1) Create a random token */ + $token = \secure_hash(uniqid(), 'admin-token'); + + /* (2) Prepare Statement */ + $pst = $this->pdo->prepare("INSERT INTO `admin`(`id_admin`, `username`, `mail`, `pass`, `token`) VALUES(DEFAULT, :username, :mail, NULL, :token)"); + + /* (3) Bind variables */ + $pst->bindParam(':username', $username, \PDO::PARAM_STR, 20); + $pst->bindParam(':mail', $mail, \PDO::PARAM_STR, 50); + $pst->bindParam(':token', $token, \PDO::PARAM_STR, 128); + + /* (4) Execute -> if error return FALSE */ + if( !$pst->execute() ) return false; + + + /* (2) Set the password (needed @id_admin) + ---------------------------------------------------------*/ + /* (1) Get last inserted id */ + $fetch_admin = $this->getByUsername($username); + + /* (2) If nothing found -> error */ + if( !is_array($fetch_admin) || !isset($fetch_admin['id_admin']) || !is_numeric($fetch_admin['id_admin']) ) + return false; + + /* (3) Extract @id_admin */ + $id_admin = intval($fetch_admin['id_admin']); + + /* (4) Repo self call */ + if( !$this->setPassword($id_admin, $password) ) + return false; + + /* (5) Return @id_admin */ + return $id_admin; + + } + + + + + } \ No newline at end of file diff --git a/build/error/.htaccess b/build/error/.htaccess deleted file mode 100755 index 896fbc5..0000000 --- a/build/error/.htaccess +++ /dev/null @@ -1,2 +0,0 @@ -Order deny,allow -Deny from all \ No newline at end of file diff --git a/build/error/core/Err.php b/build/error/core/Err.php new file mode 100755 index 0000000..720190b --- /dev/null +++ b/build/error/core/Err.php @@ -0,0 +1,121 @@ + diff --git a/build/error/core/Error.php b/build/error/core/Error.php index 060f103..8f597ff 100755 --- a/build/error/core/Error.php +++ b/build/error/core/Error.php @@ -3,130 +3,207 @@ namespace error\core; + use \error\core\Err; class Error{ - /* SUCCESS */ - const Success = 0; + private $error = null; + private $arguments = []; - /* Parsage json */ - const ParsingFailed = 1; + /* ERROR CONSTRUCTOR + * + * @error Const error + * @arg1 [OPT] Argument 1 + * @arg2 [OPT] Argument 2 + * @arg... [OPT] Argument ... + * + * @return instance Error instance + * + */ + public function __construct($const){ + call_user_func_array([$this, 'set'], func_get_args()); + } - /* ResourceDispatcher */ + /* ERROR GETTER + * + * @return Err Error + * + */ + public function get(){ return $this->error; } - // Drapeaux invalides - const InvalidFlags = 2; + /* ERROR SETTER + * + * @error Const error + * @arg1 [OPT] Argument 1 + * @arg2 [OPT] Argument 2 + * @arg... [OPT] Argument ... + * + * @return instance Error instance + * + */ + public function set($const){ + /* [1] On découpe les arguments + =========================================================*/ + /* (1) On récupère l'erreur */ + $this->error = !is_numeric($const) ? Err::UnknownError : $const; - // Fichier inexistant - const UnreachableResource = 3; - - - /* ModuleRequest */ - - // Le @path n'est pas renseigne - const MissingPath = 4; - - // Verification de la coherence du chemin (existe dans la conf) - const WrongPathModule = 5; - - // Module non specifie dans la conf - const UnknownModule = 6; - - // Methode non specifie pour ce Module dans la conf - const UnknownMethod = 7; - - // Methode inamorcable - const UncallableMethod = 8; - - // Erreur de parametre(s) - const ParamError = 9; - - // Erreur dans le traitement - const ModuleError = 10; - - /* Repo */ - - // Verification de la coherence du chemin (existe dans la conf) - const WrongPathRepo = 11; - - // Module non specifie dans la conf - const UnknownRepo = 12; - - // Erreur dans le traitement - const RepoError = 13; - - /* Database */ - - // Erreur lors de la creation d'un objet PDO (connection) - const PDOConnection = 14; - - /* API token */ - // Token inexistant ou faux - const TokenError = 15; - - const PermissionError = 16; - - /* Erreur d'UPLOAD */ - const UploadError = 17; - - // Mauvais format de fichier - const FormatError = 18; - - /* Cyclic Hash Error */ - // Expired - const CyclicHashExpired = 19; - - /* Erreur au niveau javascript */ - //const JavascriptError = 19; // -> géré en js + /* (2) On récupère les arguments */ + $this->arguments = array_slice(func_get_args(), 1); + } /* EXPLICITE UN CODE D'ERREUR * - * @error Code d'erreur - * * @return explicit Description explicite du code d'erreur * */ - public static function explicit($error){ - switch($error){ - case self::Success: return "All right man!"; break; + public function explicit(){ + switch($this->error){ + case Err::Success: return $this->Success(); break; + case Err::ParsingFailed: return $this->ParsingFailed(); break; + case Err::UnreachableResource: return $this->UnreachableResource(); break; + case Err::UploadError: return $this->UploadError(); break; + case Err::FormatError: return $this->FormatError(); break; + case Err::TokenError: return $this->TokenError(); break; + case Err::PermissionError: return $this->PermissionError(); break; + case Err::DisabledModule: return $this->DisabledModule(); break; + case Err::MissingPath: return $this->MissingPath(); break; + case Err::WrongPathModule: return $this->WrongPathModule(); break; + case Err::UnknownModule: return $this->UnknownModule(); break; + case Err::UnknownMethod: return $this->UnknownMethod(); break; + case Err::UncallableModule: return $this->UncallableModule(); break; + case Err::UncallableMethod: return $this->UncallableMethod(); break; + case Err::UnknownHttpMethod: return $this->UnknownHttpMethod(); break; + case Err::ConfigError: return $this->ConfigError(); break; + case Err::MissingParam: return $this->MissingParam(); break; + case Err::WrongParam: return $this->WrongParam(); break; + case Err::ModuleError: return $this->ModuleError(); break; + case Err::InvalidURI: return $this->InvalidURI(); break; + case Err::PDOConnection: return $this->PDOConnection(); break; + case Err::WrongPathRepo: return $this->WrongPathRepo(); break; + case Err::UnknownRepo: return $this->UnknownRepo(); break; + case Err::RepoError: return $this->RepoError(); break; + case Err::UnknownTable: return $this->UnknownTable(); break; + case Err::NotAllowedSchema: return $this->NotAllowedSchema(); break; + case Err::NoMatchFound: return $this->NoMatchFound(); break; + case Err::UnknownTemplate: return $this->UnknownTemplate(); break; + case Err::UnknownAddress: return $this->UnknownAddress(); break; + case Err::UnknownError: return $this->UnknownError(); break; + case Err::AlreadyExists: return $this->AlreadyExists(); break; + case Err::MissingBody: return $this->MissingBody(); break; + case Err::MissingHeaders: return $this->MissingHeaders(); break; - case self::ParsingFailed: return "Parsing failed (json, xml, etc). Check your format."; break; - - case self::InvalidFlags: return "You sent invalid flags."; break; - case self::UnreachableResource: return "Resource unreachable."; break; - case self::MissingPath: return "The @path is missing"; break; - case self::WrongPathModule: return "Invalid @path ('moduleName/methodName')."; break; - case self::WrongPathRepo: return "Invalid @path ('repoName/methodName')."; break; - case self::UnknownModule: return "Module not found."; break; - case self::UnknownRepo: return "Repository not found."; break; - case self::UnknownMethod: return "The method doesn't exist."; break; - case self::UncallableMethod: return "The method can't be called."; break; - - case self::ParamError: return "Missing or wrong arguments."; break; - case self::ModuleError: return "Module error."; break; - case self::RepoError: return "Repository error."; break; - - case self::PDOConnection: return "Database connection failed."; break; - - case self::TokenError: return "Missing or wrong token."; break; - case self::PermissionError: return "Permission denied."; break; - case self::UploadError: return "Upload error"; break; - case self::FormatError: return "Format error."; break; - case self::CyclicHashExpired: return "Cyclic hash has to be renewed."; break; - - default: return "Unknown Error..."; break; + default: return $this->UnknownDebugError(); break; } - - // Erreur inconnue - return null; } - public static function setHttpCode($error){ - http_response_code( $error == self::Success ? 200 : 417 ); + private function Success(){ + return 'all right'; + }private function ParsingFailed(){ + if( count($this->arguments) > 0 ) + return $this->arguments[0].' parsing failed'; + else + return 'parsing failed'; + }private function UnreachableResource(){ + return 'unreachable resource'; + }private function UploadError(){ + return 'upload error'; + }private function FormatError(){ + return 'format error'; + }private function TokenError(){ + return 'bad or expired token'; + }private function PermissionError(){ + if( count($this->arguments) > 0 ) + return "missing permission: '".$this->arguments[0]."'"; + else + return 'permission error'; + }private function DisabledModule(){ + return 'disabled module'; + }private function MissingPath(){ + return 'missing path'; + }private function WrongPathModule(){ + return 'wrong module\'s path'; + }private function UnknownModule(){ + if( count($this->arguments) > 0 ) + return 'unknown module \''.$this->arguments[0].'\''; + else + return 'unknown module'; + }private function UnknownMethod(){ + if( count($this->arguments) > 0 ) + return 'unknown method \''.$this->arguments[0].'\''; + else + return 'unknown method'; + }private function UncallableModule(){ + if( count($this->arguments) > 0 ) + return 'uncallable module \''.$this->arguments[0].'\''; + else + return 'uncallable module'; + }private function UncallableMethod(){ + if( count($this->arguments) > 0 ) + return 'uncallable method \''.$this->arguments[0].'\''; + else + return 'uncallable method'; + }private function UnknownHttpMethod(){ + return 'unknown HTTP method'; + }private function ConfigError(){ + return 'configuration error'; + }private function MissingParam(){ + if( count($this->arguments) > 0 ) + return 'missing param \''.$this->arguments[0].'\''; + else + return 'missing param'; + }private function WrongParam(){ + if( count($this->arguments) > 0 ) + if( count($this->arguments) > 1 ) + return 'wrong param \''.$this->arguments[0].'\' expected to be of type \''.$this->arguments[1].'\''; + else + return 'wrong param \''.$this->arguments[0].'\''; + else + return 'wrong param'; + }private function ModuleError(){ + if( count($this->arguments) > 0 ) + return 'module error: \''.$this->arguments[0].'\''; + else + return 'module error'; + }private function InvalidURI(){ + return 'invalid URI'; + }private function PDOConnection(){ + return 'database error'; + }private function WrongPathRepo(){ + return 'wrong repository\'s path'; + }private function UnknownRepo(){ + return 'unknown repository'; + }private function RepoError(){ + return 'repository error'; + }private function UnknownTable(){ + return 'unknown table'; + }private function NotAllowedSchema(){ + return 'schema browsing not allowed'; + }private function NoMatchFound(){ + return 'no match found'; + }private function UnknownTemplate(){ + return 'unknown template'; + }private function UnknownAddress(){ + return 'unknown'; + }private function UnknownError(){ + return 'unknown error'; + }private function AlreadyExists(){ + return 'item already exists'; + }private function MissingBody(){ + return 'body is missing'; + }private function MissingHeaders(){ + return 'headers are missing'; + }private function UnknownDebugError(){ + return 'unknown debug error'; + } + + + public function setHttpCode(){ + http_response_code( $this->error == Err::Success ? 200 : 417 ); } } + ?> diff --git a/build/http/core/HttpRequest.php b/build/http/core/HttpRequest.php new file mode 100755 index 0000000..865ac53 --- /dev/null +++ b/build/http/core/HttpRequest.php @@ -0,0 +1,208 @@ + auto-filled HTTP Request + * + =========================================================*/ + public function __construct(){ + /* [1] Define URI & Status Code & method + =========================================================*/ + $this->uri = $_SERVER['REQUEST_URI']; + $this->method = $_SERVER['REQUEST_METHOD']; + + + /* [2] Define headers + =========================================================*/ + $this->headers = \getallheaders(); + + + /* [3] Define default datasets (GET, POST) + =========================================================*/ + $this->getdata = $_GET; + $this->postdata = $_POST; + + + /* [4] Define BODY & its type + =========================================================*/ + /* (1) Default: set plain/text body */ + $this->body = \file_get_contents('php://input'); + + /* (2) Fetch content type */ + $this->type = isset($this->headers['Content-Type']) ? self::getContentType($this->headers['Content-Type']) : null; + + + /* [5] Parse BODY data -> POST + =========================================================*/ + $this->parseBody(); + } + + + + /* GET CONSTANT CT_* FROM `Content-Type` HEADER + * + * @pContentType `Content-Type` header value + * + * @return type Constant value + * + */ + private static function getContentType($pContentType=null){ + /* [1] Checks argv + =========================================================*/ + if( is_null($pContentType) ) + $pContentType = $_SERVER['CONTENT_TYPE']; + + + /* [2] Checks types + =========================================================*/ + /* (1) Form Data Types + ---------------------------------------------------------*/ + /* (1) multipart/form-data */ + if( preg_match('/^multipart\/form\-data; boundary=(.+)$/i', $pContentType) ) + return self::CT_MULTIPART_FORM_DATA; + + /* (2) application/x-www-form-urlencoded */ + if( preg_match('/^application\/x\-www\-form\-urlencoded/i', $pContentType) ) + return self::CT_X_WWW_FORM_URLENCODED; + + + /* (2) Data types + ---------------------------------------------------------*/ + /* (1) Basic JSON content type */ + if( preg_match('/^application\/json/i', $pContentType) ) + return self::CT_JSON; + + /* (2) Basic YAML content type */ + if( preg_match('/^application\/yaml/i', $pContentType) ) + return self::CT_YAML; + + /* (3) Basic TEXT content type */ + if( preg_match('/text\/[a-z]+/', $pContentType) ) + return self::CT_TEXT; + + + /* (3) Default Type + ---------------------------------------------------------*/ + return self::CT_BINARY; + + } + + + + /* PARSES BODY DATA + * + */ + private function parseBody(){ + /* [1] If empty body -> do nothing + =========================================================*/ + if( strlen($this->body) === 0 ) + return true; + + + /* [2] Management for each ContentType + =========================================================*/ + switch($this->type){ + + /* (1) multipart/form-data -> parse for not-POST methods + ---------------------------------------------------------*/ + case self::CT_MULTIPART_FORM_DATA: + /* (1) Fetch the boundary */ + if( !preg_match('/boundary=(.+)$/i', $this->headers['Content-Type'], $match) ) + return false; + + $boundary = $match[1]; + + /* (2) Break body into parts */ + $splitter = "/(?:\n|\r\n|--)*$boundary(?:\n|\r\n|--)?/im"; + $parts = preg_split($splitter, $this->body); + + /* (3) Process parts */ + foreach($parts as $part) + if( preg_match('/^Content\-Disposition: form\-data; name=\"([^"]+)\"(?:\n|\r\n){2}(.+)$/mi', $part, $match) ) + $this->postdata[$match[1]] = $match[2]; + + /* (4) Erases body */ + $this->body = ''; + break; + + + /* (2) application/x-www-form-urlencoded -> parse for not-POST methods + ---------------------------------------------------------*/ + case self::CT_X_WWW_FORM_URLENCODED: + /* Auto parse builtin-php function */ + parse_str($this->body, $this->postdata); + + /* Erases body */ + $this->body = ''; + break; + + + /* (3) application/json -> parse if no error + ---------------------------------------------------------*/ + case self::CT_JSON: + /* (1) Decode body content */ + $decoded = json_decode($this->body, true); + + /* (2) If error -> do nothing */ + if( is_null($decoded) ) + return; + + /* (3) Parse body into body */ + $this->body = $decoded; + break; + + + /* (4) application/yaml -> parse if no error + ---------------------------------------------------------*/ + case self::CT_YAML: + + break; + } + } + + + public function BODY(){ return $this->body; } + public function POST(){ return $this->postdata; } + public function GET(){ return $this->getdata; } + public function HEADERS(){ return $this->headers; } + public function METHOD(){ return $this->method; } + public function URI(){ return $this->uri; } + } diff --git a/build/router/controller/api.php b/build/router/controller/api.php new file mode 100755 index 0000000..6778a66 --- /dev/null +++ b/build/router/controller/api.php @@ -0,0 +1,56 @@ + Calling URI + * + */ + public function __construct($matches){ + + /* (1) Rebuild request url */ + $uri = $matches['uri']; + + /* (2) Creates request */ + $this->request = Loader::remote($uri); + + } + + + /* CALL + * + */ + public function call(){ + + /* (1) Process response */ + $this->response = $this->request->dispatch(); + + /* (2) Manages result */ + if( $this->response instanceof Response ) + echo $this->response->serialize(); + + return true; + + } + + /* POST-CALL + * + */ + public function __destruct(){ + + } + + + + } diff --git a/build/router/controller/page.php b/build/router/controller/page.php new file mode 100644 index 0000000..f40fe03 --- /dev/null +++ b/build/router/controller/page.php @@ -0,0 +1,34 @@ + Calling URI + * + */ + public function __construct($url){ + } + + + /* CALL + * + */ + public function load(){ + require_once(__ROOT__.'/view/home.php'); + } + + /* POST-CALL + * + */ + public function __destruct(){ + + } + + + + } diff --git a/build/router/controller/redirect.php b/build/router/controller/redirect.php new file mode 100755 index 0000000..68a0e2e --- /dev/null +++ b/build/router/controller/redirect.php @@ -0,0 +1,34 @@ + Calling URI + * + */ + public function __construct($url){ + } + + + /* CALL + * + */ + public function root(){ + header('Location: /'); + } + + /* POST-CALL + * + */ + public function __destruct(){ + + } + + + + } diff --git a/build/router/core/ControllerFactory.php b/build/router/core/ControllerFactory.php new file mode 100755 index 0000000..679e927 --- /dev/null +++ b/build/router/core/ControllerFactory.php @@ -0,0 +1,61 @@ + Nom du controller + * + * @return exists Si oui ou non le controller existe + * + */ + public static function checkController($controller){ + /* (1) Check type + pattern */ + if( !is_string($controller) || !preg_match('/^[A-Za-z_]\w+$/', $controller) ) + return false; + + /* (2) On vérifie que la classe existe */ + if( !file_exists(__BUILD__."/router/controller/$controller.php") ) + return false; + + /* (3) Sinon il existe */ + return true; + } + + + + /* INSTANCIE UN CONTROLLER + * + * @controller Nom du controller + * @arguments [OPTIONNEL] Arguments à passer au constructeur + * + * @return instance Instance du controller en question + * + */ + public static function getController($controller, $arguments=[]){ + /* (1) On vérifie l'existance du controller */ + if( !self::checkController($controller) ) + return false; + + /* (2) On récupère la classe */ + $class_name = "\\router\\controller\\$controller"; + + /* (3) On retourne une instance */ + return new $class_name($arguments); + } + + } diff --git a/build/router/core/Route.php b/build/router/core/Route.php index 6753091..83bfd95 100755 --- a/build/router/core/Route.php +++ b/build/router/core/Route.php @@ -1,63 +1,100 @@ Pattern correspondant a la route - * @callback Fonction de callback de la route - * - * @return this Retour de l'instance courante - * - */ - public function __construct($pattern, $callback){ - // On enregistre la fonction de callback - $this->callback = $callback; + class Route{ - // On formatte le pattern en regexp - $this->pattern = '#^'.$pattern.'$#'; + /* [1] Attributs + =========================================================*/ + private $pattern; + private $controller; + private $method; + private $matches; - return $this; - } - /* Verifie si l'URL correspond a la route - * - * @url URL pour laquelle on veut verifier - * - * @return match TRUE si match sinon FAUX - * - */ - public function match($url){ - // Si ne match pas -> FALSE - if( !preg_match($this->pattern, $url, $matches) ) - return false; + /* [2] Instanciation de la route + * + * @pattern Pattern correspondant a la route + * @controller Controller de la route + * @method Methode du controller + * + * @return instance Retour de l'instance courante + * + =========================================================*/ + public function __construct($pattern=null, $controller=null, $method=null){ + // Note: all arguments must be verified by 'Router->add' method - // On supprime le premier match global - array_shift($matches); + /* (1) Pattern -> regex format */ + $this->pattern = "/^$pattern$/"; - $this->matches = $matches; + /* (2) Controller */ + $this->controller = $controller; - return true; + /* (3) Controller's method */ + $this->method = $method; + + /* (4) Initialize matches */ + $this->matches = []; + + } + + + + /* [3] Checks if route matches URL + * + * @url URL + * + * @return matches If matches URL + * + =========================================================*/ + public function match($url){ + + /* (1) If doesn't matches @url -> false */ + if( !preg_match($this->pattern, $url, $matches) ) + return false; + + /* (2) Return only named matches */ + foreach($matches as $name=>$match) + if( !is_numeric($name) ) + $this->matches[$name] = $match; + + /* (4) Add complete URL */ + $this->matches['__URL__'] = $url; + + /* (5) Return status */ + return true; + } + + + /* [4] Method call + * + * @return response Response + * + =========================================================*/ + public function call(){ + /* (1) Instanciate controller */ + $instance = ControllerFactory::getController($this->controller, $this->matches); + + /* (2) Launch method & catch response */ + $response = call_user_func([$instance, $this->method]); + + /* (3) Call controller's destructor */ + $instance = null; + + /* (4) Return response */ + return $response; + } } - - /* Amorcage de la fonction de callback - * - */ - public function call(){ - return call_user_func($this->callback, $this->matches); - } - -} - -?> diff --git a/build/router/core/Router.php b/build/router/core/Router.php index ee62743..8873dfa 100755 --- a/build/router/core/Router.php +++ b/build/router/core/Router.php @@ -1,91 +1,335 @@ l'URL de la page courante - * - * @return this Retour de l'instance courante - * - */ - public function __construct($url){ - $this->url = $url; + namespace router\core; - // On initialise les routes - $this->routes = [ - 'GET' => [], - 'POST' => [] - ]; - - return $this; - } - - /* Ajoute une route GET - * - * @pattern le format de l'URL associe - * @callback function a appeler si l'URL correspond - * - * @return this Retour de l'instance courante - * - */ - public function get($pattern, $callback){ - array_push( - $this->routes['GET'], - new Route($pattern, $callback) - ); - - return $this; - } + class Router{ - /* Ajoute une route POST - * - * @pattern le format de l'URL associe - * @callback function a appeler si l'URL correspond - * - * @return this Retour de l'instance courante - * - */ - public function post($pattern, $callback){ - array_push( - $this->routes['POST'], - new Route($pattern, $callback) - ); - return $this; - } + /* [1] Attributes + =========================================================*/ + private $url; // current URL + private $cnf; // parsed configuration + private $http_methods; // allowed HTTP methods + private $routes; // built routes - /* Demarre le routeur - * - * @return this Retour de l'instance courante - * - */ - public function run(){ - $httpMethod = $_SERVER['REQUEST_METHOD']; - // Si aucune route pour la methode courante -> false - if( count($this->routes[$httpMethod]) <= 0 ) - return false; + /* [2] Configuration file + =========================================================*/ + private static function config_path(){ return __CONFIG__.'/routes.json'; } + + // Get random token + public static function randBoundary(){ return dechex( random_int((int) 1e10, (int) 1e20) ); } + + + // Instance getter + public static function launch($url=null){ + + /* (1) Instanciate the router (propagation) */ + $instance = new Router($url); + + /* (2) Launches the router */ + $instance->run(); - // Pour chaque route - foreach($this->routes[$httpMethod] as $route){ - // Si la route match - if( $route->match($this->url) ) - return $route->call(); // On l'amorce } - // Retourne false si erreur - return false; - } -} -?> + /* [3] Constructor + * + * @url Current URL + * + * @return instance Instance du routeur + * + =========================================================*/ + public function __construct($url=null){ + /* (1) Checks arguments + ---------------------------------------------------------*/ + /* (1) Default value if incorrect */ + $this->url = is_string($url) ? $url : ''; + + /* (2) Add first '/' if missing */ + if( !preg_match('/^\//', $url) ) + $this->url = '/'.$this->url; + + + /* (2) Loads configuration + ---------------------------------------------------------*/ + /* (1) Tries to load configuration */ + $this->cnf = self::loadConfig(); + + /* (2) If error occurs, throw Exception */ + if( is_null($this->cnf) ) + throw new \Exception("[Router] Configuration file error found"); + + + /* (3) Set allowed HTTP methods + ---------------------------------------------------------*/ + /* (1) If not defined */ + if( !isset($this->cnf['methods']) ) + throw new \Exception('[Router] Configuration file error, \'methods\' clause missing'); + + /* (2) Try to clean methods */ + $this->http_methods = self::cleanMethods($this->cnf['methods']); + + /* (3) Manage error */ + if( is_null($this->http_methods) ) + throw new \Exception('[Router] Configuration file error. \'methods\' must be an array of HTTP methods ["GET", "POST", ...]'); + + + /* (4) Initialize routes + ---------------------------------------------------------*/ + /* (1) Init routes */ + $this->routes = []; + + foreach($this->http_methods as $method) + $this->routes[$method] = []; + + /* (2) Default configuration if missing */ + if( !isset($this->cnf['routes']) || !is_array($this->cnf['routes']) ) + $this->cnf['routes'] = []; + + + /* (5) Loads each route + ---------------------------------------------------------*/ + foreach($this->cnf['routes'] as $pattern=>$route){ + + /* (1) If missing (required) parameters */ + if( !isset($route['controller']) ) + continue; + + /* (2) Default value for 'methods' */ + ( !isset($route['methods']) || !is_array($route['methods']) ) && ($route['methods'] = $this->http_methods); + + /* (3) Default value for 'arguments' */ + ( !isset($route['arguments']) || !is_array($route['arguments']) ) && ($route['arguments'] = []); + + /* (4) Add route */ + $added = $this->add($pattern, $route['controller'], $route['arguments']); + + // if error -> next + if( $added === false ) + continue; + + + /* (5) Add route for each method */ + foreach($route['methods'] as $method) + if( in_array($method, $this->http_methods) ) + $this->routes[$method][] = $added; + + } + + + } + + + + + /* [4] Adds a route + * + * @pattern URL pattern with {somevar} variables within + * @controller Controller name + method "controllername:methodname" + * @arguments List of pattern's arguments and their RegExp composition (default is alphanumeric) + * + * @return route New instance of Route || false on error + * + =========================================================*/ + public function add($pattern=null, $controller=null, $arguments=[]){ + + /* (1) Format and check pattern + ---------------------------------------------------------*/ + /* (1) If not a string */ + if( !is_string($pattern) ) + return false; + + /* (2) Format pattern and check result */ + // /*DEBUG*/ var_dump($pattern); + $pattern = self::formatPattern($pattern, $arguments); + // /*DEBUG*/ var_dump($pattern); + + if( $pattern === false ) + return false; + + + /* (2) Check controller + ---------------------------------------------------------*/ + /* (1) Check default type */ + if( !is_string($controller) || !preg_match('/^([A-Za-z_]\w+):([A-Za-z_]\w+)$/', $controller, $c_matches) ) + return false; + + /* (2) Check existence */ + if( !ControllerFactory::checkController($c_matches[1]) ) + return false; + + + /* (3) Check method + ---------------------------------------------------------*/ + if( !method_exists('\\router\\controller\\'.$c_matches[1], $c_matches[2]) ) + return false; + + + /* (4) Return new route + ---------------------------------------------------------*/ + return new Route($pattern, $c_matches[1], $c_matches[2]); + } + + + + + /* [5] Router launch + * + =========================================================*/ + public function run(){ + /* (1) Manage HTTP method + ---------------------------------------------------------*/ + /* (1) Fetch HTTP method */ + $httpMethod = $_SERVER['REQUEST_METHOD']; + + /* (2) If no route for this -> exit */ + if( !isset($this->routes[$httpMethod]) || count($this->routes[$httpMethod]) <= 0 ) + return false; + + + /* (2) Manage routes (matching) + ---------------------------------------------------------*/ + /* (1) Check for each HTTP method's route */ + foreach($this->routes[$httpMethod] as $route) + + /* (2) First route that matches -> call & return response */ + if( $route->match($this->url) ) + return $route->call(); + + + /* (3) If no route found -> return false + ---------------------------------------------------------*/ + return false; + } + + + /* FORMATS A PATTERN + * + * @pattern Pattern to process on + * @arguments List of used arguments, with regex if given + * @vars [OPT] If variable replacement have to be done + * + * @return formatted Formatted pattern || false on error + * + */ + public static function formatPattern($pattern, $arguments=[]){ + + /* (1) Check arguments + ---------------------------------------------------------*/ + /* (1) Check minimal length */ + if( strlen($pattern) < 1 ) + return false; + + /* (2) Arguments formatting */ + $arguments = !is_array($arguments) ? [] : $arguments; + + + /* (2) Replace special characters + replace vars + ---------------------------------------------------------*/ + /* (1) Check default URL format */ + if( !preg_match('/^(\/[\w\.]*(?:\{[a-z_]+\})*[\w\.]*)*\/?$/i', $pattern) ) + return false; + + /* (2) Escape special characters */ + $pattern = str_replace('/', '\\/', $pattern); + + /* (3) Add optional ending '/' */ + if( !preg_match('/\/$/', $pattern) ) + $pattern .= '\\/?'; + + /* (4) Replace variable by tagged capturing groups */ + $boundary = self::randBoundary(); + $pattern = preg_replace('/\{([a-z_][a-z0-9_]*)\}/i', '(?P<$1>'.$boundary.'-$1-'.$boundary.')', $pattern); + + + /* (3) Variable replacement + ---------------------------------------------------------*/ + /* (1) List variables */ + $vars = []; + $var_pattern = '/'.$boundary.'\-([A-Za-z_][A-Za-z0-9_]*)\-'.$boundary.'/'; + preg_match_all($var_pattern, $pattern, $matches); + + /* (2) For each matching variable -> replace with associated regex */ + if( is_array($matches) && isset($matches[1]) ){ + + foreach($matches[1] as $m=>$varname){ + + // {3.1.1} Not in @arguments -> default regex // + if( !isset($arguments[$varname]) || !is_string($arguments[$varname]) ){ + $pattern = str_replace($matches[0][$m], '[A-Za-z0-9_]+', $pattern); + continue; + } + + // {3.1.2} If variable in @arguments -> set regex without capturing-groups // + // $without_capg = str_replace('(', '(?:', $arguments[$varname]); + $pattern = str_replace($matches[0][$m], $arguments[$varname], $pattern); + + } + + } + + + /* (4) Return formatted pattern + ---------------------------------------------------------*/ + return $pattern; + } + + + + /* LOADS CONFIGURATION + * + * @return cnf Configuration content || NULL if error + * + */ + private static function loadConfig(){ + /* (1) Set configuration file's path */ + $cnfpath = self::config_path(); + + /* (2) Checks file */ + if( !file_exists($cnfpath) ) + return null; // throw new \Exception("[Router] Configuration file not found"); + + /* (3) Checks format -> null if error */ + return json_decode( file_get_contents($cnfpath), true ); + + } + + + /* CHECKS METHODS AND CLEAN THE LIST IF CORRECT + * + * @wanted List of wanted methods + * + * @return cleaned Cleaned methods || null if error + * + */ + private static function cleanMethods($wanted=[]){ + /* (1) Checks @wanted */ + if( !is_array($wanted) || count($wanted) < 1 ) + return null; // throw new \Exception('[Router] Configuration file error, \'methods\' must be an array containing managed HTTP methods'); + + /* (2) Set methods (uppercase and unique) */ + $cleaned = []; + + foreach($wanted as $method) + if( !in_array(strtoupper($method), $cleaned) ) + $cleaned[] = strtoupper($method); + + /* (3) Return cleaned method list */ + return $cleaned; + } + + } diff --git a/config/database-driver.json b/config/database-driver.json new file mode 100755 index 0000000..da74338 --- /dev/null +++ b/config/database-driver.json @@ -0,0 +1,16 @@ +{ + "default": { + "local": { + "host" : "localhost", + "dbname" : "prod-releaser", + "user" : "prod-releaser-php", + "password" : "a64e9fede92c55932ce82d77891f77a1f015a9f1" + }, + "remote": { + "host" : "db_remote_host", + "dbname" : "db_remote_name", + "user" : "db_remote_user", + "password" : "db_remote_password" + } + } +} diff --git a/config/modules.json b/config/modules.json index e438ead..3145330 100755 --- a/config/modules.json +++ b/config/modules.json @@ -1,7 +1,43 @@ { - "authentification": { - "renew": { + "admin": { + + "POST": { + "description": "Creates a new administrator", + "permissions": [["admin"]], + "parameters": { + "username": { "description": "The new administrator username", "type": "varchar(3,20,alphanumeric)" }, + "mail": { "description": "The new administrator email address", "type": "mail" }, + "password": { "description": "The new administrator passowrd", "type": "text" } + } + }, + + "PUT": { + "description": "Gets an administrator's data", + "permissions": [], + "parameters": { + "URL0": { "description": "Some string token", "type": "text", "rename": "id_article" }, + "postdata": { "description": "Some string token", "type": "text" } + } + }, + + "DELETE": { + "description": "Deletes an administrator", + "permissions": [["admin"]], + "parameters": {} + }, + + "GET": { + "description": "Deletes an administrator", + "permissions": [["admin"]], + "parameters": {} + } + + }, + + + "token/renew": { + "POST": { "description": "Renewal of the cyclic hashing system.", "permission": ["cyclic-hash"], "parameters": { @@ -11,8 +47,9 @@ }, - "release": { - "pull": { + "release/pull": { + + "POST": { "description": "Pulls project from git branch.", "permissions": ["cyclic-hash"], "parameters": { diff --git a/config/projects.json b/config/projects.json index cead3c2..ab48f12 100755 --- a/config/projects.json +++ b/config/projects.json @@ -1,12 +1,11 @@ { - "nxtic": { - "apache": "/vhost/nxtic", - "git": "/vhost/git-repositories/nxtic" - }, - "prod-releaser": { - "apache": "/vhost/prod-releaser", - "git": "/vhost/git-repositories/prod-release.php" + "ndli1718": { + "dir": "/vhost/ndli1718", + "cmd" : [ + "git pull origin master", + "npm run release" + ] } } diff --git a/config/routes.json b/config/routes.json new file mode 100755 index 0000000..746489f --- /dev/null +++ b/config/routes.json @@ -0,0 +1,32 @@ +{ + + "methods": [ "GET", "POST", "PUT", "DELETE", "VIEW" ], + + + "routes": { + + "/api/v/1.0/{uri}": { + "methods": ["GET", "POST", "PUT", "DELETE", "VIEW"], + "controller": "api:call", + "arguments": { + "uri": ".*" + } + }, + + "/": { + "methods": ["GET"], + "controller": "page:load", + "arguments": {} + }, + + "/{any}": { + "methods": ["GET"], + "controller": "redirect:root", + "arguments": { + "any": ".*" + } + } + + } + +} diff --git a/config/server.json b/config/server.json deleted file mode 100755 index d5c847c..0000000 --- a/config/server.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - - "local" : { - "host" : "http://prod-releaser/", - "root" : "/" - }, - - "remote" : { - "host" : "https://prod-releaser.xdrm.io/", - "root" : "/" - } - -} diff --git a/install.sh b/install.sh deleted file mode 100755 index 6e77d74..0000000 --- a/install.sh +++ /dev/null @@ -1,3 +0,0 @@ -groupadd w-x-b; -usermod -a -G w-x-b www-data; -chgrp -R w-x-b .; diff --git a/public_html/index.php b/public_html/index.php index bf05fd6..8238b65 100755 --- a/public_html/index.php +++ b/public_html/index.php @@ -1,54 +1,20 @@ - api - =========================================================*/ - $R->post('api(?:/(.*))?', function($url){ - $request = ModuleRequest::fromPost($url, $_POST); - $answer = $request->dispatch(); - - // Si c'est une réponse - if( $answer instanceof ModuleResponse ) - echo $answer->serialize(); - - }); - - - /* [3] Any other URL -> homepage - =========================================================*/ - $R->get('.+', function(){ http_response_code(417); exit(); }); - $R->post('.+', function(){ http_response_code(417); exit(); }); - - - /* [8] Launch Router - ===================================================*/ - $R->run(); - -?> diff --git a/view/home.php b/view/home.php new file mode 100644 index 0000000..c1d0ca2 --- /dev/null +++ b/view/home.php @@ -0,0 +1,12 @@ + + + + + + + 404 + + + + + \ No newline at end of file