Merge branch 'master' into fix/security/filter-mimetype

pull/66/head
Maxence Lange 2018-11-28 20:32:23 -01:00 zatwierdzone przez GitHub
commit f47ce64200
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
32 zmienionych plików z 749 dodań i 115 usunięć

Wyświetl plik

@ -41,7 +41,7 @@
<field>
<name>summary</name>
<type>text</type>
<length>500</length>
<length>3000</length>
<notnull>true</notnull>
</field>
@ -198,7 +198,7 @@
<field>
<name>summary</name>
<type>text</type>
<length>255</length>
<length>3000</length>
<notnull>true</notnull>
</field>
@ -362,7 +362,7 @@
<field>
<name>summary</name>
<type>text</type>
<length>500</length>
<length>3000</length>
<notnull>true</notnull>
</field>

Wyświetl plik

@ -5,7 +5,7 @@
<name>Social</name>
<summary>🎉 Nextcloud becomes part of the federated social networks!</summary>
<description><![CDATA[test]]></description>
<version>0.0.50</version>
<version>0.0.51</version>
<licence>agpl</licence>
<author mail="maxence@artificial-owl.com">Maxence Lange</author>
<author mail="jus@bitgrid.net">Julius Härtl</author>
@ -28,12 +28,14 @@
<background-jobs>
<job>OCA\Social\Cron\Cache</job>
<job>OCA\Social\Cron\Queue</job>
</background-jobs>
<commands>
<command>OCA\Social\Command\CacheRefresh</command>
<command>OCA\Social\Command\QueueStatus</command>
<command>OCA\Social\Command\NoteCreate</command>
<command>OCA\Social\Command\QueueStatus</command>
<command>OCA\Social\Command\QueueProcess</command>
</commands>
</info>

8
composer.lock wygenerowano
Wyświetl plik

@ -12,12 +12,12 @@
"source": {
"type": "git",
"url": "https://github.com/daita/my-small-php-tools.git",
"reference": "12090dc3ae29d2eb49d5274ca3f6ebfb76ce5997"
"reference": "56cff24fdde14d21e3903428c5ee629c839866af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/12090dc3ae29d2eb49d5274ca3f6ebfb76ce5997",
"reference": "12090dc3ae29d2eb49d5274ca3f6ebfb76ce5997",
"url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/56cff24fdde14d21e3903428c5ee629c839866af",
"reference": "56cff24fdde14d21e3903428c5ee629c839866af",
"shasum": ""
},
"require": {
@ -40,7 +40,7 @@
}
],
"description": "My small PHP Tools",
"time": "2018-11-28T10:47:43+00:00"
"time": "2018-11-28T13:07:27+00:00"
}
],
"packages-dev": [],

Wyświetl plik

@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - Social Support
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
* @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\Social\Command;
use Exception;
use OC\Core\Command\Base;
use OCA\Social\Exceptions\ActorDoesNotExistException;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Service\ActivityService;
use OCA\Social\Service\ConfigService;
use OCA\Social\Service\MiscService;
use OCA\Social\Service\QueueService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class QueueProcess extends Base {
/** @var ActivityService */
private $activityService;
/** @var QueueService */
private $queueService;
/** @var ConfigService */
private $configService;
/** @var MiscService */
private $miscService;
/**
* NoteCreate constructor.
*
* @param ActivityService $activityService
* @param QueueService $queueService
* @param ConfigService $configService
* @param MiscService $miscService
*/
public function __construct(
ActivityService $activityService, QueueService $queueService, ConfigService $configService,
MiscService $miscService
) {
parent::__construct();
$this->activityService = $activityService;
$this->queueService = $queueService;
$this->configService = $configService;
$this->miscService = $miscService;
}
/**
*
*/
protected function configure() {
parent::configure();
$this->setName('social:queue:process')
->setDescription('Process the request queue');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$requests = $this->queueService->getRequestStandby($total = 0);
$output->writeLn('found a total of ' . $total . ' requests in the queue');
if ($total === 0) {
return;
}
$output->writeLn(sizeof($requests) . ' are processable at this time');
if (sizeof($requests) === 0) {
return;
}
$this->activityService->manageInit();
foreach ($requests as $request) {
$output->write('.');
try {
$this->activityService->manageRequest($request);
} catch (RequestException $e) {
} catch (SocialAppConfigException $e) {
}
}
$output->writeLn('done');
}
}

Wyświetl plik

