123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- <?php
- /**
- * Classe de gestion des API (REST).
- * @author Valentin CARRUESCO
- * @category Plugin
- * @license MIT
- */
- class Api{
- public $slug,$description;
- function __construct($slug=null,$description=null){
- if(isset($slug)) $this->slug = $slug;
- if(isset($description)) $this->description = $description;
- $this->route = array();
- return $this;
- }
- function route($slug = null,$description = null,$method = null,$callback){
- $route = new ApiRoute($slug,$description,$method,$callback);
- $this->route[$route->slug][$method] = $route;
- }
- function register(){
- Plugin::addHook('api',function(&$apis){
- $apis[$this->slug] = $this;
- });
- }
- //retourne la liste des headers de la requete courante (clé normalisées)
- public static function headers(){
- $headers = array();
- foreach(getallheaders() as $key=>$value)
- $headers[mb_strtolower($key)] = $value;
- return $headers;
- }
- //définis si la requete courante est via api ou en http classique (ou autre)
- public static function requested(){
- return preg_match('/\/api\/v[0-9]*\//i', $_SERVER['REQUEST_URI']);
- }
- //SI un JWToken est fournis en header Authorization: Bearer <token>, on récupere la session associée
- //si la session a expirée on la relance
- public static function token_session(){
- if(php_sapi_name() == 'cli') return;
- if(!self::requested()) return;
- $headers = self::headers();
- if(!isset($headers['authorization'])) return;
- try{
- $authorization = explode(' ',$headers['authorization']);
- if(count($authorization)!=2 || strtolower($authorization[0])!='bearer') throw new Exception("Bad Authorization header (required Authorization: Bearer <JWToken>)",403);
- global $conf,$myUser;
- $token = JWToken::parse(trim($authorization[1]),$conf->get('jwtauth_secret'));
- //si une session est en cours
- if(session_status() == PHP_SESSION_ACTIVE){
- //si la session courant a le mauvais id (ex une session anonyme ou expirée) on la détruit et on la recréé avec le bon id
- if( session_id() !=$token['attributes']['session_id']){
- session_destroy();
- session_id($token['attributes']['session_id']);
- session_start();
- }
- }else{
- //si aucun session en cours, on la créé avec le bon id
- session_id($token['attributes']['session_id']);
- session_start();
- }
- if(!isset($_SESSION['currentUser'])) throw new Exception('Token session has expired',498);
- }catch(Exception $e){
- $response['error'] = $e->getMessage();
- $response['errorCode'] = $e->getCode();
- echo json_encode($response);
- exit;
- }
- }
- public static function run(){
- global $_,$myUser;
- $response = array();
- try{
- set_error_handler(function( $errno , $errstr , $errfile , $errline){
- throw new Exception("Error ".$errno." Processing Request : ".$errfile." => ".$errstr.' L'.$errline, 500);
- });
- // register_shutdown_function(function(){
- // $error = error_get_last();
- // throw new Exception("Error 0 Processing Request ".$error['file'], 500);
- // });
- $command = explode('/',$_['command']);
- if(count($command)<1) throw new Exception("Unspecified API");
- $headers = self::headers();
- //basic auth (deprecated, utiliser plutot JWToken et Authorization Bearer)
- if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])){
- $user = User::check($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']);
- if(!$user) throw new Exception('Le compte spécifié est inexistant');
- $myUser = $user;
- }
- $request = array(
- 'version' =>array_shift($command),
- 'module' =>array_shift($command),
- 'route' =>array_shift($command),
- 'body' =>file_get_contents("php://input"),
- 'headers' =>$headers,
- 'method' => strtoupper($_SERVER['REQUEST_METHOD']),
- 'parameters' => $_,
- 'pathes' => array_filter($command)
- );
- $code = 200;
- $headers = array(
- 'Content-Type: application/json'
- );
- $apis = array();
- Plugin::callHook('api',array(&$apis));
- //format de l'url : /api/v{version}/
- if(!isset($request['version']) || !is_numeric(substr($request['version'],1)) || substr($request['version'], 0,1)!='v') throw new Exception("Api version is missing or invalid, specify ".ROOT_URL."/api/{version} for valid requests", 404);
- if($request['module'] == 'schema'){
- if(!$myUser->connected()) throw new Exception("Credentials are missing",401);
- foreach ($apis as $api) {
- $routes= array();
- foreach($api->route as $route){
- foreach($route as $method=>$infos){
- if(!isset($routes[$infos->slug])) $routes[$infos->slug] = array() ;
- $routes[$infos->slug][] = $infos->method.' '.$api->slug .'/'.$infos->pattern .': '. $infos->description ;
- }
- }
- $response[] = array( $api->slug => $api->description,'routes'=> $routes);
- }
- }else{
- if(!isset($apis[$request['module']])) throw new Exception("Api '".$request['module']."' is missing, see ".ROOT_URL."/api/{version}/schema for available calls", 404);
- $api = $apis[$request['module']];
- if(!isset($api->route[$request['route']])) throw new Exception("Route '".$api->slug.'/'.$request['route']."' is missing, see ".ROOT_URL."/api/schema?pretty for available calls", 404);
- $route = $api->route[$request['route']];
- if(!isset($route[$request['method']])) throw new Exception("Method ".$request['method']." '".$api->slug.'/'.$route->slug."' is not allowed", 405);
- $method = $route[$request['method']];
- $callback = $method->callback;
- $callback($request,$response);
- if(isset($response['code'])) $code = $response['code'];
- if(isset($response['headers'])) $headers = array_merge($headers,$response['headers']);
- unset($response['code']);
- unset($response['headers']);
- }
- } catch(Exception $e) {
- $response['error'] = $e->getMessage();
- $code = $e->getCode();
- if(empty($code)) $code = 0;
- $response['errorCode'] = $code;
- }
- $codes = array(
- 200 => 'HTTP/1.1 200 Ok',
- 201 => 'HTTP/1.1 201 Created',
- 204 => 'HTTP/1.1 204 No Content',
- 207 => 'HTTP/1.1 207 Multi-Status',
- 400 => 'HTTP/1.0 400 Bad Request',
- 401 => 'HTTP/1.0 401 Unauthorized',
- 403 => 'HTTP/1.1 403 Forbidden',
- 404 => 'HTTP/1.1 404 Not Found',
- 404 => 'HTTP/1.1 405 Method Not Allowed',
- 406 => 'HTTP/1.1 406 Not acceptable',
- 409 => 'HTTP/1.1 409 Conflict',
- 415 => 'HTTP/1.1 415 Unsupported Media Type',
- 423 => 'HTTP/1.1 423 Locked',
- 500 => 'HTTP/1.1 500 Internal Server Error',
- 501 => 'HTTP/1.1 501 Not implemented',
- 0 => 'HTTP/1.1 0 Unknown Error',
- );
- if (ob_get_length()) ob_end_clean();
- header(isset($codes[$code]) ? $codes[$code] : $codes[0]);
- foreach ($headers as $header)
- header($header);
- echo (isset($_['pretty'])) ? json_encode($response,JSON_PRETTY_PRINT) : json_encode($response);
- exit();
- }
- }
- class ApiRoute{
- public $slug,$description,$callback,$method,$pathes,$parameters,$pattern;
- function __construct($slug='default',$description='No description',$method='GET',$callback){
- $this->pattern = $slug;
- $infos = explode('?',$slug);
- if(isset($infos[1])) $this->parameters = $infos[1];
- $infos = explode('/',$infos[0]);
- $this->slug = array_shift($infos);
- $this->pathes = $infos;
- $this->description = $description;
- $this->method = $method;
- $this->callback = $callback;
- }
- }
|