aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/restapi
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-06-19 14:13:42 +0000
committerDavid Siegfried <david.siegfried@uni-vechta.de>2024-06-19 14:13:42 +0000
commitd4d5c311ec24ddc92c8d9053f9eeb37acf44f395 (patch)
tree76591780b670da7891f03ae31a63c77668c8e9ef /lib/classes/restapi
parent45ed79fbcc9a8c9be7f236b24b4c6a117b48184f (diff)
remove restapi, fixes #2798
Closes #2798 Merge request studip/studip!1888
Diffstat (limited to 'lib/classes/restapi')
-rw-r--r--lib/classes/restapi/ConsumerPermissions.php212
-rw-r--r--lib/classes/restapi/Response.php148
-rw-r--r--lib/classes/restapi/RouteMap.php1060
-rw-r--r--lib/classes/restapi/Router.php665
-rw-r--r--lib/classes/restapi/RouterException.php31
-rw-r--r--lib/classes/restapi/RouterHalt.php19
-rw-r--r--lib/classes/restapi/UriTemplate.php115
-rw-r--r--lib/classes/restapi/UserPermissions.php144
-rw-r--r--lib/classes/restapi/consumer/Base.php226
-rw-r--r--lib/classes/restapi/consumer/HTTP.php50
-rw-r--r--lib/classes/restapi/consumer/OAuth.php231
-rw-r--r--lib/classes/restapi/consumer/Studip.php36
-rw-r--r--lib/classes/restapi/renderer/DebugRenderer.php57
-rw-r--r--lib/classes/restapi/renderer/DefaultRenderer.php74
-rw-r--r--lib/classes/restapi/renderer/JSONRenderer.php35
15 files changed, 0 insertions, 3103 deletions
diff --git a/lib/classes/restapi/ConsumerPermissions.php b/lib/classes/restapi/ConsumerPermissions.php
deleted file mode 100644
index 8fc2252..0000000
--- a/lib/classes/restapi/ConsumerPermissions.php
+++ /dev/null
@@ -1,212 +0,0 @@
-<?php
-namespace RESTAPI;
-use DBManager, PDO;
-
-/**
- * REST API routing permissions
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class ConsumerPermissions
-{
- /**
- * Create a permission object (for a certain consumer).
- * Permissions object will be cached for each consumer.
- *
- * @param mixed $consumer_id Id of consumer (optional, defaults to global)
- * @return ConsumerPermissions Returns permissions object
- */
- public static function get($consumer_id = null)
- {
- static $cache = [];
- if (!isset($cache[$consumer_id])) {
- $cache[$consumer_id] = new self($consumer_id);
- }
-
- return $cache[$consumer_id];
- }
-
- private $consumer_id;
- private $permissions = [];
-
- /**
- * Creates the actual permission object (for a certain consumer).
- *
- * @param mixed $consumer_id Id of consumer (optional, defaults to global)
- */
- private function __construct($consumer_id = null)
- {
- $this->consumer_id = $consumer_id;
-
- // Init with global permissions
- $this->loadPermissions('global', true);
-
- // Specific consumers permissions?
- if ($consumer_id) {
- $this->loadPermissions($consumer_id, false);
- }
- }
-
- /**
- * Defines whether access if allowed for the current consumer to the
- * passed route via the passed method.
- *
- * @param String $route_id Route template (hash)
- * @param String $method HTTP method
- * @param mixed $granted Granted state (PHP'ish boolean)
- * @param bool $overwrite May values be overwritten
- * @return bool Indicates if value could be changed.
- */
- public function set($route_id, $method, $granted, $overwrite = false)
- {
- // If route_id is not an md5 hash, convert it
- if (!preg_match('/^[0-9a-f]{32}$/', $route_id)) {
- $route_id = md5($route_id);
- }
-
- if (!isset($this->permissions[$route_id])) {
- // Skip if not globally set and not allowed to overwrite
- if (!$overwrite) {
- return false;
- }
- $this->permissions[$route_id] = [];
- }
-
- // overwrite only if globally allowed
- if (!$overwrite && empty($this->permissions[$route_id][$method])) {
- return false;
- }
-
- $this->permissions[$route_id][$method] = (bool) $granted;
-
- return true;
- }
-
- /**
- * Convenience method for activating all routes in a route map.
- *
- * @param \RESTAPI\RouteMap $routemap RouteMap to activate
- */
- public function activateRouteMap(RouteMap $routemap)
- {
- foreach ($routemap->getRoutes() as $method => $routes) {
- foreach (array_keys($routes) as $route) {
- $this->set($route, $method, true, true);
- }
- }
-
- $this->store();
- }
-
- /**
- * Removes stored permissions for a given route and method.
- *
- * @param String $route_id Route template
- * @param String $method HTTP method
- * @return bool
- */
- public function remove($route_id, $method)
- {
- if (!isset($this->permissions[$route_id][$method])) {
- return false;
- }
-
- unset($this->permissions[$route_id][$method]);
-
- if (count($this->permissions[$route_id]) === 0) {
- unset($this->permissions[$route_id]);
- }
-
- return true;
- }
-
- /**
- * Convenience method for deactivating all routes in a route map.
- *
- * @param \RESTAPI\RouteMap $routemap RouteMap to activate
- */
- public function deactivateRouteMap(RouteMap $routemap)
- {
- foreach ($routemap->getRoutes() as $method => $routes) {
- foreach (array_keys($routes) as $route) {
- $this->remove($route, $method);
- }
- }
-
- $this->store();
- }
-
- /**
- * Loads permissions for passed consumer.
- *
- * @param String $consumer_id Id of the consumer in question
- * @param bool $overwrite May values be overwritten
- * @return ConsumerPermissions Returns instance of self to allow chaining
- */
- protected function loadPermissions($consumer_id, $overwrite = false)
- {
- $query = "SELECT route_id, method, granted
- FROM api_consumer_permissions
- WHERE consumer_id = IFNULL(:consumer_id, 'global')";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':consumer_id', $consumer_id);
- $statement->execute();
- $permissions = $statement->fetchAll(PDO::FETCH_ASSOC);
-
- // Init with global permissions
- foreach ($permissions as $permission) {
- extract($permission);
-
- $this->set($route_id, $method, $granted, $overwrite);
- }
-
- return $this;
- }
-
- /**
- * Checks if access to passed route via passed method is allowed for
- * the current consumer.
- *
- * @param String $route Route template
- * @param String $method HTTP method
- * @return bool Indicates whether access is allowed
- */
- public function check($route, $method)
- {
- $route_id = md5($route);
-
- return isset($this->permissions[$route_id][$method])
- && $this->permissions[$route_id][$method];
- }
-
- /**
- * Stores the set permissions.
- *
- * @return bool Returns true if permissions were stored successfully
- */
- public function store()
- {
- $result = true;
-
- $query = "INSERT INTO api_consumer_permissions (route_id, consumer_id, method, granted)
- VALUES (:route, IFNULL(:consumer_id, 'global'), :method, :granted)
- ON DUPLICATE KEY UPDATE granted = VALUES(granted)";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':consumer_id', $this->consumer_id);
-
- foreach ($this->permissions as $route_id => $methods) {
- $statement->bindParam(':route', $route_id);
- foreach ($methods as $method => $granted) {
- $statement->bindParam(':method', $method);
- $granted = (int) !empty($granted);
- $statement->bindParam(':granted', $granted);
- $result = $result && $statement->execute();
- }
- }
-
- return $result;
- }
-}
diff --git a/lib/classes/restapi/Response.php b/lib/classes/restapi/Response.php
deleted file mode 100644
index 56d9b65..0000000
--- a/lib/classes/restapi/Response.php
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-namespace RESTAPI;
-
-/**
- * Response class for the rest api
- *
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class Response implements \ArrayAccess
-{
- public $body, $status, $headers;
-
- /**
- * Constructor, sets vital information if provided.
- *
- * @param String $body Body contents of the response, optional,
- * defaults to empty string
- * @param int $status HTTP status code, optional, defaults to 200
- * @param Array $headers HTTP headers, optional, defaults to no headers
- */
- public function __construct($body = '', $status = 200, $headers = [])
- {
- $this->body = $body;
- $this->status = (int) $status;
- $this->headers = (array) $headers;
- }
-
- /**
- * Detects whether the response status is of success type (HTTP status 2xx)
- *
- * @return bool True if status is of success type, false otherwise
- */
- public function isSuccess()
- {
- return 200 <= $this->status && $this->status <= 299;
- }
-
- /**
- * Finishes the response with the given response renderer.
- *
- * @param Renderer\DefaultRenderer $content_renderer Used response renderer,
- * only applied if body is
- * not a callable closure
- */
- public function finish($content_renderer)
- {
- if (!is_callable($this->body)) {
- $content_renderer->render($this);
- }
- }
-
- /**
- * Sends the response.
- */
- public function output()
- {
- if (isset($this->status)) {
- if (mb_strpos(PHP_SAPI, 'cgi') === 0) {
- $this->sendHeader(sprintf('Status: %d %s', $this->status, $this->reason()));
- } else {
- $this->sendHeader(sprintf('HTTP/1.1 %d %s', $this->status, $this->reason()));
- }
- }
-
- foreach ($this->headers as $k => $v) {
- $this->sendHeader("$k: $v", false, $this->status);
- }
-
- if (is_callable($this->body)) {
- call_user_func($this->body);
- } else {
- echo $this->body;
- }
- }
-
- /**
- * Internally used function to actually send headers
- *
- * @param string the HTTP header
- * @param bool optional; TRUE if previously sent header should be
- * replaced - FALSE otherwise (default)
- * @param integer optional; the HTTP response code
- *
- * @return void
- */
- public function sendHeader($header, $replace = FALSE, $status = NULL) {
- if (isset($status)) {
- header($header, $replace, $status);
- }
- else {
- header($header, $replace);
- }
- }
-
- /**
- * Returns the reason phrase of this response according to RFC2616.
- *
- * @return string the reason phrase for this response's status
- */
- public function reason() {
- $reason = [
- 100 => 'Continue', 'Switching Protocols',
- 200 => 'OK', 'Created', 'Accepted', 'Non-Authoritative Information',
- 'No Content', 'Reset Content', 'Partial Content',
- 300 => 'Multiple Choices', 'Moved Permanently', 'Found', 'See Other',
- 'Not Modified', 'Use Proxy', '(Unused)', 'Temporary Redirect',
- 400 => 'Bad Request', 'Unauthorized', 'Payment Required','Forbidden',
- 'Not Found', 'Method Not Allowed', 'Not Acceptable',
- 'Proxy Authentication Required', 'Request Timeout', 'Conflict',
- 'Gone', 'Length Required', 'Precondition Failed',
- 'Request Entity Too Large', 'Request-URI Too Long',
- 'Unsupported Media Type', 'Requested Range Not Satisfiable',
- 'Expectation Failed',
- 500 => 'Internal Server Error', 'Not Implemented', 'Bad Gateway',
- 'Service Unavailable', 'Gateway Timeout',
- 'HTTP Version Not Supported'];
-
- return isset($reason[$this->status]) ? $reason[$this->status] : '';
- }
-
- // array access methods for headers
-
- public function offsetExists($offset): bool
- {
- return isset($this->headers[$offset]);
- }
-
- /**
- * @param $offset
- */
- public function offsetGet($offset): mixed
- {
- return @$this->headers[$offset];
- }
-
- public function offsetSet($offset, $value): void
- {
- $this->headers[$offset] = $value;
- }
-
- public function offsetUnset($offset): void
- {
- unset($this->headers[$offset]);
- }
-}
diff --git a/lib/classes/restapi/RouteMap.php b/lib/classes/restapi/RouteMap.php
deleted file mode 100644
index b8ad2f4..0000000
--- a/lib/classes/restapi/RouteMap.php
+++ /dev/null
@@ -1,1060 +0,0 @@
-<?php
-namespace RESTAPI;
-
-use Config;
-use Request;
-use gossi\docblock\Docblock;
-
-/**
- * RouteMaps define and group routes to resources.
- *
- * Instances of RouteMaps are registered with the RESTAPI\Router to
- * participate in the routing business.
- *
- * A RouteMap defines at least one handler method which has to be
- * annotated with one of these annotations correlating to HTTP request
- * methods:
- *
- * @code
- * / * *
- * * An example handler method
- * *
- * * @get /foo
- * * @post /bar/:id
- * * @put /baz/:id/:other_id
- * * @delete /
- * * /
- * public function anyMethodName($id, $other_id = null) {}
- * @endcode
- *
- * By default, all API routes are unaccessible for nobody users.
- * To explicitly allow access for nobody users, add the allow_nobody
- * tag to the handler method's doc block. Example:
- *
- * @code
- * / * *
- * * Another example handler method
- * *
- * * @get /foo
- * *
- * * @allow_nobody
- * * /
- * @endcode
- *
- * As soon as the Router matches a HTTP request to a handler defined
- * in a RouteMap, it calls RouteMap::init to initialize it and
- * especially the instance field `$this->response` of type
- * RESTAPI\Response. You do not call RouteMap::init on your own.
- *
- * After the router has initialized this RouteMap, the router tries to
- * call a method `before` of this signature:
- *
- * @code
- * public function before(Router $router, Array $handler, Array $parameters);
- * @endcode
- *
- * The parameter `$handler` is a callable (as in function is_callable)
- * consisting of the instance of this RouteMap and the name of a
- * method of this instance. You may change the values of this array to
- * redirect to another handler.
- *
- * The parameter `$parameters` is an associative array whose keys
- * correlate to the placeholders in the matched URI template. The
- * values are the actual values of that placeholders in regard to the
- * HTTP request.
- *
- *
- * After calling RouteMap::before control is transfered to the actual
- * handler method. The values of the placeholders in the URI template
- * of the annotation are send as arguments to the handler.
- *
- * Example: We have got this handler method defined:
- *
- * @code
- * / * *
- * * @get /foo/:id/bar/:other_id
- * * /
- * public function fooHandler($id, $other_id) {
- * }
- * @endcode
- *
- * The router receives a request like this: `http://[..]/foo/1/bar/2`
- * and matches it to our `fooHandler` which is then called something
- * like that:
- *
- * @code
- * $result = $routeMap->fooHandler(1, 2);
- * @endcode
- *
- * In your handler methods you have to process the input and return
- * some output data, which is then rendered in an appropriate way
- * after negotiating the content format in the Router.
- *
- * Thus the return value of your handler method becomes the body of
- * the HTTP response.
- *
- *
- * The RouteMap class defines several methods to ease up your work
- * with the HTTP specifica.
- *
- * The methods RouteMap::status, RouteMap::headers and RouteMap::body
- * correlate to the components of a HTTP response.
- *
- * There are helpers for returning paginated collections, see
- * RouteMap::paginated.
- *
- * If you encounter an error or have to stop further processing, see
- * methods RouteMap::halt, RouteMap::error and RouteMap::notFound.
- *
- * These methods are \a DISRUPTIVE as they immediately stop the control
- * flow in your handler:
- *
- * @code
- * public function fooHandler($id)
- * {
- * // do something
- *
- * $this->halt();
- *
- * // this line will never be reached
- * }
- * @endcode
- *
- * If you want to simply send a redirection response (HTTP status code
- * of 302 or 303), you may find calling RouteMap::redirect helpful.
- *
- * To generate a URL to a handler, use RouteMap::url
- *
- * When you find the need to return the content of a file, please see
- * RouteMap::sendFile which will help you with streaming it to the
- * client. For custom streaming just return a Closure from your
- * handler method.
- *
- * There are several other methods which you may find useful each
- * matching a HTTP header:
- *
- * - RouteMap::contentType
- * - RouteMap::etag
- * - RouteMap::expires
- * - RouteMap::cacheControl
- * - RouteMap::lastModified
- *
- * You can access the data sent in the body of the current HTTP
- * request using the `$this->data` instance variable.
- *
- * - If the request was of Content-Type `application/json`, the
- * body of the request is decoded using `json_decode`.
- * - If the request was of Content-Type
- * `application/x-www-form-urlencoded`, the body of the request is
- * decoded using `parse_str`.
- * - Otherwise the request will not be parsed and `$this->data` will
- * just contain the raw string.
- *
- * NOTE: The result of the described parsing will always contain
- * strings encoded in windows-1252. If the original body
- * was UTF-8 encoded, it is automatically re-encoded to windows-1252.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-abstract class RouteMap
-{
- protected $router;
- protected $route;
- protected $data = null;
- protected $response;
-
- /**
- * Internal property which is used by RouteMap::paginated and
- * contains everything about a paginated collection.
- */
- protected $pagination = false;
-
- /**
- * The offset into a RouteMap::paginated collection as requested
- * by the client.
- */
- protected $offset;
-
- /**
- * The limit of a RouteMap::paginated collection as requested
- * by the client.
- */
- protected $limit;
-
- /**
- * Constructor of the route map. Initializes neccessary offset and limit
- * parameters for pagination.
- */
- public function __construct()
- {
- $this->offset = Request::int('offset', 0);
- $this->limit = Request::int('limit', Config::get()->ENTRIES_PER_PAGE);
- }
-
- /**
- * Initializes the route map by binding it to a router and passing in
- * the current route.
- *
- * @param Router $router Router to bind this route map to
- * @param array $route The matched route out of Router::matchRoute;
- * an array with keys 'handler', 'conditions' and
- * 'source'
- */
- public function init($router, $route)
- {
- $this->router = $router;
- $this->route = $route;
- $this->response = new Response();
-
- if ($mediaType = $this->getRequestMediaType()) {
- $this->data = $this->parseRequestBody($mediaType);
- }
- }
-
- /**
- * Marks this chunk of data as a slice of a larger data set with
- * a sum of "total" entries.
- *
- * @param mixed $data Chunk of data (should be sliced according
- * to current offset and limit parameters).
- * @param int $total The total number of data entries in the
- * according set.
- * @param array $uri_params Neccessary parameters when generating uris
- * for the current route.
- * @param array $query_params Optional query parameters.
- */
- public function paginated($data, $total, $uri_params = [], $query_params = [])
- {
- $uri = $this->url($this->route['uri_template']->inject($uri_params), $query_params);
-
- $this->paginate($uri, $total);
- return $this->collect($data);
- }
-
-
- /**
- * Low level method for paginating collections. You better use
- * RouteMap::paginated instead of this.
- *
- * Set the pagination data used by the RouteMap::collect.
- *
- * @param String $uri_format
- * @param int $total
- * @param mixed $offset
- * @param mixed $limit
- *
- * @return Routemap Returns instance of self to allow chaining
- */
- public function paginate($uri_format, $total, $offset = null, $limit = null)
- {
- $total = (int)$total;
- $offset = (int)($offset ?: $this->offset ?: 0);
- $limit = (int)($limit ?: $this->limit);
-
- $this->pagination = compact('uri_format', 'total', 'offset', 'limit');
-
- return $this;
- }
-
- /**
- * Low level method for paginating collections. You better use
- * RouteMap::paginated instead of this.
- *
- * Adjusts the result set to return a collection. A collection consists
- * of the passed data array and the associated pagination information
- * if available.
- *
- * Be aware that the passed data has to be already sliced according to
- * the pagination information.
- *
- * @param array $data Actual dataset
- * @return array Collection "object"
- */
- public function collect($data)
- {
- $collection = [
- 'collection' => $data
- ];
- if (is_array($this->pagination)) {
- extract($this->pagination);
-
- $offset = $offset - $offset % $limit;
- $max = ($total % $limit)
- ? $total - $total % $limit
- : $total - $limit;
-
- $pagination = compact('total', 'offset', 'limit');
- if ($total > $limit) {
- $links = [];
-
- foreach ([
- 'first' => 0,
- 'previous' => max(0, $offset - $limit),
- 'next' => min($max, $offset + $limit),
- 'last' => $max]
- as $key => $offset)
- {
- $links[$key] = \URLHelper::getURL($uri_format, compact('offset', 'limit'));
- }
-
- $pagination['links'] = $links;
- }
- $collection['pagination'] = $pagination;
- }
- return $collection;
- }
-
- /************************/
- /* REQUEST BODY METHODS */
- /************************/
-
- // find the requested media type
- private function getRequestMediaType()
- {
- if (!empty($_SERVER['CONTENT_TYPE'])) {
- $contentTypeParts = preg_split('/\s*[;,]\s*/', $_SERVER['CONTENT_TYPE']);
- return mb_strtolower($contentTypeParts[0]);
- }
- }
-
- // media-types that we know how to process
- private static $mediaTypes = [
- 'application/json' => 'parseJson',
- 'application/x-www-form-urlencoded' => 'parseFormEncoded',
- 'multipart/form-data' => 'parseMultipartFormdata'
- ];
-
- // cache the request body
- private static $_request_body;
-
- // reads the HTTP request body
- private function parseRequestBody($mediaType)
- {
- // read it only once
- if (!isset(self::$_request_body)) {
- self::$_request_body = file_get_contents('php://input');
- }
-
- if (isset(self::$mediaTypes[$mediaType])) {
- $result = call_user_func([__CLASS__, self::$mediaTypes[$mediaType]], self::$_request_body);
- if ($result) {
- return $result;
- }
- }
- return self::$_request_body;
- }
-
- // strategy to decode JSON strings
- private static function parseJson($input)
- {
- return json_decode($input, true);
- }
-
- // strategy to decode form encoded strings
- private static function parseFormEncoded($input)
- {
- parse_str($input, $result);
- return $result;
- }
-
- // strategy to decode a multipart message. Used for file-uploads.
- private static function parseMultipartFormdata($input)
- {
-
- $data = [];
- if (Request::isPost()) {
- foreach ($_POST as $key => $value) {
- $data[$key] = $value;
- }
- $data['_FILES'] = $_FILES;
- return $data;
- }
- $boundary = self::getMultipartBoundary();
- if (!$boundary) {
- return $data;
- }
- $input = explode("--".$boundary, $input);
-
- array_pop($input);
- array_shift($input);
-
- foreach ($input as $part) {
- $part = ltrim($part, "\r\n");
- [$head, $body] = explode("\r\n\r\n", $part, 2);
-
- $tmpheaders = $headers = [];
- foreach (explode("\r\n", $head) as $headline) {
- if (preg_match('/^[^\s]/', $headline)) {
- $lineIsHeader = preg_match('/([^:]+):\s*(.*)$/', $headline, $matches);
- if ($lineIsHeader) {
- $tmpheaders[] = ['index' => mb_strtolower(trim($matches[1])), 'value' => trim($matches[2])];
- }
- } else {
- //noch zur letzten Zeile hinzuzählen
- end($tmpheaders);
- $lastkey = key($tmpheaders);
- $tmpheaders[$lastkey]['value'] .= " ".mb_substr($headline, 1);
- }
- }
- foreach ($tmpheaders as $header) {
- $headers[$header['index']] = $header['value'];
- }
-
- $contentType = "";
- if (isset($headers['content-type'])) {
- preg_match("/^([^;\s]*)/", $headers['content-type'], $matches);
- $contentType = mb_strtolower($matches[1]);
- }
- switch ($headers["transfer-encoding"]) {
- case "quoted-printable":
- $body = quoted_printable_decode($body);
- break;
- case "base64":
- $body = base64_decode(preg_replace("/(\r?\n|\r)/", "", trim($body)));
- break;
- case "7bit":
- case "8bit":
- default:
- //nothing to do
- }
- $matches = [];
- preg_match("/name=([^;\s]*)/i", $headers['content-disposition'], $matches);
- $name = str_replace(["'", '"'], '', $matches[1]);
- if (!$contentType) {
- $data[$name] = mb_substr($body, 0, mb_strlen($body) - 2);
- } else {
- switch ($contentType) {
- case 'application/json':
- $data = array_merge($data, self::parseJson($body));
- break;
- case 'application/x-www-form-urlencoded':
- $data = array_merge($data, self::parseFormEncoded($body));
- break;
- default:
- $matches = [];
- preg_match("/filename=([^;\s]*)/i", $headers['content-disposition'], $matches);
- if (!$matches[1]) {
- preg_match('/filename=([^;\s]*)/i', $headers['content-type'], $matches);
- }
- $filename = str_replace(["'", '"'], '', $matches[1]);
- $tmp_name = $GLOBALS['TMP_PATH']."/uploadfile_".md5(uniqid());
- $handle = fopen($tmp_name, 'wb');
- $filesize = fwrite($handle, $body, (mb_strlen($body) - 2));
- fclose($handle);
- $data['_FILES'][$name] = [
- 'name' => $filename,
- 'type' => $contentType,
- 'tmp_name' => $tmp_name,
- 'size' => $filesize
- ];
- }
- }
- }
- return $data;
- }
-
- private static function getMultipartBoundary()
- {
- if ($contentType = $_SERVER['CONTENT_TYPE']) {
- foreach (preg_split('/\s*[;,]\s*/', $contentType) as $part) {
- if (mb_strtolower(mb_substr($part, 0, 8)) === "boundary") {
- $part = explode("=", $part);
- return $part[1];
- }
- }
- }
- return null;
- }
-
-
- /**
- * Set the HTTP status of the current response.
- *
- * @param integer $status the HTTP status of the response
- */
- public function status($status)
- {
- $this->response->status = $status;
- }
-
- /**
- * Set multiple response headers of the current response by
- * merging them with already set ones.
- *
- * @code
- * $routemap->headers(array('X-example' => "yep"));
- * @endcode
- *
- * @param array $headers the headers to set
- *
- * @return array the headers of the current response
- */
- public function headers($headers = [])
- {
- if (sizeof($headers)) {
- $this->response->headers = array_merge($this->response->headers, $headers);
- }
- return $this->response->headers;
- }
-
- /**
- * Set the HTTP body of the current response.
- *
- * @param string $body the body to send back
- */
- public function body($body)
- {
- $this->response->body = $body;
- }
-
-
- /**
- * Set the Content-Type of the HTTP response given a mime type and
- * optionally further parameters as discusses in RFC 2616 14.17.
- *
- * If no charset is given, it defaults to Stud.IP's 'windows-1252'.
- *
- * Examples:
- *
- * @code
- * // results in "Content-Type: image/gif"
- * $this->contentType('image/gif);
- *
- * // results in "Content-Type: text/html;charset=ISO-8859-4"
- * $this->contentType('text/html;charset=ISO-8859-4');
- *
- * // results in "Content-Type: text/html;charset=ISO-8859-4"
- * $this->contentType('text/html', array('charset' => 'ISO-8859-4'));
- *
- * // results in "Content-type: multipart/byteranges; boundary=THIS_STRING_SEPARATES"
- * $this->contentType('multipart/byteranges', array('boundary' => 'THIS_STRING_SEPARATES'));
- *
- * @endcode
- *
- * @param string $mime_type a string describing a MIME type like 'application/json'
- * @param array $params optional parameters as described above
- */
- public function contentType($mime_type, $params = [])
- {
- if (!isset($params['charset'])) {
- $params['charset'] = 'utf-8';
- }
-
- if (mb_strpos($mime_type, 'charset') !== FALSE) {
- unset($params['charset']);
- }
-
- if (sizeof($params)) {
- $mime_type .= mb_strpos($mime_type, ';') !== FALSE ? ', ' : ';';
- $ps = [];
- foreach ($params as $k => $v) {
- $ps[] = $k . '=' . $v;
- }
- $mime_type .= join(', ', $ps);
- }
-
- $this->response['Content-Type'] = $mime_type;
- }
-
- /**
- * (Nice) sugar for calling RouteMap::halt and therefore
- * as \a DISRUPTIVE. Code after calling RouteMap::error will not
- * be evaluated.
- *
- * @see RouteMap::halt
- *
- * @param integer $status a number indicating the HTTP status
- * code; probably something 4xx or 5xx-ish
- * @param string $body optional; the body of the HTTP response
- *
- */
- public function error($status, $body = null)
- {
- $this->halt($status, [], $body);
- }
-
-
- /**
- * Sets the HTTP response's Etag header and halts, if the incoming
- * HTTP request was a matching conditional GET using an
- * 'If-None-Match' header. Thus it is a possibly \a DISRUPTIVE
- * method as it will stop evaluation in that case and send a '304
- * Not Modified'.
- *
- * Detail: If the request contains an If-Match or If-None-Match
- * header set to `*`, a RouteMap assumes a match on safe
- * (e.g. GET) and idempotent (e.g. PUT) requests. (In those cases
- * it thinks that the resource already exists and therefore
- * matches a wildcard.). This can be changed by passing an
- * appropriate value for the `$new_resource` parameter.
-
- * Details of this can be found in RFC 2616 14.24 and 14.26
- *
- * @param string $value an identifier uniquely identifying the
- * current state of a resource
- * @param bool $strong_etag optional; indicates whether the etag
- * is a weak or strong (which is the
- * default) cache validator. Have a look
- * at the RFC for details.
- * @param bool $new_resource optional; a way to tell the RouteMap
- * that this is a new or existing
- * resource. See above.
- */
-
- public function etag($value, $strong_etag = true, $new_resource = null)
- {
- // Before touching this code, please double check RFC 2616
- // 14.24 and 14.26.
-
- if (!isset($new_resource)) {
- $new_resource = Request::isPost();
- }
-
- $value = '"' . $value . '"';
- if (!$strong_etag) {
- $value = 'W/' . $value;
- }
- $this->response['ETag'] = $value;
-
- if ($this->response->isSuccess() || $this->response->status === 304) {
- if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $this->etagMatches($_SERVER['HTTP_IF_NONE_MATCH'], $new_resource)) {
- $this->halt($this->isRequestSafe() ? 304 : 412);
- }
- if (isset($_SERVER['HTTP_IF_MATCH'])
- && !$this->etagMatches($_SERVER['HTTP_IF_MATCH'], $new_resource)) {
- $this->halt(412);
- }
- }
- }
-
- // Helper method checking if a ETag value list includes the current ETag.
- private function etagMatches($list, $new_resource)
- {
- if ($list === '*') {
- return !$new_resource;
- }
-
- return in_array($this->response['ETag'],
- preg_split('/\s*,\s*/', $list));
- }
-
- // Helper method checking if the request is safe
- private function isRequestSafe()
- {
- $method = Request::method();
- return $method === 'GET' or $method === 'HEAD' or $method === 'OPTIONS' or $method === 'TRACE';
- }
-
- /**
- * This sets the `Expires` header and the `Cache-Control`
- * directive `max-age`.
- *
- * Amount is an integer number of seconds in the future indicating
- * when the response should be considered "stale". The
- * `$cache_control` parameter is passed to RouteMap#cacheControl
- * along with the automatically generated `max_age` directive.
- *
- * @param int $amount an integer specifying the number of seconds
- * this resource will go stale.
- * @param array $cache_control optional; more directives for
- * RouteMap::cacheControl which is always
- * automatically called using the computed max_age
- */
- public function expires($amount, $cache_control = [])
- {
- $time = time() + $amount;
- $max_age = $amount;
-
- $cache_control[] = "max-age=$max_age";
- $this->cacheControl($cache_control);
-
- $this->response['Expires'] = $this->httpDate($time);
- }
-
- /**
- * This sets the Cache-Control header of the HTTP response.
- *
- * Example:
- *
- * @code
- * $this->cacheControl(array('public', 'must-revalidate'));
- * @endcode
- *
- * @param array $values an array containing Cache-Control
- * directives.
- */
- public function cacheControl($values)
- {
- if (is_array($values) && sizeof($values)) {
- $this->response['Cache-Control'] = join(', ', $values);
- }
- }
-
- /**
- * This very important method stops further execution of your
- * code. You may specify a status code, headers and the body of
- * the resulting response. As the name implies, this method is \a
- * DISRUPTIVE and will not return.
- *
- * @code
- * // stops any further code of a route
- * $this->halt();
- *
- * // you may specify an HTTP status
- * $this->halt(409):
- *
- * // you may specify the HTTP response's body
- * $this->halt('my ethereal body')
- *
- * // or even both
- * $this->halt(100, 'Yes, pleazze!')
- *
- * // giving headers
- * $this->halt(417, array('Content-Type' => 'x-not-a-cat'), 'Cats only!')
- * @endcode
- *
- * This method is called by every single \a DISRUPTIVE method.
- *
- * @param integer $status optional; the response's status code
- * @param array $headers optional; (additional) header lines
- * which get merged with already set headers
- * @param string $body optional; the response's body
- */
- public function halt(/* [status], [headers], [body] */)
- {
- $args = func_get_args();
- $result = [];
-
- $constraints = [
- 'status' => 'is_int',
- 'headers' => 'is_array',
- 'body' => function ($i) { return isset($i); } // #existy
- ];
- foreach ($constraints as $state => $constraint) {
- if ($constraint(current($args))) {
- call_user_func([$this, $state], array_shift($args));
- }
- }
-
- throw new RouterHalt($this->response);
- }
-
- /**
- * This method sets the Last-Modified header of the HTTP response
- * and halts on matching conditional GET requests. Thus this
- * method is \a DISRUPTIVE in certain circumstances.
- *
- * You have to give an integer typed timestamp (in seconds since
- * epoch) to specify the data of the last modification to the
- * requested resource.
- *
- * If the current HTTP request contains an `If-Modified-Since`
- * header, its value is compared to the specified `$time`
- * parameter. Unless the header's value is sooner than the given
- * `$time`, further execution is precluded and the RouteMap
- * returns with a '304 Not Modified'.
- *
- * @param integer $time a timestamp described in seconds since epoch
- */
- public function lastModified($time)
- {
-
- $this->response['Last-Modified'] = $this->httpDate($time);
-
- if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
- return;
- }
-
- if ($this->response->status === 200
- && isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
- // compare based on seconds since epoch
- $since = $this->httpdate($_SERVER['HTTP_IF_MODIFIED_SINCE']);
- if ($since >= (int) $time) {
- $this->halt(304);
- }
- }
-
- if (($this->response->isSuccess() || $this->response->status === 412)
- && isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {
-
- // compare based on seconds since epoch
- $since = $this->httpdate($_SERVER['HTTP_IF_UNMODIFIED_SINCE']);
-
- if ($since < (int) $time) {
- $this->halt(412);
- }
- }
- }
-
- private function httpDate($timestamp)
- {
- return gmdate('D, d M Y H:i:s \G\M\T', (int) $timestamp);
- }
-
- /**
- * Halts execution and returns a '404 Not Found' response.
- *
- * Sugar for calling RouteMap::error(404) and therefore
- * \a DISRUPTIVE. Code after calling RouteMap::notFound will
- * not be evaluated.
- *
- * @see RouteMap::error
- * @see RouteMap::halt
- *
- * @param string $body optional; the body of the HTTP response
- */
- public function notFound($body = null)
- {
- $this->halt(404, $body);
- }
-
- /**
- * Stops your code and redirects to the URL provided. This method
- * is \a DISRUPTIVE like RouteMap#halt
- *
- * In addition to the URL you may provide the status code,
- * (additional) headers and a request body as you would when
- * calling RouteMap#halt.
- *
- * @code
- * $this->redirect('/foo', 201, array('X-Some-Header' => 1234), 'and even a body');
- * @endcode
- *
- * @see RouteMap::halt
- *
- * @param string $url the URL to redirect to; it will be filtered
- * using RouteMap#url, so you may call it with
- * those nice and small strings used in the
- * annotations
- * @param mixed $args optional; any combinations of the three
- * parameters as in RouteMap::halt
- */
- public function redirect($url, $args = null)
- {
- $this->status($_SERVER["SERVER_PROTOCOL"] === 'HTTP/1.1' && !Request::isGet() ? 303 : 302);
- $this->response['Location'] = $this->url($url);
-
- $args = array_slice(func_get_args(), 1);
- call_user_func_array([$this, 'halt'], $args);
- }
-
-
- /**
- * Stops execution of your code and starts sending the specified
- * file. This method is \a DISRUPTIVE.
- *
- * Using the `$opts` parameter you may specify the file's mime
- * content type, sending an appropriate 'Content-Type' header, and
- * you may specify the 'Content-Disposition' of the file transfer.
- *
- * Example:
- *
- * @code
- * $this->sendFile('/tmp/c29tZSB0ZXh0', array(
- * 'type' => 'image/png',
- * 'disposition' => 'inline',
- * 'filename' => 'cutecats.png'));
- * @endcode
- *
- * @param string $_path the filesystem path to the file to send
- * @param array $opts optional; specify the content type,
- * disposition and filename
- */
- public function sendFile($_path, $opts = [])
- {
- $path = realpath($_path);
-
- if (!file_exists($path)) {
- $this->notFound('File to send does not exist');
- }
-
- if (isset($opts['type'])) {
- $this->contentType($opts['type']);
- } else if (!isset($this->response['Content-Type'])) {
- $this->contentType(get_mime_type($path));
- }
-
- if ($opts['disposition'] === 'attachment' || isset($opts['filename'])) {
- $this->response['Content-Disposition'] = 'attachment; ';
- $filename = $opts['filename'] ?: $path;
- $this->response['Content-Disposition'] .= encode_header_parameter('filename', basename($filename));
- }
-
- elseif ($opts['disposition'] === 'inline') {
- $this->response['Content-Disposition'] = 'inline';
- }
-
- // TODO add HTTP 'Range' support
-
- $size = filesize($path);
- $this->response['Content-Length'] = $size;
-
- // End all potential output buffers
- while (ob_get_level() > 0) {
- ob_end_clean();
- }
-
- // Send file
- $this->halt(200, $this->response->headers, function () use ($path) {
- readfile($path);
- });
- }
-
-
- /**
- * Generate a URL to a given handler using a URL fragment and URL
- * parameters.
- *
- * Example:
- * @code
- * // result in something like "/some/path/api.php/course/123/members?status=student"
- * $this->url('course/123/members', array('status' => 'student'));
- * @endcode
- *
- * @param string $addr a URL fragment to a handler
- * @param array $url_params optional; URL parameters to add to
- * the generated URL
- *
- * @return string the resulting URL
- */
- public function url($addr, $url_params = null)
- {
- $addr = ltrim($addr, '/');
- return \URLHelper::getURL("api.php/$addr", $url_params, true);
- }
-
- /**
- * A `vsprintf` like variant to the RouteMap::url method.
- *
- * Example:
- * @code
- * // results in "[...]/api.php/foo/some_id?status=student"
- * $this->urlf("foo/%s", array("some_id"), array('status' => 'student'));
- * @endcode
- *
- * @param string $addr_f a URL fragment to a handler
- * containing sprintf-ish format sequences
- * @param array $format_params values to fill into the format markers
- * @param array $url_params optional; URL parameters to add to
- * the generated URL
- *
- * @return string the resulting URL
- */
-
- public function urlf($addr_f, $format_params, $url_params = null)
- {
- if (!is_array($format_params)) {
- $format_params = [$format_params];
- }
- return $this->url(vsprintf($addr_f, $format_params), $url_params);
- }
-
- /**
- * Returns a list of all the routes this routemap provides.
- *
- * @param string $http_method Return only the routes for this specific
- * http method (optional)
- *
- * @return array of all routes grouped by method
- */
- public function getRoutes($http_method = null)
- {
- $ref = new \ReflectionClass($this);
-
- if ($ref->getDocComment()) {
- $docblock = new Docblock($ref);
- $class_conditions = $this->extractConditions($docblock);
- } else {
- $class_conditions = [];
- }
-
-
- // Create result array by creating an associative array from all
- // supported methods as keys
- $routes = array_fill_keys(Router::getSupportedMethods(), []);
-
- // Restrict routes to given http method (if given)
- if ($http_method !== null) {
- $routes = [$http_method => []];
- }
-
- // Iterate through all methods of the routemap
- foreach ($ref->getMethods( \ReflectionMethod::IS_PUBLIC) as $ref_method) {
- // No docblock? Not an api route!
- if (!$ref_method->getDocComment()) {
- continue;
- }
-
- // Parse docblock
- $docblock = new Docblock($ref_method);
-
- // No docblock tags? Not an api route!
- if ($docblock->getTags()->isEmpty()) {
- continue;
- }
-
- // Any specific condition to consider?
- $conditions = $this->extractConditions($docblock, $class_conditions);
-
- // Iterate through all possible methods in order to identify
- // any according docblock tags
- $allow_nobody = $docblock->hasTag('allow_nobody');
- foreach (array_keys($routes) as $http_method) {
- if (!$docblock->hasTag($http_method)) {
- //The tag for the current HTTP method cannot be found
- //in the route's DocBlock tags.
- continue;
- }
-
- // Route all defined method and uri template combinations to
- // the according methods of the object.
- foreach ($docblock->getTags($http_method) as $tag) {
- $uri_template = trim($tag->getDescription());
- $routes[$http_method][$uri_template] = [
- 'handler' => [$this, $ref_method->name],
- 'conditions' => $conditions,
- 'description' => trim($docblock->getShortDescription()) ?: false,
- 'allow_nobody' => $allow_nobody
- ];
- }
- }
- }
-
- // Return all routes grouped or just the routes for the wanted method
- return func_num_args() === 1
- ? reset($routes)
- : $routes;
- }
-
- /**
- * Extracts defined conditions from a given docblock.
- *
- * @param Docblock $docblock DocBlock to examine
- * @param array $conditions Optional array of already defined
- * conditions to extend
- * @return array of all extracted conditions with the variable name
- * as key and pattern to match as value
- */
- protected function extractConditions($docblock, $conditions = [])
- {
- foreach ($docblock->getTags('condition') as $condition) {
- [$var, $pattern] = explode(' ', $condition->getDescription(), 2);
- $conditions[$var] = $pattern;
- }
-
- return $conditions;
- }
-
- /**
- * Returns the response object
- * @return Response
- */
- public function getResponse(): Response
- {
- return $this->response;
- }
-}
diff --git a/lib/classes/restapi/Router.php b/lib/classes/restapi/Router.php
deleted file mode 100644
index df7a6b9..0000000
--- a/lib/classes/restapi/Router.php
+++ /dev/null
@@ -1,665 +0,0 @@
-<?php
-/** @namespace RESTAPI
- *
- * Im Namensraum RESTAPI sind alle Klassen und Funktionen versammelt,
- * die für die RESTful Web Services von Stud.IP benötigt werden.
- */
-namespace RESTAPI;
-use RESTAPI\Renderer\DefaultRenderer;
-
-/**
- * Die Aufgabe des Routers ist das Anlegen und Auswerten eines
- * Mappings von sogenannten Routen (Tupel aus HTTP-Methode und Pfad)
- * auf Code.
- *
- * Dazu werden zunächst Routen mittels der Funktion
- * Router::registerRoutes registriert.
- *
- * Wenn dann ein HTTP-Request eingeht, kann mithilfe von
- * Router::dispatch und HTTP-Methode bzw. Pfad der zugehörige Code
- * gefunden und ausgeführt werden. Der Router bildet aus dem
- * Rückgabewert des Codes ein Response-Objekt, das er als Ergebnis
- * zurück meldet.
- *
- * @code
- * $router = Router::getInstance();
- *
- * // register a sample Route
- * $router->registerRoutes(new ExampleRoute);
- *
- * // dispatch to therein defined Routes
- * $response = $router->dispatch('/example', 'GET');
- *
- * // render response
- * $response->output();
- *
- * @endcode
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @see Inspired by http://blog.sosedoff.com/2009/07/04/simpe-php-url-routing-controller/
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class Router
-{
- // instances are cached here
- protected static $instances = [];
-
- /**
- * Holds the user object of the user that is accessing the API.
- * This is null for nobody users.
- */
- protected $user = null;
-
- /**
- * Returns (and if neccessary, initializes) a (cached) router object for an
- * optional consumer id.
- *
- * @param mixed $consumer_id ID of the consumer (defaults to 'global')
- *
- * @return Router returns the Router instance associated to the
- * consumer ID (or to the 'global' ID)
- */
- public static function getInstance($consumer_id = null)
- {
- $consumer_id = $consumer_id ?: 'global';
-
- if (!isset(self::$instances[$consumer_id])) {
- self::$instances[$consumer_id] = new self($consumer_id);
- }
- return self::$instances[$consumer_id];
- }
-
- // All supported method need to be defined here
- protected static $supported_methods = [
- 'get', 'post', 'put', 'delete', 'patch', 'options', 'head'
- ];
-
- /**
- * Returns a list of all supported methods.
- *
- * @return array of methods as strings
- */
- public static function getSupportedMethods()
- {
- return self::$supported_methods;
- }
-
- // registered routes by method and uri template
- protected $routes = [];
-
- // registered content renderers
- protected $renderers = [];
-
- // identified or forced content renderer
- protected $content_renderer = false;
-
- // default renderer
- protected $default_renderer = false;
-
- // registered conditions
- protected $conditions = [];
-
- // registered descriptions
- protected $descriptions = [];
-
- // registered consumers
- protected $consumers = [];
-
- // associated permissions
- protected $permissions = false;
-
- /**
- * Constructs the router.
- *
- * @param mixed $consumer_id the ID of the consumer this router
- * should associate to
- */
- protected function __construct($consumer_id)
- {
- $this->permissions = ConsumerPermissions::get($consumer_id);
- $this->registerRenderer(new Renderer\DefaultRenderer);
- }
-
- /**
- * Registers a handler for a specific combination of request method
- * and uri template.
- *
- * @param String $request_method expected HTTP request method
- * @param String $uri_template expected URI template, for
- * example: \code "/user/:user_id/events" \endcode
- * @param Array $handler request handler array:
- * \code array($object, "methodName") \endcode
- * @param Array $conditions (optional) an associative
- * array using the name of
- * parameters as keys and regexps
- * as value
- * @param string $source (optional) this denotes the
- * origin of a route. Usually
- * either 'core' or 'plugin', but
- * defaults to 'unknown'.
- * @param bool $allow_nobody Whether the route can be accessed
- * as nobody user (true) or not (false).
- * Defaults to false.
- *
- * @return Router returns itself to allow chaining
- * @throws \Exception if passed HTTP request method is not supported
- */
- public function register($request_method, $uri_template, $handler, $conditions = [], $source = 'unknown', $allow_nobody = false)
- {
- // Normalize method and test whether it's supported
- $request_method = mb_strtolower($request_method);
- if (!in_array($request_method, self::$supported_methods)) {
- throw new \Exception('Method "' . $request_method . '" is not supported.');
- }
-
- // Initialize routes storage for this method if neccessary
- if (!isset($this->routes[$request_method])) {
- $this->routes[$request_method] = [];
- }
-
- // Normalize uri template (always starts with a slash)
- if ($uri_template[0] !== '/') {
- $uri_template = '/' . $uri_template;
- }
-
- // Sanitize conditions
- foreach ($conditions as $var => $pattern) {
- if ($pattern[0] !== $pattern[mb_strlen($pattern) - 1] || ctype_alnum($pattern[0])) {
- $conditions[$var] = '/' . $pattern . '/';
- }
- }
-
- $this->routes[$request_method][$uri_template] = compact(
- 'handler', 'conditions', 'source', 'allow_nobody'
- );
-
- // Return instance to allow chaining
- return $this;
- }
-
- /**
- * Registers the routes defined in a RouteMap instance using
- * docblock annotations (like @get) of its methods.
- *
- * \code
- * $router = \RESTAPI\Router::getInstance();
- *
- * $router->registerRoutes(new ExampleRouteMap());
- * \endcode
- *
- * @param RouteMap $map the RouteMap instance to register
- *
- * @return Router returns itself to allow chaining
- */
- public function registerRoutes(RouteMap $map)
- {
- // Investigate object, define whether it's located in the core system
- // or a plugin, respect any defined class conditions and iterate
- // through it's methods to find any defined route
- $ref = new \ReflectionClass($map);
- $filename = $ref->getFilename();
- $source = mb_strpos($filename, 'plugins_packages') !== false
- ? 'plugin'
- : 'core';
-
- foreach (self::$supported_methods as $http_method) {
- foreach ($map->getRoutes($http_method) as $uri_template => $data) {
- // Register (and describe) route
- $this->register(
- $http_method, $uri_template,
- $data['handler'], $data['conditions'],
- $source,
- $data['allow_nobody']
- );
- if ($data['description']) {
- $this->describe(
- $uri_template,
- $data['description'],
- $http_method
- );
- }
- }
- }
-
- return $this;
- }
-
- /**
- * Describe one or more routes.
- *
- * \code
- * $router = \RESTAPI\Router::getInstance();
- *
- * // describe a single route
- * $router->describe('/foo', 'returns everything about foo', 'get');
- *
- * // describe several routes that use the same path
- * $router->describe('/foo', array(
- * 'get' => 'returns everything about foo',
- * 'put' => 'updates all of foo',
- * 'delete' => 'empty up foo'
- * ));
- *
- * // describe several routes
- * $router->describe(array(
- * '/foo' => array(
- * 'get' => 'returns everything about foo',
- * 'put' => 'updates all of foo',
- * 'delete' => 'empty up foo'),
- * '/bar' => array(...),
- * ));
- * \endcode
- *
- * @param String|Array $uri_template URI template to describe or pass an
- * array to describe multiple routes.
- * @param String|null $description description of the route
- * @param String $method method to describe.
- *
- * @return Router returns instance of itself to allow chaining
- */
- public function describe($uri_template, $description = null, $method = 'get')
- {
- // describe multiple routes at once
- if (func_num_args() === 1 && is_array($uri_template)) {
- foreach ($uri_template as $template => $description) {
- $this->describe($template, $description);
- }
- }
-
- // describe routes that use the same URI template
- elseif (func_num_args() === 2 && is_array($description)) {
- foreach ($description as $method => $desc) {
- $this->describe($uri_template, $desc, $method);
- }
- }
-
- // describe a single route
- else {
- if (!isset($this->descriptions[$uri_template])) {
- $this->descriptions[$uri_template] = [];
- }
- if (isset($this->routes[$method][$uri_template])) {
- $this->descriptions[$uri_template][$method] = $description;
- } else {
- // Try to find route with different method
- foreach ($this->routes as $m => $templates) {
- if (isset($templates[$uri_template])) {
- $this->descriptions[$uri_template][$m] = $description;
- break;
- }
- }
- }
- }
- return $this;
- }
-
- /**
- * Get list of registered routes - optionally with their descriptions.
- *
- * @param bool $describe (optional) include descriptions,
- * defaults to `false`
- * @param bool $check_access (optional) only show methods this router's
- * consumer is authorized to,
- * defaults to `true`
- *
- * @return array list of registered routes
- */
- public function getRoutes($describe = false, $check_access = true)
- {
- $this->setupRoutes();
-
- $result = [];
- foreach ($this->routes as $method => $routes) {
- foreach ($routes as $uri => $route) {
- if ($check_access && !$this->permissions->check($uri, $method)) {
- continue;
- }
- if (!isset($result[$uri])) {
- $result[$uri] = [];
- }
- if ($describe) {
- $result[$uri][$method] = [
- 'description' => $this->descriptions[$uri][$method] ?? null,
- 'source' => $route['source'] ?? 'unknown',
- ];
- } else {
- $result[$uri][] = $method;
- }
- }
- }
- ksort($result);
- if ($describe) {
- $result = array_map(function ($item) {
- ksort($item);
- return $item;
- }, $result);
- }
- return $result;
- }
-
- /**
- * Dispatches an URI across the defined routes and produces a
- * Response object which may then be send back (using #output).
- *
- * @param mixed $uri URI to dispatch (defaults to `$_SERVER['PATH_INFO']`)
- * @param String $method Request method (defaults to the method
- * of the actual HTTP request or "GET")
- *
- * @return Response a Response object containing status, headers
- * and body
- * @throws RouterException may throw such an exception if there
- * is no matching route (404) or if there
- * is one, but the consumer is not
- * authorized to it (403)
- */
- public function dispatch($uri = null, $method = null)
- {
- $this->setupRoutes();
-
- $uri = $this->normalizeDispatchURI($uri);
- $method = $this->normalizeRequestMethod($method);
-
- $content_renderer = $this->negotiateContent($uri);
-
- $match_result = $this->matchRoute($uri, $method, $content_renderer);
- $route = $match_result[0];
- $parameters = $match_result[1];
- $allow_nobody = $match_result[2] ?? false;
- if (!$route) {
- //No route found for the combination of URI and method.
- //We return the allowed methods for the route in the HTTP header:
- $methods = $this->getMethodsForUri($uri);
- if (count($methods) > 0) {
- header('Allow: ' . implode(', ', $methods));
- throw new RouterException(405);
- } else {
- //Route not found.
- throw new RouterException(404);
- }
- }
- //At this point, a route is found.
- //We need to check if it can be used as nobody user or not.
- if (!$route['allow_nobody'] && !$this->user) {
- //Nobody users aren't allowed for this route.
- throw new RouterException(401, 'Unauthorized (no consumer)');
- }
-
- try {
- $response = $this->execute($route, $parameters);
- } catch (RouterHalt $halt) {
- $response = $halt->response;
- }
-
- $response->finish($content_renderer);
-
- return $response;
- }
-
- /**
- * Searches and registers available routes.
- */
- private function setupRoutes()
- {
- // A bit ugly, I confess
- static $was_setup = false;
- if ($was_setup) {
- return;
- }
- $was_setup = true;
-
- // Register default routes
- $routes = [
- 'Activity',
- 'Blubber',
- 'Clipboard',
- 'Contacts',
- 'Course',
- 'Discovery',
- 'Events',
- 'Feedback',
- 'FileSystem',
- 'Forum',
- 'Messages',
- 'News',
- 'ResourceBooking',
- 'Resources',
- 'ResourceCategories',
- 'ResourcePermissions',
- 'ResourceProperties',
- 'ResourceRequest',
- 'RoomClipboard',
- 'Schedule',
- 'Semester',
- 'Studip',
- 'User',
- 'UserConfig',
- 'Wiki'
- ];
-
- foreach ($routes as $route) {
- require_once "app/routes/$route.php";
- $class = "\\RESTAPI\\Routes\\$route";
- $this->registerRoutes(new $class);
- }
-
- // Register plugin routes
- $router = $this;
- $routes = array_flatten(\PluginEngine::sendMessage('RESTAPIPlugin', 'getRouteMaps'));
- array_walk(
- $routes,
- function ($route) use ($router) {
- $router->registerRoutes($route);
- }
- );
- }
-
- /**
- * Takes a route and the parameters out of the requested path and
- * executes the handler of the route.
- *
- * @param array $route the matched route out of
- * Router::matchRoute; an array with keys
- * 'handler', 'conditions' and 'source'
- * @param array $parameters the matched parameters out of
- * Router::matchRoute; something like:
- * `array('user_id' => '23a21d...e78f')`
- * @return Response the resulting Response object which is then
- * polished in Router::dispatch
- */
- protected function execute($route, $parameters)
- {
- $handler = $route['handler'];
-
- if (!is_object($handler[0])) {
- throw new \RuntimeException("Handler is not a method.");
- }
-
- $handler[0]->init($this, $route);
-
- if (method_exists($handler[0], 'before')) {
- $handler[0]->before($this, $handler, $parameters);
- }
-
- $result = call_user_func_array($handler, $parameters);
-
- if (is_object($result) && method_exists($result, 'toArray')) {
- $result = $result->toArray();
- }
-
- // $result is stronger than $response->body
- if (isset($result)) {
- $handler[0]->body($result);
- }
-
- if (method_exists($handler[0], 'after')) {
- $handler[0]->after($this, $parameters);
- }
-
- return $handler[0]->getResponse();
- }
-
- /**
- * Registers a content renderer.
- *
- * @param DefaultRenderer $renderer instance of a content renderer
- * @param boolean $is_default (optional) set this
- * renderer as default?;
- * defaults to `false`
- *
- * @return Router returns itself to allow chaining
- */
- public function registerRenderer($renderer, $is_default = false)
- {
- $this->renderers[$renderer->extension()] = $renderer;
- if ($is_default) {
- $this->default_renderer = $renderer;
- }
-
- return $this;
- }
-
- private function normalizeDispatchURI($uri)
- {
- return $uri ?? \Request::pathInfo();
- }
-
- private function normalizeRequestMethod($method)
- {
- return mb_strtolower($method ?: \Request::method() ?: 'get');
- }
-
- /**
- * Negotiate content using the registered content renderers. The
- * first ContentRenderer that returns `true` when calling
- * ContentRenderer::shouldRespondTo gets the job.
- *
- * @param String $uri the URI to which the content renderers may respond
- *
- * @return ContentRenderer either a ContentRenderer that responds
- * to the URI or the default
- * ContentRenderer of this router.
- */
- protected function negotiateContent($uri)
- {
- $content_renderer = null;
- foreach ($this->renderers as $renderer) {
- if ($renderer->shouldRespondTo($uri)) {
- $content_renderer = $renderer;
- break;
- }
- }
- if (!$content_renderer) {
- $content_renderer = $this->default_renderer ?: reset($this->renderers);
- }
- return $content_renderer;
- }
-
- /**
- * Tries to match a route given a URI and a HTTP request method.
- *
- * @param String $uri the URI to match
- * @param String $method the HTTP request method to match
- * @param DefaultRenderer $content_renderer the used
- * ContentRenderer which
- * is needed to remove
- * a file extension
- *
- * @return array an array containing the matched route and the
- * found parameters
- */
- protected function matchRoute($uri, $method, $content_renderer)
- {
- $matched = null;
- $parameters = [];
- if (isset($this->routes[$method])) {
- if ($content_renderer->extension() && mb_strpos($uri, $content_renderer->extension()) !== false) {
- $uri = mb_substr($uri, 0, -mb_strlen($content_renderer->extension()));
- }
-
- foreach ($this->routes[$method] as $uri_template => $route) {
- if (!isset($route['uri_template'])) {
- $route['uri_template'] = new UriTemplate($uri_template, $route['conditions']);
- }
-
- $prmtrs = null; // Will be filled by a successful match()
- if ($route['uri_template']->match($uri, $prmtrs)) {
- if (!$this->permissions->check($uri_template, $method)) {
- throw new RouterException(403, "Route not activated");
- }
- $matched = $route;
- $parameters = $prmtrs;
- break;
- }
- }
- }
- return [$matched, $parameters];
- }
-
- /**
- * Returns all methods the given uri responds to.
- *
- * @param String $uri the URI to match
- *
- * @return array of all of responding methods
- */
- protected function getMethodsForUri($uri)
- {
- $methods = [];
-
- foreach ($this->routes as $method => $templates) {
- foreach ($templates as $uri_template => $route) {
- if (!isset($route['uri_template'])) {
- $route['uri_template'] = new UriTemplate($uri_template, $route['conditions']);
- }
-
- if ($route['uri_template']->match($uri)
- && $this->permissions->check($uri_template, $method))
- {
- $methods[] = $method;
- }
- }
- }
-
- return array_map('strtoupper', $methods);
- }
-
-
- /**
- * Sets up the authentication for the router.
- */
- public function setupAuth()
- {
- // Detect consumer
- $consumer = Consumer\Base::detectConsumer();
- if (!$consumer) {
- return null;
- }
-
- $this->user = $consumer->getUser();
-
- // Set authentication if present
- if ($this->user) {
- // Skip fake authentication if user is already logged in
- if ($GLOBALS['user']->id !== $this->user->id) {
-
- $GLOBALS['auth'] = new \Seminar_Auth();
- $GLOBALS['auth']->auth = [
- 'uid' => $this->user->user_id,
- 'uname' => $this->user->username,
- 'perm' => $this->user->perms,
- ];
-
- $GLOBALS['user'] = new \Seminar_User($this->user);
-
- $GLOBALS['perm'] = new \Seminar_Perm();
- $GLOBALS['MAIL_VALIDATE_BOX'] = false;
- }
- setTempLanguage($GLOBALS['user']->id);
- }
-
- return $this->user;
- }
-}
diff --git a/lib/classes/restapi/RouterException.php b/lib/classes/restapi/RouterException.php
deleted file mode 100644
index 1ce2afc..0000000
--- a/lib/classes/restapi/RouterException.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-namespace RESTAPI;
-use \Exception;
-
-/**
- * Router exception.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class RouterException extends Exception
-{
- protected static $error_messages = [
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 500 => 'Internal Server Error',
- 501 => 'Not implemented',
- ];
-
- public function __construct($code = 500, $message = '', $previous = null)
- {
- $message = $message ?: self::$error_messages[$code] ?: '';
- parent::__construct($message, $code, $previous);
- }
-}
diff --git a/lib/classes/restapi/RouterHalt.php b/lib/classes/restapi/RouterHalt.php
deleted file mode 100644
index 55a2ca1..0000000
--- a/lib/classes/restapi/RouterHalt.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-namespace RESTAPI;
-
-/**
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class RouterHalt extends \Exception
-{
- public $response;
-
- public function __construct($response)
- {
- parent::__construct();
- $this->response = $response;
- }
-}
diff --git a/lib/classes/restapi/UriTemplate.php b/lib/classes/restapi/UriTemplate.php
deleted file mode 100644
index 67161de..0000000
--- a/lib/classes/restapi/UriTemplate.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-namespace RESTAPI;
-
-/**
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class UriTemplate
-{
- public $uri_template;
- public $conditions;
-
- public function __construct($uri_template, $conditions = [])
- {
- $this->uri_template = $uri_template;
- $this->conditions = $conditions;
- }
-
- /**
- * Tests whether an uri matches a template.
- *
- * The template may contain placeholders by prefixing an appropriate,
- * unique placeholder name with a colon (:).
- *
- * <code>$template = '/hello/:name';</code>
- *
- * If the uri matches the template, all evaluated placeholders will
- * be stored in the parameters array.
- *
- * @param String $uri The uri to test
- * @param array $parameters Stores evaluated parameters on match (optional)
- *
- * @return bool Returns true if the uri matches the template
- */
- public function match($uri, &$parameters = null)
- {
- // Initialize parameters array
- $parameters = [];
-
- // Split and normalize uri and template
- $given = array_filter(explode('/', $uri), 'mb_strlen');
- $rules = array_filter(explode('/', $this->uri_template));
-
- // Leave if uri and template do not contain the same number of
- // elements
- if (count($given) !== count($rules)) {
- return false;
- }
-
- // Combine uri and template element-wise (simplifies iteration)
- $combined = array_combine($rules, $given);
-
- // Iterate over uri and template and compare element by element
- foreach ($combined as $rule => $actual) {
- if ($rule[0] === ':') {
- // Rule is a placeholder
- $parameter_name = mb_substr($rule, 1);
-
- if (isset($this->conditions[$parameter_name])
- && !preg_match($this->conditions[$parameter_name], $actual)) {
- return false;
- }
-
- $parameters[$parameter_name] = $actual;
-
- } elseif ($actual !== $rule) {
- // Elements do not match
- $parameters = [];
- return false;
- }
- }
-
- return true;
- }
-
-
- public function inject($params)
- {
- // Initialize parameters array
- $parameters = [];
-
- // Split and normalize template
- $rules = array_filter(explode('/', $this->uri_template));
-
- foreach ($rules as &$rule) {
-
- // Rule is a placeholder
- if ($rule[0] === ':') {
- $parameter_name = mb_substr($rule, 1);
-
- if (!isset($params[$parameter_name])) {
- $reason = sprintf('UriTemplate parameter :%s missing.',
- htmlReady($parameter_name));
- throw new \RuntimeException($reason);
- }
-
- $actual = $params[$parameter_name];
-
- if (isset($this->conditions[$parameter_name])
- && !preg_match($this->conditions[$parameter_name], $actual)) {
- $reason = sprintf('UriTemplate parameter :%s did not satisfy its condition.',
- htmlReady($parameter_name));
- throw new \RuntimeException($reason);
- }
-
- $rule = htmlReady($actual);
- }
- }
-
- return join('/', $rules);
- }
-}
diff --git a/lib/classes/restapi/UserPermissions.php b/lib/classes/restapi/UserPermissions.php
deleted file mode 100644
index dcf1601..0000000
--- a/lib/classes/restapi/UserPermissions.php
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-namespace RESTAPI;
-use DBManager, PDO;
-
-/**
- * REST API routing permissions
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license GPL 2 or later
- * @since Stud.IP 2.6
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class UserPermissions
-{
- /**
- * Create a permission object (for a certain user).
- * Permissions object will be cached for each user.
- *
- * @param mixed $user_id Id of user (optional, defaults to global)
- * @return UserPermissions Returns permissions object
- */
- public static function get($user_id = null)
- {
- $user_id = $user_id ?: $GLOBALS['user']->id;
-
- static $cache = [];
- if (!isset($cache[$user_id])) {
- $cache[$user_id] = new self($user_id);
- }
-
- return $cache[$user_id];
- }
-
- private $user_id;
- private $permissions = [];
-
- /**
- * Creates the actual permission object (for a certain user).
- *
- * @param mixed $user_id Id of user (optional, defaults to global)
- */
- private function __construct($user_id = null)
- {
- $this->user_id = $user_id;
-
- // Init with global permissions
- $this->loadPermissions();
- }
-
- /**
- * Defines whether access is allowed for the current user to the
- * passed route via the passed method.
- *
- * @param String $user_id Id of the user
- * @param mixed $granted Granted state (PHP'ish boolean)
- * @return UserPermissions Returns instance of self to allow chaining
- */
- public function set($user_id, $granted = true)
- {
- $this->permissions[$user_id] = (bool)$granted;
-
- return $this;
- }
-
- /**
- * Loads permissions for passed user.
- *
- * @return UserPermissions Returns instance of self to allow chaining
- */
- protected function loadPermissions()
- {
- $query = "SELECT consumer_id, granted
- FROM api_user_permissions
- WHERE user_id = :user_id";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':user_id', $this->user_id);
- $statement->execute();
- $permissions = $statement->fetchAll(PDO::FETCH_ASSOC);
-
- // Init with global permissions
- foreach ($permissions as $permission) {
- extract($permission);
-
- $this->set($permission['consumer_id'], $permission['granted']);
- }
-
- return $this;
- }
-
- /**
- * Checks if access to consumer is allowed for the current user.
- *
- * @param String $consumer_id Id of the consumer
- * @return bool Indicates whether access is allowed
- */
- public function check($consumer_id)
- {
- return isset($this->permissions[$consumer_id])
- && $this->permissions[$consumer_id];
- }
-
- /**
- * Stores the set permissions.
- *
- * @return bool Returns true if permissions were stored successfully
- */
- public function store()
- {
- $result = true;
-
- $query = "INSERT INTO api_user_permissions (user_id, consumer_id, granted, mkdate, chdate)
- VALUES (:user_id, :consumer_id, :granted, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())
- ON DUPLICATE KEY UPDATE granted = VALUES(granted),
- chdate = UNIX_TIMESTAMP()";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':user_id', $this->user_id);
-
- foreach ($this->permissions as $consumer_id => $granted) {
- $statement->bindValue(':consumer_id', $consumer_id);
- $statement->bindValue(':granted', (int) !empty($granted));
-
- $result = $result && $statement->execute();
- }
-
- return $result;
- }
-
- /**
- * Get a list of all consumer the user has granted acces to.
- *
- * @return array List of consumer objects
- */
- public function getConsumers()
- {
- $result = [];
- foreach ($this->permissions as $consumer_id => $granted) {
- if (!$granted) {
- continue;
- }
- $result[$consumer_id] = Consumer\Base::find($consumer_id);
- }
- return $result;
- }
-}
diff --git a/lib/classes/restapi/consumer/Base.php b/lib/classes/restapi/consumer/Base.php
deleted file mode 100644
index 50f3150..0000000
--- a/lib/classes/restapi/consumer/Base.php
+++ /dev/null
@@ -1,226 +0,0 @@
-<?php
-namespace RESTAPI\Consumer;
-
-use AuthUserMd5;
-use DBManager;
-use DBManagerException;
-use PDO;
-
-/**
- * Base consumer class for the rest api
- *
- * Consumers provide means for authenticating a user and the access
- * permissions for routes are bound to specific consumers.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-abstract class Base extends \SimpleORMap
-{
- /**
- * Each consumer type has to implement a detect feature which
- * should extract crucial information from the request and return
- * an instance of itself if the consumer detects a valid signature
- * it can respond to.
- *
- * @param mixed $request_type Type of request (optional; defaults to any)
- * @return mixed Detected consumer object or false
- */
- abstract public static function detect($request_type = null);
-
- /* Concrete */
-
- /**
- * Configures the model.
- *
- * @param array $config Configuration array
- */
- protected static function configure($config = [])
- {
- $config['db_table'] = 'api_consumers';
-
- parent::configure($config);
- }
-
- /**
- * Stores all known consumer types
- */
- protected static $known_types = [];
-
- /**
- * Add a consumer type to the list of consumer types
- *
- * @param String $type Name of the type
- * @param String $class Associated consumer class
- */
- public static function addType($type, $class)
- {
- self::$known_types[$type] = $class;
- }
-
- /**
- * Removes a consumer type from the list of consumer types
- *
- * @param String $type Name of the type
- */
- public static function removeType($type)
- {
- unset(self::$known_types[$type]);
- }
-
- /**
- * Overloaded find method. Will return a concrete specialized consumer
- * object of the associated type.
- *
- * @param String $id Id of the consumer
- * @return \RESTAPI\Consumer\Base Associated consumer object (derived
- * from consumer base type)
- * @throws \Exception if either consumer id or consumer type is invalid
- */
- public static function find($id)
- {
- $query = "SELECT consumer_type
- FROM api_consumers
- WHERE consumer_id = :id";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':id', $id);
- $statement->execute();
- $type = $statement->fetchColumn();
-
- if (!isset(self::$known_types[$type])) {
- throw new \Exception('Consumer #' . $id . ' is of unknown type "' . $type . '"');
- }
-
- return new self::$known_types[$type]($id);
- }
-
- /**
- * Returns a list of all known consumers.
- *
- * @return array List of all known consumers (as specialized consumer
- * objects)
- */
- public static function findAll()
- {
- $query = "SELECT consumer_id FROM api_consumers";
- $statement = DBManager::get()->query($query);
- $ids = $statement->fetchAll(PDO::FETCH_COLUMN);
-
- return array_map([self::class, 'find'], $ids);
- }
-
- /**
- * Creates a new consumer of the given type.
- *
- * @param String $type Name of the type
- * @return \RESTAPI\Consumer\Base Consumer object of the given (derived
- * from consumer base type)
- * @throws \Exception if type is invalid
- */
- public static function create($type)
- {
- if (!isset(self::$known_types[$type])) {
- throw new \Exception('Consumer is of unknown type "' . $type . '"');
- }
-
- return new self::$known_types[$type];
- }
-
- /**
- * This method is used to detect a consumer (of a specific type) by
- * executing the detect method on all known consumer types.
- *
- * @param mixed $type Name of the type (optional; defaults to all types)
- * @param mixed $request_type Type of request (optional; defaults to any)
- * @return mixed Either the detected consumer or false if no consumer
- * was detected
- * @throws \Exception if type is invalid
- */
- public static function detectConsumer($type = null, $request_type = null)
- {
- $needles = $type === null
- ? array_keys(self::$known_types)
- : [$type];
- foreach ($needles as $needle) {
- if (!isset(self::$known_types)) {
- throw new \Exception('Trying to detect consumer of unkown type "' . $needle . '"');
- }
- $consumer_class = self::$known_types[$needle];
- if ($consumer = $consumer_class::detect($request_type)) {
- return $consumer;
- }
- }
- return false;
- }
-
- /**
- * Contains user information
- */
- protected $user = null;
-
- /**
- * Extended SimpleORMap constructor. A certain user can be injected upon
- * creation.
- *
- * @param mixed $id Id of the consumer or null to create a new one
- * @param mixed $user Either a user object or id to inject to the consumer
- * or null if no user should be injected
- */
- public function __construct($id = null, $user = null)
- {
- parent::__construct($id);
-
- if ($user !== null) {
- $this->setUser($user);
- }
- }
-
- /**
- * Retrieve the api permissions associated with this consumer.
- *
- * @return \RESTAPI\ConsumerPermissions Permission object for this consumer
- */
- public function getPermissions()
- {
- return \RESTAPI\ConsumerPermissions::get($this->id);
- }
-
- /**
- * Inject a user to this consumer. Injecting in this context refers to
- * "having a user authenticated by this consumer".
- *
- * @param mixed $user Either a user object or a user id
- * @return \RESTAPI\Consumer\Base Returns instance of self to allow
- * chaining
- */
- public function setUser($user)
- {
- if (!is_object($user)) {
- $user = \User::findFull($user);
- }
- $this->user = $user;
- return $this;
- }
-
- /**
- * Returns whether the consumer has an injected user or not.
- *
- * @return bool True if a valid user is found, false otherwise
- */
- public function hasUser()
- {
- return $this->user !== null && $this->user->id && $this->user->id !== 'nobody';
- }
-
- /**
- * Return the injected user.
- *
- * @param mixed User object or false if no user was injected
- */
- public function getUser()
- {
- return $this->user;
- }
-}
diff --git a/lib/classes/restapi/consumer/HTTP.php b/lib/classes/restapi/consumer/HTTP.php
deleted file mode 100644
index 97b0657..0000000
--- a/lib/classes/restapi/consumer/HTTP.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-namespace RESTAPI\Consumer;
-use StudipAuthAbstract, RESTAPI\RouterException;
-
-/**
- * Basic HTTP Authentication consumer for the rest api
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class HTTP extends Base
-{
- /**
- * Detects if a user is authenticated via basic http authentication.
- * The only supported authentication for now is via the url:
- *
- * http://username:password@host/path?query
- *
- * @param mixed $request_type Type of request (optional; defaults to any)
- * @return mixed Instance of self if authentication was detected, false
- * otherwise
- * @throws RouterException if authentication fails
- * @todo Integrate and test HTTP_AUTHORIZATION header authentication
- */
- public static function detect($request_type = null)
- {
- if (
- isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])
- || isset($_SERVER['HTTP_AUTHORIZATION'])
- ) {
- $user_id = false;
-
- if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
- $username = $_SERVER['PHP_AUTH_USER'];
- $password = $_SERVER['PHP_AUTH_PW'];
- } elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
- list($username, $password) = explode(':', base64_decode(mb_substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
- }
-
- $check = StudipAuthAbstract::CheckAuthentication($username, $password);
- if ($check['uid'] && $check['uid'] !== 'nobody') {
- return new self(null, $check['uid']);
- }
-
- }
- return false;
- }
-}
diff --git a/lib/classes/restapi/consumer/OAuth.php b/lib/classes/restapi/consumer/OAuth.php
deleted file mode 100644
index caf51c2..0000000
--- a/lib/classes/restapi/consumer/OAuth.php
+++ /dev/null
@@ -1,231 +0,0 @@
-<?php
-namespace RESTAPI\Consumer;
-use StudipAutoloader, DBManager, OAuthRequestVerifier, OAuthStore, OAuthServer, Exception;
-use \RESTAPI\UserPermissions;
-
-StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR . 'vendor/oauth-php/library/');
-
-/**
- * OAuth consumer for the rest api
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class OAuth extends Base
-{
- /**
- * Configures the model.
- *
- * @param array $config Configuration array
- */
- protected static function configure($config = [])
- {
- $config['default_values']['consumer_type'] = 'oauth';
-
- $config['registered_callbacks']['before_store'][] = 'before_store';
-
- parent::configure($config);
- }
-
- /**
- * Detects whether the request is authenticated via OAuth.
- *
- * @param mixed $request_type Type of request (optional; defaults to any)
- * @return mixed Instance of self if authentication was detected, false
- * otherwise
- */
- public static function detect($request_type = null)
- {
- if (OAuthRequestVerifier::requestIsSigned() && $request_type !== 'request') {
- $user_id = false;
-
- $parameters = (in_array($_SERVER['REQUEST_METHOD'], ['GET', 'POST']))
- ? null
- : $GLOBALS['_' . $_SERVER['REQUEST_METHOD']];
-
- $req = new OAuthRequestVerifier(null, null, $parameters);
-
- // Check oauth timestamp and deny access if timestamp is outdated
- if ($req->getParam('oauth_timestamp') < strtotime('-6 hours')) {
- return false;
- }
- $result = $req->verifyExtended('access');
-
- // @todo
- # self::$consumer_key = $result['consumer_key'];
-
- $query = "SELECT user_id FROM api_oauth_user_mapping WHERE oauth_id = :oauth_id";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':oauth_id', $result['user_id']);
- $statement->execute();
- $user_id = $statement->fetchColumn();
-
- if (!$user_id) {
- return false;
- }
-
- $consumer = reset(self::findByAuth_Key($result['consumer_key']));
- $consumer->setUser($user_id);
- return $consumer;
- } else {
- try {
- // Check if there is a valid request token in the current request
- // Returns an array with the consumer key, consumer secret, token, token secret and token type.
- $rs = self::getServer()->authorizeVerify();
-
- $query = "SELECT consumer_id
- FROM api_consumers
- WHERE auth_key = :key";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':key', $rs['consumer_key']);
- $statement->execute();
- $id = $statement->fetchColumn();
-
- if ($id) {
- return new self($id);
- }
- } catch (Exception $e) {
- }
- }
- return false;
- }
-
- /**
- * Returns a singleton instance of the oauth server.
- *
- * @return OAuthServer The server object
- */
- public static function getServer()
- {
- static $server = null;
- if ($server === null) {
- $server = new OAuthServer(null, null, null, 'SESSION', [], [
- 'allowed_uri_schemes' => []
- ]);
- }
- return $server;
- }
-
- /**
- * "Before store" trigger. Creates a clone of the consumer in the
- * tables for the vendor oauth library.
- */
- protected function before_store()
- {
- static $mapping = [
- 'auth_key' => 'consumer_key',
- 'auth_secret' => 'consumer_secret',
- 'active' => 'enabled',
- 'contact' => 'requester_name',
- 'email' => 'requester_email',
- 'callback' => 'callback_uri',
- 'url' => 'application_uri',
- 'title' => 'application_title',
- 'description' => 'application_descr',
- 'notes' => 'application_notes',
- 'type' => 'application_type',
- 'commercial' => 'application_commercial',
- ];
-
- $consumer = [];
- foreach ($mapping as $from => $to) {
- $consumer[$to] = $this->$from;
- }
-
- $query = "SELECT osr_id
- FROM oauth_server_registry
- WHERE osr_consumer_key = :key AND osr_consumer_secret = :secret";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':key', $this->auth_key);
- $statement->bindValue(':secret', $this->auth_secret);
- $statement->execute();
- $consumer['id'] = $statement->fetchColumn();
-
- $consumer_key = OAuthStore::instance('PDO')->updateConsumer($consumer, null, true);
-
- if ($this->isNew()) {
- $consumer = OAuthStore::instance('PDO')->getConsumer($consumer_key, null, true);
- $this->auth_key = $consumer['consumer_key'];
- $this->auth_secret = $consumer['consumer_secret'];
- }
- }
-
- /**
- * Grant oauth access for a user.
- *
- * @param mixed $user_id Specific user id or null to default to the
- * injected user
- * @throws Exception If no valid user is present
- */
- public function grantAccess($user_id = null)
- {
- if ($user_id === null && $this->hasUser()) {
- $user_id = $this->user->id;
- }
- if (!$user_id) {
- throw new Exception('Can not grant access to unknown user');
- }
-
- UserPermissions::get($GLOBALS['user']->id)->set($this->id, true)->store();
- return self::getServer()->authorizeFinish(true, self::getOAuthId($user_id));
- }
-
- /**
- * Revoke oauth access from a user.
- *
- * @param mixed $user_id Specific user id or null to default to the
- * injected user
- * @throws Exception If no valid user is present
- */
- public function revokeAccess($user_id = null)
- {
- if ($user_id === null && $this->hasUser()) {
- $user_id = $this->user->id;
- }
- if (!$user_id) {
- throw new Exception('Can not revoke access from unknown user');
- }
-
- $query = "DELETE oauth_server_token
- FROM oauth_server_token
- JOIN oauth_server_registry
- WHERE ost_usa_id_ref = :id AND osr_consumer_key = :key AND osr_consumer_secret = :secret";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':id', self::getOAuthId($user_id));
- $statement->bindValue(':key', $this->auth_key);
- $statement->bindValue(':secret', $this->auth_secret);
- $statement->execute();
-
- UserPermissions::get($GLOBALS['user']->id)->set($this->id, false)->store();
- return self::getServer()->authorizeFinish(false, self::getOAuthId($user_id));
- }
-
- /**
- * Maps a user to an oauth id. This is neccessary due to the fact that
- * the oauth lib works with different ids than Stud.IP.
- *
- * @param String $user_id Id of the user to get an oauth id for
- * @return String The mapped oauth id
- */
- public static function getOAuthId($user_id)
- {
- $query = "SELECT oauth_id FROM api_oauth_user_mapping WHERE user_id = :id";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':id', $user_id);
- $statement->execute();
- $oauth_id = $statement->fetchColumn();
-
- if (!$oauth_id) {
- $query = "INSERT INTO api_oauth_user_mapping (user_id, mkdate)
- VALUES (:id, UNIX_TIMESTAMP())";
- $statement = DBManager::get()->prepare($query);
- $statement->bindValue(':id', $user_id);
- $statement->execute();
- $oauth_id = DBManager::get()->lastInsertId();
- }
-
- return $oauth_id;
- }
-}
diff --git a/lib/classes/restapi/consumer/Studip.php b/lib/classes/restapi/consumer/Studip.php
deleted file mode 100644
index 738dd75..0000000
--- a/lib/classes/restapi/consumer/Studip.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-namespace RESTAPI\Consumer;
-
-/**
- * Stud.IP Session Consumer for the rest api
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class Studip extends Base
-{
- /**
- * Detects a user via the Stud.IP session. If a session is present and
- * valid, the auth and user object have already been set up by stud.ip
- * functions, so we just need to check if these are present.
- *
- * @param mixed $request_type Type of request (optional; defaults to any)
- * @return mixed Instance of self if authentication was detected, false
- * otherwise
- */
- public static function detect($request_type = null)
- {
- if (
- !isset($GLOBALS['auth'])
- || !$GLOBALS['auth']->is_authenticated()
- || $GLOBALS['user']->id === 'nobody'
- || !\CSRFProtection::verifyRequest()
- ) {
- return false;
- }
-
- return new self(null, $GLOBALS['user']->id);
- }
-}
diff --git a/lib/classes/restapi/renderer/DebugRenderer.php b/lib/classes/restapi/renderer/DebugRenderer.php
deleted file mode 100644
index afd56f6..0000000
--- a/lib/classes/restapi/renderer/DebugRenderer.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-namespace RESTAPI\Renderer;
-
-/**
- * Debug content renderer.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class DebugRenderer extends DefaultRenderer
-{
- /**
- * Returns an associated content type.
- */
- public function contentType()
- {
- return 'text/plain';
- }
-
- /**
- * Returns an associated extension.
- */
- public function extension()
- {
- return '.debug';
- }
-
- /**
- * Response transformation function.
- *
- * @param \RESTAPI\Response $response the response to transform
- */
- public function render($response)
- {
- if (!isset($response['Content-Type'])) {
- $response['Content-Type'] = $this->contentType() . ';charset=utf-8';
- }
-
- $debug = function ($label, $data) {
- echo str_pad('', 78, '=') . PHP_EOL;
- echo str_pad('- ' . $label, 77, ' ') . '-' . PHP_EOL;
- echo str_pad('', 78, '=') . PHP_EOL;
- var_export($data);
- echo PHP_EOL;
- };
-
- ob_start();
- $debug('Response Status', $response->status);
- $debug('Response Header', $response->headers);
- $debug('Response Body', $response->body);
- $debug('Request', $GLOBALS['_' . $_SERVER['REQUEST_METHOD']]);
- $response->body = ob_get_clean();
- }
-}
diff --git a/lib/classes/restapi/renderer/DefaultRenderer.php b/lib/classes/restapi/renderer/DefaultRenderer.php
deleted file mode 100644
index 836ba36..0000000
--- a/lib/classes/restapi/renderer/DefaultRenderer.php
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-namespace RESTAPI\Renderer;
-
-/**
- * Default base content renderer class (outputs text/plain).
- *
- * Content renderers are output filters that can reshape data before it
- * is sent to the client.
- * Each content renderer is associated with a certain content type and a
- * certain file extension. This is neccessary for content negotiation.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class DefaultRenderer
-{
- /**
- * Returns an associated content type.
- *
- * @return String Content/mime type for this renderer
- */
- public function contentType()
- {
- return 'text/plain';
- }
-
- /**
- * Returns an associated extension.
- *
- * @return String Associated extension for this renderer.
- */
- public function extension()
- {
- return '';
- }
-
- /**
- * Response transformation function.
- *
- * @param \RESTAPI\Response $response the response to transform
- */
- public function render($response)
- {
- if (!isset($response['Content-Type'])) {
- $response['Content-Type'] = $this->contentType() . ';charset=utf-8';
- }
- }
-
- /**
- * Detects whether the renderer should respond to either a certain
- * filename (tests by extension) or to a certain media range.
- *
- * @param String $filename Filename to test against
- * @param mixed $media_range Media range to test against (optional,
- * defaults to request's accept header if set)
- * @return bool Returns whether the renderer should respond
- */
- public function shouldRespondTo($filename, $media_range = null)
- {
- // If no media range is passed, evalute http header "Accept"
- if ($media_range === null && isset($_SERVER['ACCEPT'])) {
- $media_ranges = explode(';', $_SERVER['ACCEPT']);
- $media_range = reset($media_ranges);
- }
-
- // Test if either the filename has the appropriate extension or
- // if the client accepts the content type
- return ($this->extension() && fnmatch('*' . $this->extension(), $filename))
- || ($media_range && fnmatch($media_range, $this->contentType()));
- }
-}
diff --git a/lib/classes/restapi/renderer/JSONRenderer.php b/lib/classes/restapi/renderer/JSONRenderer.php
deleted file mode 100644
index 9c6e449..0000000
--- a/lib/classes/restapi/renderer/JSONRenderer.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-namespace RESTAPI\Renderer;
-
-/**
- * Content renderer for json content.
- *
- * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
- * @author <mlunzena@uos.de>
- * @license GPL 2 or later
- * @since Stud.IP 3.0
- * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 6.0.
- */
-class JSONRenderer extends DefaultRenderer
-{
- public function contentType()
- {
- return 'application/json';
- }
-
- public function extension()
- {
- return '.json';
- }
-
- public function render($response)
- {
- if (!isset($response['Content-Type'])) {
- $response['Content-Type'] = $this->contentType() . ';charset=utf-8';
- }
-
- if (isset($response->body)) {
- $response->body = json_encode($response->body);
- }
- }
-}