@ -32,9 +32,10 @@ namespace OCA\Social\Controller;
use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
use Exception;
use OC\AppFramework\Http;
use OCA\Social\AppInfo\Application;
use OCA\Social\Db\NotesRequest;
use OCA\Social\Exceptions\SignatureException;
use OCA\Social\Exceptions\SignatureIsGoneException;
use OCA\Social\Exceptions\UnknownItemException;
use OCA\Social\Service\ActivityPub\FollowService;
use OCA\Social\Service\ActivityService;
@ -124,7 +125,6 @@ class ActivityPubController extends Controller {
* @param string $username
*
* @return Response
* @throws \OC\User\NoUserException
*/
public function actor(string $username): Response {
if (!$this->checkSourceActivityStreams()) {
@ -184,6 +184,8 @@ class ActivityPubController extends Controller {
}
return $this->success([]);
} catch (SignatureIsGoneException $e) {
return $this->fail($e, [], Http::STATUS_GONE);
} catch (Exception $e) {
return $this->fail($e);
}
@ -207,10 +209,13 @@ class ActivityPubController extends Controller {
public function inbox(string $username): Response {
try {
$actor = $this->actorService->getActor($username);
$this->activityService->checkRequest($this->request);
$body = file_get_contents('php://input');
// TODO - check the recipient <-> username
// $actor = $this->actorService->getActor($username);
$this->miscService->log('Inbox: ' . $body);
$activity = $this->importService->import($body);
@ -220,6 +225,8 @@ class ActivityPubController extends Controller {
}
return $this->success([]);
} catch (SignatureIsGoneException $e) {
return $this->fail($e, [], Http::STATUS_GONE);
} catch (Exception $e) {
return $this->fail($e);
}

Wyświetl plik

@ -140,9 +140,14 @@ class LocalController extends Controller {
$post->setType($this->get('type', $data, NoteService::TYPE_PUBLIC));
/** @var ACore $activity */
$this->postService->createPost($post, $activity);
$token = $this->postService->createPost($post, $activity);
return $this->directSuccess($activity->getObject());
return $this->success(
[
'post' => $activity->getObject(),
'token' => $token
]
);
} catch (Exception $e) {
return $this->fail($e);
}
@ -415,7 +420,6 @@ class LocalController extends Controller {
$cached = [];
foreach ($documents as $id) {
try {
$document = $this->documentService->cacheRemoteDocument($id);
$cached[] = $document;
} catch (Exception $e) {

Wyświetl plik

@ -40,6 +40,7 @@ use OCA\Social\AppInfo\Application;
use OCA\Social\Exceptions\AccountAlreadyExistsException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Service\ActivityPub\DocumentService;
use OCA\Social\Service\ActivityPub\PersonService;
use OCA\Social\Service\ActorService;
use OCA\Social\Service\ConfigService;
use OCA\Social\Service\MiscService;
@ -85,6 +86,9 @@ class NavigationController extends Controller {
/** @var IL10N */
private $l10n;
/** @var PersonService */
private $personService;
/**
* NavigationController constructor.
*
@ -95,12 +99,14 @@ class NavigationController extends Controller {
* @param ActorService $actorService
* @param DocumentService $documentService
* @param ConfigService $configService
* @param PersonService $personService
* @param MiscService $miscService
* @param IL10N $l10n
*/
public function __construct(
IRequest $request, $userId, IConfig $config, IURLGenerator $urlGenerator,
ActorService $actorService, DocumentService $documentService, ConfigService $configService,
PersonService $personService,
MiscService $miscService, IL10N $l10n
) {
parent::__construct(Application::APP_NAME, $request);
@ -112,6 +118,7 @@ class NavigationController extends Controller {
$this->actorService = $actorService;
$this->documentService = $documentService;
$this->configService = $configService;
$this->personService = $personService;
$this->miscService = $miscService;
$this->l10n = $l10n;
}
@ -125,7 +132,6 @@ class NavigationController extends Controller {
* @NoSubAdminRequired
*
* @return TemplateResponse
* @throws NoUserException
*/
public function navigate($path = ''): TemplateResponse {
$data = [
@ -162,6 +168,10 @@ class NavigationController extends Controller {
$data['serverData']['firstrun'] = true;
} catch (AccountAlreadyExistsException $e) {
// we do nothing
} catch (NoUserException $e) {
// well, should not happens
} catch (SocialAppConfigException $e) {
// neither.
}
return new TemplateResponse(Application::APP_NAME, 'main', $data);
@ -231,9 +241,13 @@ class NavigationController extends Controller {
* @param $username
*
* @return RedirectResponse|PublicTemplateResponse
* @throws NoUserException
*/
public function public($username) {
// Redirect to external instances
if (preg_match('/@[\w._-]+@[\w._-]+/', $username) === 1) {
$actor = $this->personService->getFromAccount(substr($username, 1));
return new RedirectResponse($actor->getUrl());
}
if (\OC::$server->getUserSession()
->isLoggedIn()) {
return $this->navigate();

Wyświetl plik

@ -98,10 +98,10 @@ class QueueController extends Controller {
$this->async();
$requests = $this->queueService->getRequestFromToken($token, RequestQueue::STATUS_STANDBY);
$this->activityService->manageInit();
foreach ($requests as $request) {
try {
$this->activityService->manageRequest($request);
} catch (ActorDoesNotExistException $e) {
} catch (RequestException $e) {
} catch (SocialAppConfigException $e) {
}

109
lib/Cron/Queue.php 100644
Wyświetl plik

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* Nextcloud - Social Support
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
* @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\Social\Cron;
use Exception;
use OC\BackgroundJob\TimedJob;
use OCA\Social\AppInfo\Application;
use OCA\Social\Exceptions\ActorDoesNotExistException;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Service\ActivityPub\DocumentService;
use OCA\Social\Service\ActivityPub\PersonService;
use OCA\Social\Service\ActivityService;
use OCA\Social\Service\ActorService;
use OCA\Social\Service\CacheService;
use OCA\Social\Service\ConfigService;
use OCA\Social\Service\MiscService;
use OCA\Social\Service\QueueService;
use OCP\AppFramework\QueryException;
/**
* Class Queue
*
* @package OCA\Social\Cron
*/
class Queue extends TimedJob {
/** @var ActivityService */
private $activityService;
/** @var QueueService */
private $queueService;
/** @var MiscService */
private $miscService;
/**
* Cache constructor.
*/
public function __construct() {
$this->setInterval(12 * 60); // 12 minutes
}
/**
* @param mixed $argument
*
* @throws QueryException
*/
protected function run($argument) {
$app = new Application();
$c = $app->getContainer();
$this->queueService = $c->query(QueueService::class);
$this->activityService = $c->query(ActivityService::class);
$this->miscService = $c->query(MiscService::class);
$this->manageQueue();
}
private function manageQueue() {
$requests = $this->queueService->getRequestStandby($total = 0);
$this->activityService->manageInit();
foreach ($requests as $request) {
try {
$this->activityService->manageRequest($request);
} catch (RequestException $e) {
} catch (SocialAppConfigException $e) {
}
}
}
}

Wyświetl plik

@ -60,32 +60,28 @@ class ActorsRequest extends ActorsRequestBuilder {
* @param Person $actor
*
* @return string
* @throws \Exception
* @throws SocialAppConfigException
*/
public function create(Person $actor): string {
$id = $this->configService->getUrlSocial() . '@' . $actor->getPreferredUsername();
try {
$qb = $this->getActorsInsertSql();
$qb = $this->getActorsInsertSql();
$qb->setValue('id', $qb->createNamedParameter($id))
$qb->setValue('id', $qb->createNamedParameter($id))
// ->setValue('type', $qb->createNamedParameter($actor->getType()))
->setValue('user_id', $qb->createNamedParameter($actor->getUserId()))
->setValue('name', $qb->createNamedParameter($actor->getName()))
->setValue('summary', $qb->createNamedParameter($actor->getSummary()))
->setValue(
'preferred_username', $qb->createNamedParameter($actor->getPreferredUsername())
)
->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey()))
->setValue('private_key', $qb->createNamedParameter($actor->getPrivateKey()));
->setValue('user_id', $qb->createNamedParameter($actor->getUserId()))
->setValue('name', $qb->createNamedParameter($actor->getName()))
->setValue('summary', $qb->createNamedParameter($actor->getSummary()))
->setValue(
'preferred_username', $qb->createNamedParameter($actor->getPreferredUsername())
)
->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey()))
->setValue('private_key', $qb->createNamedParameter($actor->getPrivateKey()));
$qb->execute();
$qb->execute();
return $id;
} catch (\Exception $e) {
throw $e;
}
return $id;
}

Wyświetl plik

@ -150,6 +150,7 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder {
$qb = $this->getCacheDocumentsSelectSql();
$this->limitToDBFieldEmpty($qb, 'local_copy');
$this->limitToCaching($qb, self::CACHING_TIMEOUT);
$this->limitToDBFieldInt($qb, 'error', 0);
$documents = [];
$cursor = $qb->execute();

Wyświetl plik

@ -361,14 +361,6 @@ class CoreRequestBuilder {
}
/**
* @param IQueryBuilder $qb
*/
protected function orderByPriority(IQueryBuilder &$qb) {
$qb->orderBy('priority', 'desc');
}
/**
* @param IQueryBuilder $qb
* @param string $field

Wyświetl plik

@ -57,18 +57,6 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
public function multiple(array $queues) {
foreach ($queues as $queue) {
$this->create($queue);
// $qb->values(
// [
// 'source' => $qb->createNamedParameter($queue->getSource()),
// 'activity' => $qb->createNamedParameter($queue->getActivity()),
// 'instance' => $qb->createNamedParameter(
// json_encode($queue->getInstance(), JSON_UNESCAPED_SLASHES)
// ),
// 'status' => $qb->createNamedParameter($queue->getStatus()),
// 'tries' => $qb->createNamedParameter($queue->getTries()),
// 'last' => $qb->createNamedParameter($queue->getLast())
// ]
// );
}
}
@ -92,14 +80,34 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
)
->setValue('priority', $qb->createNamedParameter($queue->getPriority()))
->setValue('status', $qb->createNamedParameter($queue->getStatus()))
->setValue('tries', $qb->createNamedParameter($queue->getTries()))
->setValue('last', $qb->createNamedParameter($queue->getLast()));
->setValue('tries', $qb->createNamedParameter($queue->getTries()));
$qb->execute();
}
/**
* return Actor from database based on the username
* return Queue from database based on the status=0
*
* @return RequestQueue[]
*/
public function getStandby(): array {
$qb = $this->getQueueSelectSql();
$this->limitToStatus($qb, RequestQueue::STATUS_STANDBY);
$qb->orderBy('id', 'asc');
$requests = [];
$cursor = $qb->execute();
while ($data = $cursor->fetch()) {
$requests[] = $this->parseQueueSelectSql($data);
}
$cursor->closeCursor();
return $requests;
}
/**
* return Queue from database based on the token
*
* @param string $token
* @param int $status
@ -114,7 +122,7 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
$this->limitToStatus($qb, $status);
}
$this->orderByPriority($qb);
$qb->orderBy('priority', 'desc');
$requests = [];
$cursor = $qb->execute();
@ -180,9 +188,11 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
*/
public function setAsFailure(RequestQueue &$queue) {
$qb = $this->getQueueUpdateSql();
$qb->set('status', $qb->createNamedParameter(RequestQueue::STATUS_STANDBY));
// TODO - increment tries++
// ->set('tries', 'tries+1');
$func = $qb->func();
$expr = $qb->expr();
$qb->set('status', $qb->createNamedParameter(RequestQueue::STATUS_STANDBY))
->set('tries', $func->add('tries', $expr->literal(1)));
$this->limitToId($qb, $queue->getId());
$this->limitToStatus($qb, RequestQueue::STATUS_RUNNING);
@ -195,5 +205,13 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
$queue->setStatus(RequestQueue::STATUS_SUCCESS);
}
public function delete(RequestQueue $queue) {
$qb = $this->getQueueDeleteSql();
$this->limitToId($qb, $queue->getId());
$qb->execute();
}
}

Wyświetl plik

@ -0,0 +1,8 @@
<?php
namespace OCA\Social\Exceptions;
class Request410Exception extends \Exception {
}

Wyświetl plik

@ -0,0 +1,8 @@
<?php
namespace OCA\Social\Exceptions;
class SignatureIsGoneException extends \Exception {
}

Wyświetl plik

@ -32,6 +32,7 @@ namespace OCA\Social\Model;
use daita\MySmallPhpTools\Traits\TArrayTools;
use DateTime;
use JsonSerializable;
@ -299,9 +300,15 @@ class RequestQueue implements JsonSerializable {
$this->setActivity($this->get('activity', $data, ''));
$this->setStatus($this->getInt('status', $data, 0));
$this->setTries($this->getInt('tries', $data, 0));
$this->setLast($this->getInt('last', $data, 0));
}
$last = $this->get('last', $data, '');
if ($last === '') {
$this->setLast(0);
} else {
$dTime = new DateTime($last);
$this->setLast($dTime->getTimestamp());
}
}
/**
* @return array

Wyświetl plik

@ -38,6 +38,7 @@ use OCA\Social\Db\CacheDocumentsRequest;
use OCA\Social\Exceptions\CacheActorDoesNotExistException;
use OCA\Social\Exceptions\CacheDocumentDoesNotExistException;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Exceptions\Request410Exception;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Exceptions\UrlCloudException;
@ -124,6 +125,7 @@ class PersonService implements ICoreService {
* @throws RequestException
* @throws SocialAppConfigException
* @throws UrlCloudException
* @throws Request410Exception
*/
public function getFromId(string $id, bool $refresh = false): Person {

Wyświetl plik

@ -43,8 +43,10 @@ use OCA\Social\Exceptions\EmptyQueueException;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Exceptions\NoHighPriorityRequestException;
use OCA\Social\Exceptions\QueueStatusException;
use OCA\Social\Exceptions\Request410Exception;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SignatureException;
use OCA\Social\Exceptions\SignatureIsGoneException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Exceptions\UrlCloudException;
use OCA\Social\Model\ActivityPub\ACore;
@ -105,6 +107,10 @@ class ActivityService {
private $miscService;
/** @var array */
private $failInstances;
/**
* ActivityService constructor.
*
@ -231,10 +237,12 @@ class ActivityService {
$author = $this->getAuthorFromItem($activity);
$instancePaths = $this->generateInstancePaths($activity);
$token = $this->queueService->generateRequestQueue($instancePaths, $activity, $author);
$this->manageInit();
try {
$directRequest = $this->queueService->getPriorityRequest($token);
$this->manageRequest($directRequest);
} catch (RequestException $e) {
} catch (NoHighPriorityRequestException $e) {
} catch (EmptyQueueException $e) {
return '';
@ -246,14 +254,23 @@ class ActivityService {
}
public function manageInit() {
$this->failInstances = [];
}
/**
* @param RequestQueue $queue
*
* @throws ActorDoesNotExistException
* @throws RequestException
* @throws SocialAppConfigException
*/
public function manageRequest(RequestQueue $queue) {
$host = $queue->getInstance()
->getAddress();
if (in_array($host, $this->failInstances)) {
throw new RequestException();
}
try {
$this->queueService->initRequest($queue);
@ -261,15 +278,22 @@ class ActivityService {
return;
}
$result = $this->generateRequest(
$queue->getInstance(), $queue->getActivity(), $queue->getAuthor()
);
try {
$result = $this->generateRequest(
$queue->getInstance(), $queue->getActivity(), $queue->getAuthor()
);
} catch (ActorDoesNotExistException $e) {
$this->queueService->deleteRequest($queue);
} catch (Request410Exception $e) {
$this->queueService->deleteRequest($queue);
}
try {
if ($this->getint('_code', $result, 500) === 202) {
$this->queueService->endRequest($queue, true);
} else {
$this->queueService->endRequest($queue, false);
$this->failInstances[] = $host;
}
} catch (QueueStatusException $e) {
}
@ -339,6 +363,7 @@ class ActivityService {
*
* @return Request[]
* @throws ActorDoesNotExistException
* @throws Request410Exception
* @throws RequestException
* @throws SocialAppConfigException
*/
@ -385,6 +410,9 @@ class ActivityService {
* @throws MalformedArrayException
* @throws RequestException
* @throws SignatureException
* @throws SocialAppConfigException
* @throws UrlCloudException
* @throws SignatureIsGoneException
*/
public function checkRequest(IRequest $request) {
$dTime = new DateTime($request->getHeader('date'));
@ -394,7 +422,12 @@ class ActivityService {
throw new SignatureException('object is too old');
}
$this->checkSignature($request);
try {
$this->checkSignature($request);
} catch (Request410Exception $e) {
throw new SignatureIsGoneException();
}
}
@ -429,9 +462,12 @@ class ActivityService {
* @param IRequest $request
*
* @throws InvalidResourceException
* @throws MalformedArrayException
* @throws Request410Exception
* @throws RequestException
* @throws SignatureException
* @throws MalformedArrayException
* @throws SocialAppConfigException
* @throws UrlCloudException
* @throws Exception
*/
private function checkSignature(IRequest $request) {
@ -508,6 +544,7 @@ class ActivityService {
* @throws RequestException
* @throws SocialAppConfigException
* @throws UrlCloudException
* @throws Request410Exception
*/
private function retrieveKey($keyId): string {
$actor = $this->personService->getFromId($keyId);

Wyświetl plik

@ -142,7 +142,7 @@ class ActorService {
*
* @throws AccountAlreadyExistsException
* @throws NoUserException
* @throws Exception
* @throws SocialAppConfigException
*/
public function createActor(string $userId, string $username) {

Wyświetl plik

@ -33,6 +33,8 @@ namespace OCA\Social\Service;
use daita\MySmallPhpTools\Model\Request;
use daita\MySmallPhpTools\Traits\TArrayTools;
use daita\MySmallPhpTools\Traits\TPathTools;
use Exception;
use OCA\Social\Exceptions\Request410Exception;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
@ -71,6 +73,7 @@ class CurlService {
*
* @return array
* @throws RequestException
* @throws Request410Exception
*/
public function request(Request $request): array {
$curl = $this->initRequest($request);
@ -85,6 +88,7 @@ class CurlService {
$this->parseRequestResultCode301($code);
// $this->parseRequestResultCode401($code);
$this->parseRequestResultCode404($code, $request);
$this->parseRequestResultCode410($code);
// $this->parseRequestResultCode503($code);
// $this->parseRequestResultCode500($code);
// $this->parseRequestResult($result);
@ -130,7 +134,7 @@ class CurlService {
$request->setAddress($host);
try {
$this->request($request);
} catch (RequestException $e) {
} catch (Exception $e) {
}
}
@ -234,11 +238,24 @@ class CurlService {
*
* @param Request $request
*
* @throws RequestException
* @throws Request410Exception
*/
private function parseRequestResultCode404(int $code, Request $request) {
if ($code === 404) {
throw new RequestException('404 Not Found - ' . json_encode($request));
throw new Request410Exception('404 Not Found - ' . json_encode($request));
}
}
/**
* @param int $code
*
* @param Request $request
*
* @throws Request410Exception
*/
private function parseRequestResultCode410(int $code) {
if ($code === 410) {
throw new Request410Exception();
}
}

Wyświetl plik

@ -35,6 +35,7 @@ use daita\MySmallPhpTools\Model\Request;
use daita\MySmallPhpTools\Traits\TArrayTools;
use daita\MySmallPhpTools\Traits\TPathTools;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Exceptions\Request410Exception;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Model\ActivityPub\ACore;
use OCA\Social\Model\Instance;
@ -79,12 +80,12 @@ class InstanceService {
* @return mixed
* @throws RequestException
* @throws InvalidResourceException
* @throws Request410Exception
*/
public function retrieveAccount(string $account) {
$account = $this->withoutBeginAt($account);
if (strstr(substr($account, 0, -3), '@') === false)
{
if (strstr(substr($account, 0, -3), '@') === false) {
throw new InvalidResourceException();
}
list($username, $host) = explode('@', $account);
@ -113,6 +114,7 @@ class InstanceService {
*
* @return mixed
* @throws RequestException
* @throws Request410Exception
*/
public function retrieveObject($id) {
$url = parse_url($id);

Wyświetl plik

@ -147,6 +147,22 @@ class QueueService {
}
public function getRequestStandby(int &$total = 0): array {
$requests = $this->requestQueueRequest->getStandby();
$total = sizeof($requests);
$result = [];
foreach ($requests as $request) {
$delay = floor(pow($request->getTries(), 4) / 3);
if ($request->getLast() < (time() - $delay)) {
$result[] = $request;
}
}
return $result;
}
/**
* @param string $token
* @param int $status
@ -186,6 +202,12 @@ class QueueService {
}
}
/**
* @param RequestQueue $queue
*/
public function deleteRequest(RequestQueue $queue) {
$this->requestQueueRequest->delete($queue);
}
}

Wyświetl plik

@ -4,7 +4,8 @@
<app-navigation :menu="menu" />
</div>
<div id="app-content">
<router-view :key="$route.fullPath" />
<Search v-if="searchTerm != ''" :term="searchTerm" />
<router-view v-if="searchTerm === ''" :key="$route.fullPath" />
</div>
</div>
<div v-else class="setup">
@ -30,6 +31,7 @@
.app-social {
width: 100%;
}
.setup {
margin: auto;
width: 700px;
@ -38,6 +40,16 @@
.setup input[type=url] {
width: 300px;
}
#app-content .social__wrapper {
margin: 15px calc(50% - 350px - 75px);
}
#social-spacer a:hover,
#social-spacer a:focus {
border: none !important;
}
</style>
<script>
@ -50,6 +62,7 @@ import {
import axios from 'nextcloud-axios'
import TimelineEntry from './components/TimelineEntry'
import ProfileInfo from './components/ProfileInfo'
import Search from './components/Search'
export default {
name: 'App',
@ -59,13 +72,15 @@ export default {
TimelineEntry,
Multiselect,
Avatar,
ProfileInfo
ProfileInfo,
Search
},
data: function() {
return {
infoHidden: false,
state: [],
cloudAddress: ''
cloudAddress: '',
searchTerm: ''
}
},
computed: {
@ -116,8 +131,7 @@ export default {
}
},
{
id: 'social-spacer',
classes: []
id: 'social-spacer'
},
{
id: 'social-local',
@ -146,12 +160,19 @@ export default {
}
}
},
watch: {
$route(to, from) {
this.searchTerm = ''
}
},
beforeMount: function() {
// importing server data into the store
const serverDataElmt = document.getElementById('serverData')
if (serverDataElmt !== null) {
this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server))
}
this.search = new OCA.Search(this.search, this.resetSearch)
},
methods: {
hideInfo() {
@ -162,6 +183,12 @@ export default {
this.$store.commit('setServerDataEntry', 'setup', false)
this.$store.commit('setServerDataEntry', 'cloudAddress', this.cloudAddress)
})
},
search(term) {
this.searchTerm = term
},
resetSearch() {
this.searchTerm = ''
}
}
}

Wyświetl plik

@ -58,11 +58,11 @@
</emoji-picker>
<div class="options">
<input :value="t('social', 'Post')" :disabled="post.length < 1" class="submit primary"
<input :value="currentVisibilityPostLabel" :disabled="post.length < 1" class="submit primary"
type="submit" title="" data-original-title="Post">
<div>
<button :class="currentVisibilityIconClass" @click.prevent="togglePopoverMenu" />
<div :class="{open: menuOpened}" class="popovermenu">
<div :class="{open: menuOpened}" class="popovermenu menu-center">
<PopoverMenu :menu="visibilityPopover" />
</div>
</div>
@ -154,6 +154,7 @@
padding: 5px;
width: 200px;
height: 200px;
top: 44px;
}
.emoji-picker > div {
overflow: hidden;
@ -167,6 +168,9 @@
margin: 3px;
width: 16px;
}
.popovermenu {
top: 55px;
}
</style>
<style>
/* Tribute-specific styles TODO: properly scope component css */
@ -289,7 +293,7 @@ export default {
},
data() {
return {
type: 'public',
type: localStorage.getItem('social.lastPostType') || 'followers',
post: '',
canType: true,
search: '',
@ -308,18 +312,19 @@ export default {
+ '<a href="' + item.original.url + '" target="_blank"><img src="' + item.original.avatar + '" />@' + item.original.value + '</a></span>'
},
values: (text, cb) => {
let users = []
if (text.length < 1) {
cb([])
cb(users)
}
this.remoteSearch(text).then((result) => {
let users = []
if (result.data.result.exact) {
let user = result.data.result.exact
users.push({
key: user.preferredUsername,
value: user.account,
url: user.url,
avatar: 'http://localhost:8000/index.php/avatar/admin/32?v=0' // TODO: use real avatar from server
avatar: user.local ? OC.generateUrl(`/avatar/${user.preferredUsername}/32`) : ''// TODO: use real avatar from server
})
}
for (var i in result.data.result.accounts) {
@ -328,7 +333,7 @@ export default {
key: user.preferredUsername,
value: user.account,
url: user.url,
avatar: 'http://localhost:8000/index.php/avatar/admin/32?v=0' // TODO: use real avatar from server
avatar: user.local ? OC.generateUrl(`/avatar/${user.preferredUsername}/32`) : ''// TODO: use real avatar from server
})
}
cb(users)
@ -360,20 +365,40 @@ export default {
}
}
},
currentVisibilityPostLabel() {
return this.visibilityPostLabel(this.type)
},
visibilityPostLabel() {
return (type) => {
if (typeof type === 'undefined') {
type = this.type
}
switch (type) {
case 'public':
return t('social', 'Post publicly')
case 'followers':
return t('social', 'Post to followers')
case 'direct':
return t('social', 'Post to recipients')
case 'unlisted':
return t('social', 'Post unlisted')
}
}
},
visibilityPopover() {
return [
{
action: () => { this.switchType('public') },
icon: this.visibilityIconClass('public'),
text: t('social', 'Public'),
longtext: t('social', 'Post to public timelines')
},
{
action: () => { this.switchType('direct') },
icon: this.visibilityIconClass('direct'),
text: t('social', 'Direct'),
longtext: t('social', 'Post to mentioned users only')
},
{
action: () => { this.switchType('unlisted') },
icon: this.visibilityIconClass('unlisted'),
text: t('social', 'Unlisted'),
longtext: t('social', 'Do not post to public timelines')
},
{
action: () => { this.switchType('followers') },
icon: this.visibilityIconClass('followers'),
@ -381,10 +406,10 @@ export default {
longtext: t('social', 'Post to followers only')
},
{
action: () => { this.switchType('unlisted') },
icon: this.visibilityIconClass('unlisted'),
text: t('social', 'Unlisted'),
longtext: t('social', 'Do not post to public timelines')
action: () => { this.switchType('public') },
icon: this.visibilityIconClass('public'),
text: t('social', 'Public'),
longtext: t('social', 'Post to public timelines')
}
]
}
@ -400,6 +425,7 @@ export default {
switchType(type) {
this.type = type
this.menuOpened = false
localStorage.setItem('social.lastPostType', type)
},
getPostData() {
let element = this.$refs.composerInput.cloneNode(true)

Wyświetl plik

@ -33,13 +33,13 @@
<ul class="user-profile--sections">
<li>
<router-link :to="{ name: 'profile', params: { account: $route.params.account }}" class="icon-category-monitoring">{{ accountInfo.posts }} posts</router-link>
<router-link :to="{ name: 'profile', params: { account: uid } }" class="icon-category-monitoring">{{ accountInfo.posts }} posts</router-link>
</li>
<li>
<router-link :to="{ name: 'following', params: { account: $route.params.account }}" class="icon-category-social">{{ accountInfo.following }} following</router-link>
<router-link :to="{ name: 'profile.following', params: { account: uid } }" class="icon-category-social">{{ accountInfo.following }} following</router-link>
</li>
<li>
<router-link :to="{ name: 'followers', params: { account: $route.params.account }}" class="icon-category-social">{{ accountInfo.followers }} followers</router-link>
<router-link :to="{ name: 'profile.followers', params: { account: uid } }" class="icon-category-social">{{ accountInfo.followers }} followers</router-link>
</li>
</ul>
</div>

Wyświetl plik

@ -0,0 +1,91 @@
<!--
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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/>.
-
-->
<template>
<div class="social__wrapper">
<div v-if="results.length < 1" id="emptycontent" :class="{'icon-loading': loading}"
class="">
<div class="icon-search" />
<h2>{{ t('social', 'No accounts found') }}</h2>
<p>No accounts found for {{ term }}</p>
</div>
<div v-if="match || results.length > 0">
<h3>{{ t('social', 'Search') }} {{ term }}</h3>
<UserEntry :item="match" />
<UserEntry v-for="result in results" :key="result.id" :item="result" />
</div>
</div>
</template>
<style scoped>
</style>
<script>
import UserEntry from './UserEntry'
import axios from 'nextcloud-axios'
export default {
name: 'Search',
components: {
UserEntry
},
props: {
term: {
type: String,
default: ''
}
},
data() {
return {
results: [],
loading: false,
match: null
}
},
watch: {
term(val) {
this.loading = true
this.accountSearch(val).then((response) => {
this.results = response.data.result.accounts
this.loading = false
})
const re = /@((\w+)(@[\w.]+)?)/g
if (val.match(re)) {
this.remoteSearch(val).then((response) => {
this.match = response.data.result.account
}).catch((e) => { this.match = null })
}
}
},
methods: {
accountSearch(term) {
this.loading = true
return axios.get(OC.generateUrl('apps/social/api/v1/accounts/search?search=' + term))
},
remoteSearch(term) {
return axios.get(OC.generateUrl('apps/social/api/v1/account/info?account=' + term))
}
}
}
</script>

Wyświetl plik

@ -11,6 +11,10 @@
<span class="post-author">{{ item.actor_info.preferredUsername }}</span>
<span class="post-author-id">{{ item.actor_info.account }}</span>
</router-link>
<a v-else-if="item.local" :href="item.id">
<span class="post-author">{{ item.actor_info.preferredUsername }}</span>
<span class="post-author-id">{{ item.actor_info.account }}</span>
</a>
<a v-else :href="item.actor_info.url">
<span class="post-author">{{ item.actor_info.preferredUsername }}</span>
<span class="post-author-id">{{ item.actor_info.account }}</span>
@ -44,7 +48,13 @@ export default {
formatedMessage: function() {
let message = this.item.content
message = message.replace(/(?:\r\n|\r|\n)/g, '<br />')
message = message.linkify()
message = message.linkify({
formatHref: {
email: function(href) {
return OC.generateUrl('/apps/social/@' + (href.indexOf('mailto:') === 0 ? href.substring(7) : href))
}
}
})
message = this.$twemoji.parse(message)
return message
}

Wyświetl plik

@ -0,0 +1,87 @@
<!--
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @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/>.
-
-->
<template>
<div class="user-entry">
<div class="entry-content">
<div class="user-avatar">
<avatar v-if="item.local" :size="32" :user="item.preferredUsername" />
<avatar v-else url="" />
</div>
<div class="user-details">
<router-link v-if="item.local" :to="{ name: 'profile', params: { account: item.account }}">
<span class="post-author">{{ item.preferredUsername }}</span>
</router-link>
<a v-else href="{{ item.id }}" target="_blank"
rel="noreferrer">{{ item.preferredUsername }}</a>
<p class="user-description">{{ item.account }}</p>
</div>
<button v-if="item.following" class="icon-checkmark-color">Following</button>
<button v-else class="primary">Follow</button>
</div>
</div>
</template>
<script>
import { Avatar } from 'nextcloud-vue'
export default {
name: 'UserEntry',
components: {
Avatar
},
props: {
item: { type: Object, default: () => {} }
},
data: function() {
return {
}
}
}
</script>
<style scoped>
.user-entry {
padding: 20px;
margin-bottom: 10px;
}
.user-avatar {
margin: 5px;
margin-right: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.entry-content {
display: flex;
align-items: flex-start;
}
.user-details {
flex-grow: 1;
}
.user-description {
opacity: 0.7;
}
</style>

Wyświetl plik

@ -53,29 +53,32 @@ export default new Router({
},
{
path: '/:index(index.php/)?apps/social/account/@:account',
alias: './timeline',
components: {
default: Profile
default: Profile,
details: ProfileTimeline
},
props: true,
name: 'profile',
children: [
{
path: '',
name: 'profile',
components: {
details: ProfileTimeline
}
},
{
path: 'followers',
name: 'profile.followers',
components: {
default: Profile,
details: ProfileFollowers
}
},
{
path: 'following',
name: 'profile.following',
components: {
default: Profile,
details: ProfileFollowers
}
}

Wyświetl plik

@ -58,7 +58,8 @@ const actions = {
},
post(context, post) {
return axios.post(OC.generateUrl('apps/social/api/v1/post'), { data: post }).then((response) => {
context.commit('addPost', { data: response.data })
// eslint-disable-next-line no-console
console.log('Post created with token ' + response.data.result.token)
}).catch((error) => {
OC.Notification.showTemporary('Failed to create a post')
console.error('Failed to create a post', error)

Wyświetl plik

@ -21,31 +21,49 @@
-->
<template>
<div class="social__timeline">
<ul>
<li>User List</li>
</ul>
<div class="social__followers">
<user-entry v-for="user in users" :item="user" :key="user.id" />
</div>
</template>
<style scoped>
.social__timeline {
.social__followers {
max-width: 700px;
margin: 15px auto;
display: flex;
}
.user-entry {
width: 50%;
}
</style>
<script>
import TimelineEntry from './../components/TimelineEntry'
import UserEntry from '../components/UserEntry'
export default {
name: 'ProfileFollowers',
components: {
TimelineEntry
UserEntry
},
computed: {
timeline: function() {
return this.$store.getters.getTimeline
},
users() {
return [
{
id: 'admin',
displayName: 'Administrator',
description: 'My social account',
following: true
},
{
id: 'admin',
displayName: 'Administrator',
description: 'My social account',
following: false
}
]
}
}
}

Wyświetl plik

@ -1,6 +1,5 @@
<template>
<div class="social__wrapper">
{{ type }}
<div class="social__container">
<transition name="slide-fade">
<div v-if="showInfo" class="social__welcome">