From 72321d4646584e6f5e4f9d6e8470734206353637 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 10 Dec 2017 20:33:18 +0100 Subject: [PATCH] BIG UPDATE: build/api/core (refactor + complete redesign of configuration) --- build/api/core/AuthSystem.php | 29 + build/api/core/AuthSystemDefault.php | 262 ++++++++ build/api/core/Authentification.php | 177 ------ build/api/core/Checker.php | 27 +- build/api/core/Loader.php | 53 ++ build/api/core/ModuleFactory.php | 45 ++ build/api/core/ModuleRequest.php | 513 --------------- build/api/core/Request.php | 599 ++++++++++++++++++ .../core/{ModuleResponse.php => Response.php} | 84 +-- config/modules.json | 42 +- 10 files changed, 1073 insertions(+), 758 deletions(-) create mode 100755 build/api/core/AuthSystem.php create mode 100755 build/api/core/AuthSystemDefault.php delete mode 100644 build/api/core/Authentification.php create mode 100755 build/api/core/Loader.php create mode 100755 build/api/core/ModuleFactory.php delete mode 100755 build/api/core/ModuleRequest.php create mode 100755 build/api/core/Request.php rename build/api/core/{ModuleResponse.php => Response.php} (70%) 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..08ebd82 --- /dev/null +++ b/build/api/core/Request.php @@ -0,0 +1,599 @@ + 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 $params; // paramètres (POST+GET) + 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 */ + $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($params) ) // 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 = $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) 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); + + + + /* (4) 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 + * + * @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 + ---------------------------------------------------------*/ + /* (1) Si @params n'est pas un tableau */ + if( !is_array($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=>$paramsdata){ + + /* (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 @paramsdata n'est pas un tableau */ + if( !is_array($paramsdata) ) + return $this->error->set(Err::ConfigError); + + /* (3) So @paramsdata['type] manquant ou incorrect */ + if( !isset($paramsdata['type']) || !is_string($paramsdata['type']) ) + return $this->error->set(Err::ConfigError); + + + /* (2.2) Gestion des spécifications + ---------------------------------------------------------*/ + /* (1) On récupère si le paramètre est optionnel ou pas */ + $optional = isset($paramsdata['optional']) && $paramsdata['optional'] === true; + + /* (2) Si de type 'FILE' + fichier existe => on enregistre la ref. */ + if( $paramsdata['type'] == 'FILE' && isset($_FILES[$name]) ) + $params[$name] = &$_FILES[$name]; + + /* (3) Si param obligatoire et manquant -> erreur */ + if( !isset($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($params[$name]) ){ + + // On le crée le param optionnel avec la valeur NULL + $params[$name] = null; + + /* (2) Si le paramètre est renseigné (sauf FILE) */ + }elseif( $paramsdata['type'] != 'FILE'){ + + // Si la verification est fausse, on retourne faux + if( !Checker::run($paramsdata['type'], $params[$name]) ) + return $this->error->set(Err::WrongParam, $name, $paramsdata['type']); + + } + + } + + + /* (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/config/modules.json b/config/modules.json index e438ead..71ed342 100755 --- a/config/modules.json +++ b/config/modules.json @@ -1,7 +1,40 @@ { - "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" } + } + }, + + "GET": { + "description": "Gets an administrator's data", + "permissions": [], + "parameters": {} + }, + + "DELETE": { + "description": "Deletes an administrator", + "permissions": [["admin"]], + "parameters": {} + }, + + "PUT": { + "description": "Deletes an administrator", + "permissions": [["admin"]], + "parameters": {} + } + + }, + + + "token/renew": { + "POST": { "description": "Renewal of the cyclic hashing system.", "permission": ["cyclic-hash"], "parameters": { @@ -11,8 +44,9 @@ }, - "release": { - "pull": { + "release/pull": { + + "POST": { "description": "Pulls project from git branch.", "permissions": ["cyclic-hash"], "parameters": {