|
|
@@ -37,44 +37,10 @@ use OCP\AppFramework\Http; |
|
|
|
|
|
|
|
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 |
|
|
@@ -106,249 +72,6 @@ class OC_API { |
|
|
|
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(); |
|
|
|
$appManager = \OC::$server->getAppManager(); |
|
|
|
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' => $appManager->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' => $appManager->isShipped($action['app']), |
|
|
|
); |
|
|
|
continue; |
|
|
|
} |
|
|
|
// Run the action |
|
|
|
$responses[] = array( |
|
|
|
'app' => $action['app'], |
|
|
|
'response' => call_user_func($action['action'], $parameters), |
|
|
|
'shipped' => $appManager->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::$server->getUserSession()->isLoggedIn(); |
|
|
|
if ($loggedIn === true) { |
|
|
|
if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor(\OC::$server->getUserSession()->getUser())) { |
|
|
|
// Do not allow access to OCS until the 2FA challenge was solved successfully |
|
|
|
return false; |
|
|
|
} |
|
|
|
$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 |
|
|
|
$userSession = \OC::$server->getUserSession(); |
|
|
|
$request = \OC::$server->getRequest(); |
|
|
|
try { |
|
|
|
if ($userSession->tryTokenLogin($request) |
|
|
|
|| $userSession->tryBasicAuthLogin($request, \OC::$server->getBruteForceThrottler())) { |
|
|
|
self::$logoutRequired = true; |
|
|
|
} else { |
|
|
|
return false; |
|
|
|
} |
|
|
|
// initialize the user's filesystem |
|
|
|
\OC_Util::setupFS(\OC_User::getUser()); |
|
|
|
self::$isLoggedIn = true; |
|
|
|
|
|
|
|
return \OC_User::getUser(); |
|
|
|
} catch (\OC\User\LoginException $e) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* respond to a call |
|
|
|
* @param \OC\OCS\Result $result |