Move over to Entity/EntityMapper

* Use the AppFrameworks entities
* Use the query builder
* Pass Entities (with types) around
* Remove old classes

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
Roeland Jago Douma 2018-03-27 13:33:33 +02:00
Родитель c6ce323857
Коммит 958d1fe279
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: F941078878347C0C
5 изменённых файлов: 239 добавлений и 319 удалений

Просмотреть файл

@ -22,10 +22,12 @@
namespace OCA\Richdocuments\Controller;
use OC\Files\View;
use OCA\Richdocuments\Db\WopiMapper;
use OCA\Richdocuments\TokenManager;
use OCA\Richdocuments\Db\Wopi;
use OCA\Richdocuments\Helper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\File;
@ -43,10 +45,12 @@ class WopiController extends Controller {
private $urlGenerator;
/** @var IConfig */
private $config;
/** @var ITokenManager */
/** @var TokenManager */
private $tokenManager;
/** @var IUserManager */
private $userManager;
/** @var WopiMapper */
private $wopiMapper;
// Signifies LOOL that document has been changed externally in this storage
const LOOL_STATUS_DOC_CHANGED = 1010;
@ -58,8 +62,9 @@ class WopiController extends Controller {
* @param IRootFolder $rootFolder
* @param IURLGenerator $urlGenerator
* @param IConfig $config
* @param ITokenManager $tokenManager
* @param TokenManager $tokenManager
* @param IUserManager $userManager
* @param WopiMapper $wopiMapper
*/
public function __construct($appName,
$UserId,
@ -68,13 +73,15 @@ class WopiController extends Controller {
IURLGenerator $urlGenerator,
IConfig $config,
TokenManager $tokenManager,
IUserManager $userManager) {
IUserManager $userManager,
WopiMapper $wopiMapper) {
parent::__construct($appName, $request);
$this->rootFolder = $rootFolder;
$this->urlGenerator = $urlGenerator;
$this->config = $config;
$this->tokenManager = $tokenManager;
$this->userManager = $userManager;
$this->wopiMapper = $wopiMapper;
}
/**
@ -91,16 +98,17 @@ class WopiController extends Controller {
$token = $this->request->getParam('access_token');
list($fileId, , $version) = Helper::parseFileId($fileId);
$db = new Wopi();
$res = $db->getPathForToken($fileId, $token);
if ($res === false) {
try {
$wopi = $this->wopiMapper->getPathForToken($token);
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
// Login the user to see his mount locations
try {
/** @var File $file */
$userFolder = $this->rootFolder->getUserFolder($res['owner']);
$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
$file = $userFolder->getById($fileId)[0];
} catch (\Exception $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
@ -114,23 +122,23 @@ class WopiController extends Controller {
'BaseFileName' => $file->getName(),
'Size' => $file->getSize(),
'Version' => $version,
'UserId' => !is_null($res['editor']) ? $res['editor'] : 'guest',
'OwnerId' => $res['owner'],
'UserFriendlyName' => !is_null($res['editor']) ? \OC_User::getDisplayName($res['editor']) : 'Guest user',
'UserId' => !is_null($wopi->getEditorUid()) ? $wopi->getEditorUid() : 'guest',
'OwnerId' => $wopi->getOwnerUid(),
'UserFriendlyName' => !is_null($wopi->getEditorUid()) ? \OC_User::getDisplayName($wopi->getEditorUid()) : 'Guest user',
'UserExtraInfo' => [
],
'UserCanWrite' => $res['canwrite'] ? true : false,
'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() ? true : is_null($res['editor']),
'PostMessageOrigin' => $res['server_host'],
'UserCanWrite' => $wopi->getCanwrite(),
'UserCanNotWriteRelative' => \OC::$server->getEncryptionManager()->isEnabled() ? true : is_null($wopi->getEditorUid()),
'PostMessageOrigin' => $wopi->getServerHost(),
'LastModifiedTime' => Helper::toISO8601($file->getMtime())
];
$serverVersion = $this->config->getSystemValue('version');
if (version_compare($serverVersion, '13', '>=')) {
$user = $this->userManager->get($res['editor']);
$user = $this->userManager->get($wopi->getEditorUid());
if($user !== null) {
if($user->getAvatarImage(32) !== null) {
$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $res['editor'], 'size' => 32]);
$response['UserExtraInfo']['avatar'] = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $wopi->getEditorUid(), 'size' => 32]);
}
}
}
@ -152,20 +160,18 @@ class WopiController extends Controller {
public function getFile($fileId,
$access_token) {
list($fileId, , $version) = Helper::parseFileId($fileId);
$row = new Wopi();
$row->loadBy('token', $access_token);
$res = $row->getPathForToken($fileId, $access_token);
$wopi = $this->wopiMapper->getPathForToken($access_token);
try {
/** @var File $file */
$userFolder = $this->rootFolder->getUserFolder($res['owner']);
$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
$file = $userFolder->getById($fileId)[0];
\OC_User::setIncognitoMode(true);
if ($version !== '0')
{
$view = new View('/' . $res['owner'] . '/files');
if ($version !== '0') {
$view = new View('/' . $wopi->getOwnerUid() . '/files');
$relPath = $view->getRelativePath($file->getPath());
$versionPath = '/files_versions/' . $relPath . '.v' . $version;
$view = new View('/' . $res['owner']);
$view = new View('/' . $wopi->getOwnerUid());
if ($view->file_exists($versionPath)){
$response = new StreamResponse($view->fopen($versionPath, 'rb'));
}
@ -201,17 +207,14 @@ class WopiController extends Controller {
list($fileId, , $version) = Helper::parseFileId($fileId);
$isPutRelative = ($this->request->getHeader('X-WOPI-Override') === 'PUT_RELATIVE');
$row = new Wopi();
$row->loadBy('token', $access_token);
$res = $row->getPathForToken($fileId, $access_token);
if (!$res['canwrite']) {
$wopi = $this->wopiMapper->getPathForToken($access_token);
if (!$wopi->getCanwrite()) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
try {
/** @var File $file */
$userFolder = $this->rootFolder->getUserFolder($res['owner']);
$userFolder = $this->rootFolder->getUserFolder($wopi->getOwnerUid());
$file = $userFolder->getById($fileId)[0];
if ($isPutRelative) {
@ -263,10 +266,10 @@ class WopiController extends Controller {
$content = fopen('php://input', 'rb');
// Setup the FS which is needed to emit hooks (versioning).
\OC_Util::tearDownFS();
\OC_Util::setupFS($res['owner']);
\OC_Util::setupFS($wopi->getOwnerUid());
// Set the user to register the change under his name
$editor = \OC::$server->getUserManager()->get($res['editor']);
$editor = \OC::$server->getUserManager()->get($wopi->getEditorUid());
if (!is_null($editor)) {
\OC::$server->getUserSession()->setUser($editor);
}

Просмотреть файл

@ -1,220 +0,0 @@
<?php
/**
* ownCloud - Richdocuments App
*
* @author Victor Dubiniuk
* @copyright 2013 Victor Dubiniuk victor.dubiniuk@gmail.com
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
namespace OCA\Richdocuments\Db;
/**
* Generic DB class
*/
abstract class DbBase {
protected $data;
protected $tableName;
protected $insertStatement;
protected $loadStatement;
public function __construct($data = array()){
$this->setData($data);
}
/**
* Insert current object data into database
* @return mixed
*/
public function insert(){
$result = $this->execute($this->insertStatement);
return $result;
}
/**
* Get id of the recently inserted record
* @return mixed
*/
public function getLastInsertId(){
return \OC::$server->getDatabaseConnection()->lastInsertId($this->tableName);
}
/**
* Get single record by primary key
* @param int $value primary key value
* @return \OCA\Richdocuments\Db
*/
public function load($value){
if (!is_array($value)){
$value = array($value);
}
$result = $this->execute($this->loadStatement, $value);
$data = $result->fetch();
if (!is_array($data)){
$data = array();
}
$this->data = $data;
return $this;
}
/**
* Get single record matching condition
* @param string $field for WHERE condition
* @param mixed $value matching value(s)
* @return \OCA\Richdocuments\Db
* @throws Exception
*/
public function loadBy($field, $value){
if (!is_array($value)){
$value = array($value);
}
$result = $this->execute('SELECT * FROM ' . $this->tableName . ' WHERE `'. $field .'` =?', $value);
$data = $result->fetchAll();
if (!is_array($data) || !count($data)){
$this->data = array();
} elseif (count($data) !== 1) {
throw new Exception('Duplicate ' . $value . ' for the filed ' . $field);
} else {
$this->data = $data[0];
}
return $this;
}
/**
* Delete records matching the condition
* @param string $field for WHERE condition
* @param mixed $value matching value(s)
*/
public function deleteBy($field, $value){
if (!is_array($value)){
$value = array($value);
}
$count = count($value);
if ($count===0){
return;
} elseif ($count===1){
$this->execute('DELETE FROM ' . $this->tableName . ' WHERE `'. $field .'` =?', $value);
} else {
$stmt = $this->buildInQuery($field, $value);
$this->execute('DELETE FROM ' . $this->tableName . ' WHERE ' . $stmt, $value);
}
}
/**
* Get all records from the table
* @return array
*/
public function getCollection(){
$result = $this->execute('SELECT * FROM ' . $this->tableName);
$data = $result->fetchAll();
if (!is_array($data)){
$data = array();
}
return $data;
}
/**
* Get array of matching records
* @param string $field for WHERE condition
* @param mixed $value matching value(s)
* @return array
*/
public function getCollectionBy($field, $value){
if (!is_array($value)){
$value = array($value);
}
$count = count($value);
if ($count===0){
return array();
} elseif ($count===1){
$result = $this->execute('SELECT * FROM ' . $this->tableName . ' WHERE `'. $field .'` =?', $value);
} else {
$stmt = $this->buildInQuery($field, $value);
$result = $this->execute('SELECT * FROM ' . $this->tableName . ' WHERE '. $stmt , $value);
}
$data = $result->fetchAll();
if (!is_array($data)){
$data = array();
}
return $data;
}
/**
* Get object data
* @return Array
*/
public function getData(){
return $this->data;
}
/**
* Set object data
* @param array $data
*/
public function setData($data){
$this->data = $data;
}
/**
* Check if there are any data in current object
* @return bool
*/
public function hasData(){
return count($this->data)>0;
}
/**
* Build placeholders for the query with variable input data
* @param string $field field name
* @param Array $array data
* @return String `field` IN (?, ?...) placeholders matching the number of elements in array
*/
protected function buildInQuery($field, $array){
$count = count($array);
$placeholders = array_fill(0, $count, '?');
$stmt = implode(', ', $placeholders);
return '`' . $field . '` IN (' . $stmt . ')';
}
/**
* Execute a query on database
* @param string $statement query to be executed
* @param mixed $args value(s) for the query.
* If omited the query will be run on the current object $data
* @return mixed (array/false)
*/
protected function execute($statement, $args = null){
$query = \OC::$server->getDatabaseConnection()->prepare($statement);
if (!is_null($args)){
$result = $query->execute($args);
} elseif (count($this->data)){
$result = $query->execute($this->data);
} else {
$result = $query->execute();
}
return $result ? $query : false;
}
public function __call($name, $arguments){
if (substr($name, 0, 3) === 'get'){
$requestedProperty = substr($name, 3);
$property = strtolower(preg_replace('/(.)([A-Z])/', "$1_$2", $requestedProperty));
if (isset($this->data[$property])){
return $this->data[$property];
}
}
return null;
}
}

Просмотреть файл

@ -1,83 +1,85 @@
<?php
/**
* ownCloud - Richdocuments App
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Ashod Nakashian
* @copyright 2016 Ashod Nakashian ashod.nakashian@collabora.co.uk
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
namespace OCA\Richdocuments\Db;
class Wopi extends DbBase {
use OCP\AppFramework\Db\Entity;
const DB_TABLE = '`*PREFIX*richdocuments_wopi`';
/**
* Class WopiEntity
*
* @package OCA\Richdocuments\Db
*
* @method void setOwnerUid(string $uid)
* @method string getOwnerUid()
* @method void setEditorUid(string $uid)
* @method string getEditorUid()
* @method void setFileid(int $fileid)
* @method int getFileid()
* @method void setVersion(int $version)
* @method int getVersion()
* @method void setCanwrite(bool $canwrite)
* @method bool getCanwrite()
* @method void setServerHost(string $host)
* @method string getServerHost()
* @method void setToken(string $token)
* @method string getToken()
* @method void setExpiry(int $expiry)
* @method int getExpiry()
*/
class Wopi extends Entity {
/** @var string */
protected $ownerUid;
// Tokens expire after this many seconds (not defined by WOPI specs).
const TOKEN_LIFETIME_SECONDS = 1800;
/** @var string */
protected $editorUid;
protected $tableName = '`*PREFIX*richdocuments_wopi`';
/** @var int */
protected $fileid;
protected $insertStatement = 'INSERT INTO `*PREFIX*richdocuments_wopi` (`fileid`, `owner_uid`, `editor_uid`, `version`, `canwrite`, `server_host`, `token`, `expiry`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
/** @var int */
protected $version;
protected $loadStatement = 'SELECT * FROM `*PREFIX*richdocuments_wopi` WHERE `token`= ?';
/** @var bool */
protected $canwrite;
public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost) {
$token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(32,
\OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
\OCP\Security\ISecureRandom::CHAR_DIGITS);
/** @var string */
protected $serverHost;
$wopi = new \OCA\Richdocuments\Db\Wopi([
$fileId,
$owner,
$editor,
$version,
$updatable,
$serverHost,
$token,
time() + self::TOKEN_LIFETIME_SECONDS
]);
/** @var string */
protected $token;
if (!$wopi->insert()) {
throw new \Exception('Failed to add wopi token into database');
}
/** @var int */
protected $expiry;
return $token;
public function __construct() {
$this->addType('owner_uid', 'string');
$this->addType('editor_uid', 'string');
$this->addType('fileid', 'int');
$this->addType('version', 'int');
$this->addType('canwrite', 'bool');
$this->addType('server_host', 'string');
$this->addType('token', 'string');
$this->addType('expiry', 'int');
}
/*
* Given a token, validates it and
* constructs and validates the path.
* Returns the path, if valid, else false.
*/
public function getPathForToken($fileId, $token){
$wopi = new Wopi();
$row = $wopi->loadBy('token', $token)->getData();
\OC::$server->getLogger()->debug('Loaded WOPI Token record: {row}.', [ 'row' => $row ]);
if (count($row) === 0)
{
// Invalid token.
http_response_code(401);
return false;
}
//TODO: validate.
if ($row['expiry'] > time()){
// Expired token!
//http_response_code(404);
//$wopi->deleteBy('id', $row['id']);
//return false;
}
return array(
'owner' => $row['owner_uid'],
'editor' => $row['editor_uid'],
'canwrite' => $row['canwrite'],
'server_host' => $row['server_host']
);
}
}

124
lib/Db/WopiMapper.php Normal file
Просмотреть файл

@ -0,0 +1,124 @@
<?php
/**
* @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Richdocuments\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Mapper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\Security\ISecureRandom;
class WopiMapper extends Mapper {
// Tokens expire after this many seconds (not defined by WOPI specs).
const TOKEN_LIFETIME_SECONDS = 1800;
/** @var ISecureRandom */
private $random;
/** @var ILogger */
private $logger;
/** @var ITimeFactory */
private $timeFactory;
public function __construct(IDBConnection $db,
ISecureRandom $random,
ILogger $logger,
ITimeFactory $timeFactory) {
parent::__construct($db, 'richdocuments_wopi', Wopi::class);
$this->random = $random;
$this->logger = $logger;
$this->timeFactory = $timeFactory;
}
/**
* @param int $fileId
* @param string $owner
* @param string$editor
* @param int $version
* @param bool $updatable
* @param string $serverHost
* @return Wopi
*/
public function generateFileToken($fileId, $owner, $editor, $version, $updatable, $serverHost) {
$token = $this->random->generate(32, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
$wopi = Wopi::fromParams([
'fileid' => $fileId,
'ownerUid' => $owner,
'editorUid' => $editor,
'version' => $version,
'canwrite' => $updatable,
'serverHost' => $serverHost,
'token' => $token,
'expiry' => $this->timeFactory->getTime() + self::TOKEN_LIFETIME_SECONDS,
]);
/** @var Wopi $wopi */
$wopi = $this->insert($wopi);
return $wopi;
}
/**
* Given a token, validates it and
* constructs and validates the path.
* Returns the path, if valid, else false.
*
* @param string $token
* @throws DoesNotExistException
* @return Wopi
*/
public function getPathForToken($token) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('richdocuments_wopi')
->where(
$qb->expr()->eq('token', $qb->createNamedParameter($token))
);
$result = $qb->execute();
$row = $result->fetch();
$result->closeCursor();
$this->logger->debug('Loaded WOPI Token record: {row}.', [ 'row' => $row ]);
if ($row === false) {
throw new DoesNotExistException('Could not find token.');
}
/** @var Wopi $wopi */
$wopi = Wopi::fromRow($row);
//TODO: validate.
if ($wopi->getExpiry() > $this->timeFactory->getTime()){
// Expired token!
//http_response_code(404);
//$wopi->deleteBy('id', $row['id']);
//return false;
}
return $wopi;
}
}

Просмотреть файл

@ -22,6 +22,7 @@
namespace OCA\Richdocuments;
use OC\Share\Constants;
use OCA\Richdocuments\Db\WopiMapper;
use OCA\Richdocuments\Helper;
use OCA\Richdocuments\Db\Wopi;
use OCA\Richdocuments\WOPI\Parser;
@ -39,25 +40,36 @@ class TokenManager {
private $urlGenerator;
/** @var Parser */
private $wopiParser;
/** @var AppConfig */
private $appConfig;
/** @var string */
private $userId;
/** @var WopiMapper */
private $wopiMapper;
/**
* @param IRootFolder $rootFolder
* @param IManager $shareManager
* @param IURLGenerator $urlGenerator
* @param Parser $wopiParser
* @param AppConfig $appConfig
* @param string $UserId
* @param WopiMapper $wopiMapper
*/
public function __construct(IRootFolder $rootFolder,
IManager $shareManager,
IURLGenerator $urlGenerator,
Parser $wopiParser,
AppConfig $appConfig,
$UserId) {
$UserId,
WopiMapper $wopiMapper) {
$this->rootFolder = $rootFolder;
$this->shareManager = $shareManager;
$this->urlGenerator = $urlGenerator;
$this->wopiParser = $wopiParser;
$this->appConfig = $appConfig;
$this->userId = $UserId;
$this->wopiMapper = $wopiMapper;
}
/**
@ -112,15 +124,14 @@ class TokenManager {
if (is_null($owneruid)) {
$owneruid = $file->getOwner()->getUID();
}
$row = new Wopi();
$serverHost = $this->urlGenerator->getAbsoluteURL('/');//$this->request->getServerProtocol() . '://' . $this->request->getServerHost();
$token = $row->generateFileToken($fileId, $owneruid, $this->userId, $version, (int)$updatable, $serverHost);
$wopi = $this->wopiMapper->generateFileToken($fileId, $owneruid, $this->userId, $version, (int)$updatable, $serverHost);
try {
return [
$this->wopiParser->getUrlSrc($file->getMimeType())['urlsrc'],
$token,
$wopi->getToken(),
];
} catch (\Exception $e){
throw $e;