summaryrefslogtreecommitdiffstats
path: root/lib/private/legacy/api.php
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@owncloud.com>2016-04-30 10:45:19 +0200
committerRoeland Jago Douma <rullzer@owncloud.com>2016-04-30 11:32:22 +0200
commit368be8894cebd8e730f2ef7994d0b56bdc0f47e2 (patch)
treee4153727428d58cdbf82173cc3132bc30dd7e4e2 /lib/private/legacy/api.php
parent023c8b0eacc243b2dd4763e6086fc87ce8b76126 (diff)
downloadnextcloud-server-368be8894cebd8e730f2ef7994d0b56bdc0f47e2.tar.gz
nextcloud-server-368be8894cebd8e730f2ef7994d0b56bdc0f47e2.zip
Move non PSR-4 files from lib/private root to legacy
As discussed we move all old style classes (OC_FOO_BAR) to legacy. Then from there we can evaluate the need to convert them back or if they can be fully deprecated/deleted.
Diffstat (limited to 'lib/private/legacy/api.php')
-rw-r--r--lib/private/legacy/api.php533
1 files changed, 533 insertions, 0 deletions
diff --git a/lib/private/legacy/api.php b/lib/private/legacy/api.php
new file mode 100644
index 00000000000..bab879c95f8
--- /dev/null
+++ b/lib/private/legacy/api.php
@@ -0,0 +1,533 @@
+<?php
+/**
+ * @author Bart Visscher <bartv@thisnet.nl>
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Björn Schießle <schiessle@owncloud.com>
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Michael Gapczynski <GapczynskiM@gmail.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Tom Needham <tom@owncloud.com>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+use OCP\API;
+use OCP\AppFramework\Http;
+
+/**
+ * @author Bart Visscher <bartv@thisnet.nl>
+ * @author Bernhard Posselt <dev@bernhard-posselt.com>
+ * @author Björn Schießle <schiessle@owncloud.com>
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Michael Gapczynski <GapczynskiM@gmail.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Tom Needham <tom@owncloud.com>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+class OC_API {
+
+ /**
+ * API authentication levels
+ */
+
+ /** @deprecated Use \OCP\API::GUEST_AUTH instead */
+ const GUEST_AUTH = 0;
+
+ /** @deprecated Use \OCP\API::USER_AUTH instead */
+ const USER_AUTH = 1;
+
+ /** @deprecated Use \OCP\API::SUBADMIN_AUTH instead */
+ const SUBADMIN_AUTH = 2;
+
+ /** @deprecated Use \OCP\API::ADMIN_AUTH instead */
+ const ADMIN_AUTH = 3;
+
+ /**
+ * API Response Codes
+ */
+
+ /** @deprecated Use \OCP\API::RESPOND_UNAUTHORISED instead */
+ const RESPOND_UNAUTHORISED = 997;
+
+ /** @deprecated Use \OCP\API::RESPOND_SERVER_ERROR instead */
+ const RESPOND_SERVER_ERROR = 996;
+
+ /** @deprecated Use \OCP\API::RESPOND_NOT_FOUND instead */
+ const RESPOND_NOT_FOUND = 998;
+
+ /** @deprecated Use \OCP\API::RESPOND_UNKNOWN_ERROR instead */
+ const RESPOND_UNKNOWN_ERROR = 999;
+
+ /**
+ * api actions
+ */
+ protected static $actions = array();
+ private static $logoutRequired = false;
+ private static $isLoggedIn = false;
+
+ /**
+ * registers an api call
+ * @param string $method the http method
+ * @param string $url the url to match
+ * @param callable $action the function to run
+ * @param string $app the id of the app registering the call
+ * @param int $authLevel the level of authentication required for the call
+ * @param array $defaults
+ * @param array $requirements
+ */
+ public static function register($method, $url, $action, $app,
+ $authLevel = API::USER_AUTH,
+ $defaults = array(),
+ $requirements = array()) {
+ $name = strtolower($method).$url;
+ $name = str_replace(array('/', '{', '}'), '_', $name);
+ if(!isset(self::$actions[$name])) {
+ $oldCollection = OC::$server->getRouter()->getCurrentCollection();
+ OC::$server->getRouter()->useCollection('ocs');
+ OC::$server->getRouter()->create($name, $url)
+ ->method($method)
+ ->defaults($defaults)
+ ->requirements($requirements)
+ ->action('OC_API', 'call');
+ self::$actions[$name] = array();
+ OC::$server->getRouter()->useCollection($oldCollection);
+ }
+ self::$actions[$name][] = array('app' => $app, 'action' => $action, 'authlevel' => $authLevel);
+ }
+
+ /**
+ * handles an api call
+ * @param array $parameters
+ */
+ public static function call($parameters) {
+ $request = \OC::$server->getRequest();
+ $method = $request->getMethod();
+
+ // Prepare the request variables
+ if($method === 'PUT') {
+ $parameters['_put'] = $request->getParams();
+ } else if($method === 'DELETE') {
+ $parameters['_delete'] = $request->getParams();
+ }
+ $name = $parameters['_route'];
+ // Foreach registered action
+ $responses = array();
+ foreach(self::$actions[$name] as $action) {
+ // Check authentication and availability
+ if(!self::isAuthorised($action)) {
+ $responses[] = array(
+ 'app' => $action['app'],
+ 'response' => new OC_OCS_Result(null, API::RESPOND_UNAUTHORISED, 'Unauthorised'),
+ 'shipped' => OC_App::isShipped($action['app']),
+ );
+ continue;
+ }
+ if(!is_callable($action['action'])) {
+ $responses[] = array(
+ 'app' => $action['app'],
+ 'response' => new OC_OCS_Result(null, API::RESPOND_NOT_FOUND, 'Api method not found'),
+ 'shipped' => OC_App::isShipped($action['app']),
+ );
+ continue;
+ }
+ // Run the action
+ $responses[] = array(
+ 'app' => $action['app'],
+ 'response' => call_user_func($action['action'], $parameters),
+ 'shipped' => OC_App::isShipped($action['app']),
+ );
+ }
+ $response = self::mergeResponses($responses);
+ $format = self::requestedFormat();
+ if (self::$logoutRequired) {
+ \OC::$server->getUserSession()->logout();
+ }
+
+ self::respond($response, $format);
+ }
+
+ /**
+ * merge the returned result objects into one response
+ * @param array $responses
+ * @return OC_OCS_Result
+ */
+ public static function mergeResponses($responses) {
+ // Sort into shipped and third-party
+ $shipped = array(
+ 'succeeded' => array(),
+ 'failed' => array(),
+ );
+ $thirdparty = array(
+ 'succeeded' => array(),
+ 'failed' => array(),
+ );
+
+ foreach($responses as $response) {
+ if($response['shipped'] || ($response['app'] === 'core')) {
+ if($response['response']->succeeded()) {
+ $shipped['succeeded'][$response['app']] = $response;
+ } else {
+ $shipped['failed'][$response['app']] = $response;
+ }
+ } else {
+ if($response['response']->succeeded()) {
+ $thirdparty['succeeded'][$response['app']] = $response;
+ } else {
+ $thirdparty['failed'][$response['app']] = $response;
+ }
+ }
+ }
+
+ // Remove any error responses if there is one shipped response that succeeded
+ if(!empty($shipped['failed'])) {
+ // Which shipped response do we use if they all failed?
+ // They may have failed for different reasons (different status codes)
+ // Which response code should we return?
+ // Maybe any that are not \OCP\API::RESPOND_SERVER_ERROR
+ // Merge failed responses if more than one
+ $data = array();
+ foreach($shipped['failed'] as $failure) {
+ $data = array_merge_recursive($data, $failure['response']->getData());
+ }
+ $picked = reset($shipped['failed']);
+ $code = $picked['response']->getStatusCode();
+ $meta = $picked['response']->getMeta();
+ $headers = $picked['response']->getHeaders();
+ $response = new OC_OCS_Result($data, $code, $meta['message'], $headers);
+ return $response;
+ } elseif(!empty($shipped['succeeded'])) {
+ $responses = array_merge($shipped['succeeded'], $thirdparty['succeeded']);
+ } elseif(!empty($thirdparty['failed'])) {
+ // Merge failed responses if more than one
+ $data = array();
+ foreach($thirdparty['failed'] as $failure) {
+ $data = array_merge_recursive($data, $failure['response']->getData());
+ }
+ $picked = reset($thirdparty['failed']);
+ $code = $picked['response']->getStatusCode();
+ $meta = $picked['response']->getMeta();
+ $headers = $picked['response']->getHeaders();
+ $response = new OC_OCS_Result($data, $code, $meta['message'], $headers);
+ return $response;
+ } else {
+ $responses = $thirdparty['succeeded'];
+ }
+ // Merge the successful responses
+ $data = [];
+ $codes = [];
+ $header = [];
+
+ foreach($responses as $response) {
+ if($response['shipped']) {
+ $data = array_merge_recursive($response['response']->getData(), $data);
+ } else {
+ $data = array_merge_recursive($data, $response['response']->getData());
+ }
+ $header = array_merge_recursive($header, $response['response']->getHeaders());
+ $codes[] = ['code' => $response['response']->getStatusCode(),
+ 'meta' => $response['response']->getMeta()];
+ }
+
+ // Use any non 100 status codes
+ $statusCode = 100;
+ $statusMessage = null;
+ foreach($codes as $code) {
+ if($code['code'] != 100) {
+ $statusCode = $code['code'];
+ $statusMessage = $code['meta']['message'];
+ break;
+ }
+ }
+
+ return new OC_OCS_Result($data, $statusCode, $statusMessage, $header);
+ }
+
+ /**
+ * authenticate the api call
+ * @param array $action the action details as supplied to OC_API::register()
+ * @return bool
+ */
+ private static function isAuthorised($action) {
+ $level = $action['authlevel'];
+ switch($level) {
+ case API::GUEST_AUTH:
+ // Anyone can access
+ return true;
+ case API::USER_AUTH:
+ // User required
+ return self::loginUser();
+ case API::SUBADMIN_AUTH:
+ // Check for subadmin
+ $user = self::loginUser();
+ if(!$user) {
+ return false;
+ } else {
+ $userObject = \OC::$server->getUserSession()->getUser();
+ if($userObject === null) {
+ return false;
+ }
+ $isSubAdmin = \OC::$server->getGroupManager()->getSubAdmin()->isSubAdmin($userObject);
+ $admin = OC_User::isAdminUser($user);
+ if($isSubAdmin || $admin) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ case API::ADMIN_AUTH:
+ // Check for admin
+ $user = self::loginUser();
+ if(!$user) {
+ return false;
+ } else {
+ return OC_User::isAdminUser($user);
+ }
+ default:
+ // oops looks like invalid level supplied
+ return false;
+ }
+ }
+
+ /**
+ * http basic auth
+ * @return string|false (username, or false on failure)
+ */
+ private static function loginUser() {
+ if(self::$isLoggedIn === true) {
+ return \OC_User::getUser();
+ }
+
+ // reuse existing login
+ $loggedIn = OC_User::isLoggedIn();
+ if ($loggedIn === true) {
+ $ocsApiRequest = isset($_SERVER['HTTP_OCS_APIREQUEST']) ? $_SERVER['HTTP_OCS_APIREQUEST'] === 'true' : false;
+ if ($ocsApiRequest) {
+
+ // initialize the user's filesystem
+ \OC_Util::setUpFS(\OC_User::getUser());
+ self::$isLoggedIn = true;
+
+ return OC_User::getUser();
+ }
+ return false;
+ }
+
+ // basic auth - because OC_User::login will create a new session we shall only try to login
+ // if user and pass are set
+ if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) ) {
+ $authUser = $_SERVER['PHP_AUTH_USER'];
+ $authPw = $_SERVER['PHP_AUTH_PW'];
+ $return = OC_User::login($authUser, $authPw);
+ if ($return === true) {
+ self::$logoutRequired = true;
+
+ // initialize the user's filesystem
+ \OC_Util::setUpFS(\OC_User::getUser());
+ self::$isLoggedIn = true;
+
+ /**
+ * Add DAV authenticated. This should in an ideal world not be
+ * necessary but the iOS App reads cookies from anywhere instead
+ * only the DAV endpoint.
+ * This makes sure that the cookies will be valid for the whole scope
+ * @see https://github.com/owncloud/core/issues/22893
+ */
+ \OC::$server->getSession()->set(
+ \OCA\DAV\Connector\Sabre\Auth::DAV_AUTHENTICATED,
+ \OC::$server->getUserSession()->getUser()->getUID()
+ );
+
+ return \OC_User::getUser();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * respond to a call
+ * @param OC_OCS_Result $result
+ * @param string $format the format xml|json
+ */
+ public static function respond($result, $format='xml') {
+ $request = \OC::$server->getRequest();
+
+ // Send 401 headers if unauthorised
+ if($result->getStatusCode() === API::RESPOND_UNAUTHORISED) {
+ // If request comes from JS return dummy auth request
+ if($request->getHeader('X-Requested-With') === 'XMLHttpRequest') {
+ header('WWW-Authenticate: DummyBasic realm="Authorisation Required"');
+ } else {
+ header('WWW-Authenticate: Basic realm="Authorisation Required"');
+ }
+ header('HTTP/1.0 401 Unauthorized');
+ }
+
+ foreach($result->getHeaders() as $name => $value) {
+ header($name . ': ' . $value);
+ }
+
+ $meta = $result->getMeta();
+ $data = $result->getData();
+ if (self::isV2($request)) {
+ $statusCode = self::mapStatusCodes($result->getStatusCode());
+ if (!is_null($statusCode)) {
+ $meta['statuscode'] = $statusCode;
+ OC_Response::setStatus($statusCode);
+ }
+ }
+
+ self::setContentType($format);
+ $body = self::renderResult($format, $meta, $data);
+ echo $body;
+ }
+
+ /**
+ * @param XMLWriter $writer
+ */
+ private static function toXML($array, $writer) {
+ foreach($array as $k => $v) {
+ if ($k[0] === '@') {
+ $writer->writeAttribute(substr($k, 1), $v);
+ continue;
+ } else if (is_numeric($k)) {
+ $k = 'element';
+ }
+ if(is_array($v)) {
+ $writer->startElement($k);
+ self::toXML($v, $writer);
+ $writer->endElement();
+ } else {
+ $writer->writeElement($k, $v);
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public static function requestedFormat() {
+ $formats = array('json', 'xml');
+
+ $format = !empty($_GET['format']) && in_array($_GET['format'], $formats) ? $_GET['format'] : 'xml';
+ return $format;
+ }
+
+ /**
+ * Based on the requested format the response content type is set
+ * @param string $format
+ */
+ public static function setContentType($format = null) {
+ $format = is_null($format) ? self::requestedFormat() : $format;
+ if ($format === 'xml') {
+ header('Content-type: text/xml; charset=UTF-8');
+ return;
+ }
+
+ if ($format === 'json') {
+ header('Content-Type: application/json; charset=utf-8');
+ return;
+ }
+
+ header('Content-Type: application/octet-stream; charset=utf-8');
+ }
+
+ /**
+ * @param \OCP\IRequest $request
+ * @return bool
+ */
+ protected static function isV2(\OCP\IRequest $request) {
+ $script = $request->getScriptName();
+
+ return substr($script, -11) === '/ocs/v2.php';
+ }
+
+ /**
+ * @param integer $sc
+ * @return int
+ */
+ public static function mapStatusCodes($sc) {
+ switch ($sc) {
+ case API::RESPOND_NOT_FOUND:
+ return Http::STATUS_NOT_FOUND;
+ case API::RESPOND_SERVER_ERROR:
+ return Http::STATUS_INTERNAL_SERVER_ERROR;
+ case API::RESPOND_UNKNOWN_ERROR:
+ return Http::STATUS_INTERNAL_SERVER_ERROR;
+ case API::RESPOND_UNAUTHORISED:
+ // already handled for v1
+ return null;
+ case 100:
+ return Http::STATUS_OK;
+ }
+ // any 2xx, 4xx and 5xx will be used as is
+ if ($sc >= 200 && $sc < 600) {
+ return $sc;
+ }
+
+ return Http::STATUS_BAD_REQUEST;
+ }
+
+ /**
+ * @param string $format
+ * @return string
+ */
+ public static function renderResult($format, $meta, $data) {
+ $response = array(
+ 'ocs' => array(
+ 'meta' => $meta,
+ 'data' => $data,
+ ),
+ );
+ if ($format == 'json') {
+ return OC_JSON::encode($response);
+ }
+
+ $writer = new XMLWriter();
+ $writer->openMemory();
+ $writer->setIndent(true);
+ $writer->startDocument();
+ self::toXML($response, $writer);
+ $writer->endDocument();
+ return $writer->outputMemory(true);
+ }
+}