Signed-off-by: dartcafe <github@dartcafe.de>
This commit is contained in:
dartcafe 2024-05-10 17:26:49 +02:00
Родитель 0699ae72ad
Коммит abe4bd91b2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CCE73CEF3035D3C8
32 изменённых файлов: 450 добавлений и 320 удалений

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

@ -29,12 +29,6 @@ abstract class AppConstants {
/** @var string */
public const APP_ID = 'polls';
/** @var string */
public const SESSION_KEY_USER_ID = 'ncPollsUserId';
/** @var string */
public const SESSION_KEY_SHARE_TOKEN = 'ncPollsPublicToken';
/** @var string */
public const SESSION_KEY_SHARE_TYPE = 'ncPollsShareType';
/** @var string */
public const CLIENT_ID = 'ncPollsClientId';
/** @var string */
public const CLIENT_TZ = 'ncPollsClientTimeZone';

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

@ -74,6 +74,7 @@ use OCA\Polls\Middleware\RequestAttributesMiddleware;
use OCA\Polls\Model\Settings\AppSettings;
use OCA\Polls\Notification\Notifier;
use OCA\Polls\Provider\SearchProvider;
use OCA\Polls\UserSession;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
@ -81,10 +82,7 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\ISession;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\User\Events\UserDeletedEvent;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
@ -157,25 +155,21 @@ class Application extends App implements IBootstrap {
$context->registerService(UserMapper::class, function (ContainerInterface $c): UserMapper {
return new UserMapper(
$c->get(IDBConnection::class),
$c->get(ISession::class),
$c->get(IUserSession::class),
$c->get(IUserManager::class),
$c->get(LoggerInterface::class),
);
});
$context->registerService(AppSettings::class, function (ContainerInterface $c): AppSettings {
return new AppSettings(
$c->get(IConfig::class),
$c->get(IGroupManager::class),
$c->get(IUserSession::class),
$c->get(UserSession::class),
);
});
$context->registerService(PollMapper::class, function (ContainerInterface $c): PollMapper {
return new PollMapper(
$c->get(IDBConnection::class),
$c->get(UserMapper::class)
$c->get(UserSession::class),
);
});
@ -195,7 +189,7 @@ class Application extends App implements IBootstrap {
$context->registerService(OptionMapper::class, function (ContainerInterface $c): OptionMapper {
return new OptionMapper(
$c->get(IDBConnection::class),
$c->get(UserMapper::class),
$c->get(UserSession::class),
);
});

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

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2024 René Gieling <github@dartcafe.de>
*
* @author René Gieling <github@dartcafe.de>
*
* @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\Polls\Attribute;
use Attribute;
/**
* Attribute for permission check against Acl
*/
#[Attribute]
class ShareTokenRequired {
}

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

@ -26,17 +26,16 @@ declare(strict_types=1);
namespace OCA\Polls\Controller;
use Closure;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Exceptions\Exception;
use OCA\Polls\Exceptions\NoUpdatesException;
use OCA\Polls\Model\Acl;
use OCA\Polls\UserSession;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\ISession;
/**
* @psalm-api
@ -45,9 +44,9 @@ class BasePublicController extends Controller {
public function __construct(
string $appName,
IRequest $request,
protected ISession $session,
protected Acl $acl,
protected ShareMapper $shareMapper,
protected UserSession $userSession,
) {
parent::__construct($appName, $request);
}
@ -55,12 +54,9 @@ class BasePublicController extends Controller {
/**
* response
* @param Closure $callback Callback function
* @param string $token share token
*/
#[NoAdminRequired]
protected function response(Closure $callback, string $token): JSONResponse {
$this->updateSessionToken($token);
protected function response(Closure $callback): JSONResponse {
try {
return new JSONResponse($callback(), Http::STATUS_OK);
} catch (Exception $e) {
@ -71,12 +67,9 @@ class BasePublicController extends Controller {
/**
* response
* @param Closure $callback Callback function
* @param string $token share token
*/
#[NoAdminRequired]
protected function responseLong(Closure $callback, string $token): JSONResponse {
$this->updateSessionToken($token);
protected function responseLong(Closure $callback): JSONResponse {
try {
return new JSONResponse($callback(), Http::STATUS_OK);
} catch (NoUpdatesException $e) {
@ -86,27 +79,13 @@ class BasePublicController extends Controller {
/**
* responseCreate
* @param Closure $callback Callback function
* @param string $token share token
*/
#[NoAdminRequired]
protected function responseCreate(Closure $callback, string $token): JSONResponse {
$this->updateSessionToken($token);
protected function responseCreate(Closure $callback): JSONResponse {
try {
return new JSONResponse($callback(), Http::STATUS_CREATED);
} catch (Exception $e) {
return new JSONResponse(['message' => $e->getMessage()], $e->getStatus());
}
}
private function updateSessionToken(string $token): void {
if (!$token) {
$this->session->remove(AppConstants::SESSION_KEY_SHARE_TOKEN);
$this->session->remove(AppConstants::SESSION_KEY_SHARE_TYPE);
return;
}
$share = $this->shareMapper->findByToken($token);
$this->session->set(AppConstants::SESSION_KEY_SHARE_TOKEN, $token);
$this->session->set(AppConstants::SESSION_KEY_SHARE_TYPE, $share->getType());
}
}

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

@ -25,9 +25,9 @@ declare(strict_types=1);
namespace OCA\Polls\Controller;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Service\CalendarService;
use OCA\Polls\Service\PreferencesService;
use OCA\Polls\UserSession;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
@ -43,7 +43,7 @@ class PreferencesController extends BaseController {
IRequest $request,
private PreferencesService $preferencesService,
private CalendarService $calendarService,
private UserMapper $userMapper,
private UserSession $userSession,
) {
parent::__construct($appName, $request);
}
@ -63,7 +63,7 @@ class PreferencesController extends BaseController {
*/
#[NoAdminRequired]
public function write(array $preferences): JSONResponse {
if (!$this->userMapper->getCurrentUser()->getIsLoggedIn()) {
if (!$this->userSession->getIsLoggedIn()) {
return new JSONResponse([], Http::STATUS_OK);
}
return $this->response(fn () => $this->preferencesService->write($preferences));

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

@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\Polls\Controller;
use OCA\Polls\AppConstants;
use OCA\Polls\Attribute\ShareTokenRequired;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Model\Acl;
use OCA\Polls\Service\CommentService;
@ -36,14 +37,13 @@ use OCA\Polls\Service\SubscriptionService;
use OCA\Polls\Service\SystemService;
use OCA\Polls\Service\VoteService;
use OCA\Polls\Service\WatchService;
use OCA\Polls\UserSession;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUserSession;
use OCP\Util;
/**
@ -57,10 +57,9 @@ class PublicController extends BasePublicController {
public function __construct(
string $appName,
IRequest $request,
ISession $session,
Acl $acl,
ShareMapper $shareMapper,
private IUserSession $userSession,
UserSession $userSession,
private CommentService $commentService,
private MailService $mailService,
private OptionService $optionService,
@ -70,7 +69,7 @@ class PublicController extends BasePublicController {
private VoteService $voteService,
private WatchService $watchService
) {
parent::__construct($appName, $request, $session, $acl, $shareMapper);
parent::__construct($appName, $request, $acl, $shareMapper, $userSession);
}
/**
@ -78,9 +77,10 @@ class PublicController extends BasePublicController {
*/
#[PublicPage]
#[NoCSRFRequired]
#[ShareTokenRequired]
public function votePage() {
Util::addScript(AppConstants::APP_ID, 'polls-main');
if ($this->userSession->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return new TemplateResponse(AppConstants::APP_ID, 'main');
} else {
$template = new PublicTemplateResponse(AppConstants::APP_ID, 'main');
@ -91,10 +91,10 @@ class PublicController extends BasePublicController {
/**
* get complete poll via token
* @param string $token Share token
*/
#[PublicPage]
public function getPoll(string $token): JSONResponse {
#[ShareTokenRequired]
public function getPoll(): JSONResponse {
return $this->response(function () {
$this->acl->request(Acl::PERMISSION_POLL_VIEW);
// load poll through acl
@ -102,19 +102,19 @@ class PublicController extends BasePublicController {
'acl' => $this->acl,
'poll' => $this->acl->getPoll(),
];
}, $token);
});
}
/**
* Watch poll for updates
* @param string $token Share token
* @param ?int $offset only watch changes after this timestamp
*/
#[PublicPage]
public function watchPoll(string $token, ?int $offset): JSONResponse {
#[ShareTokenRequired]
public function watchPoll(?int $offset): JSONResponse {
return $this->responseLong(fn () => [
'updates' => $this->watchService->watchUpdates(offset: $offset)
], $token);
]);
}
/**
@ -122,190 +122,193 @@ class PublicController extends BasePublicController {
* @param string $token Share token
*/
#[PublicPage]
#[ShareTokenRequired]
public function getShare(string $token): JSONResponse {
return $this->response(fn () => [
'share' => $this->shareService->request($token)
], $token);
]);
}
/**
* Get votes
* @param string $token Share token
*/
#[PublicPage]
public function getVotes(string $token): JSONResponse {
#[ShareTokenRequired]
public function getVotes(): JSONResponse {
return $this->response(fn () => [
'votes' => $this->voteService->list()
], $token);
]);
}
/**
* Delete current user's votes
* @param string $token Share token
*/
#[PublicPage]
public function deleteUser(string $token): JSONResponse {
#[ShareTokenRequired]
public function deleteUser(): JSONResponse {
return $this->response(fn () => [
'deleted' => $this->voteService->deleteCurrentUserFromPoll()
], $token);
]);
}
/**
* Delete current user's orphaned votes
* @param string $token Share token
*/
#[PublicPage]
public function deleteOrphanedVotes(string $token): JSONResponse {
#[ShareTokenRequired]
public function deleteOrphanedVotes(): JSONResponse {
return $this->response(fn () => [
'deleted' => $this->voteService->deleteCurrentUserFromPoll(deleteOnlyOrphaned: true)
], $token);
]);
}
/**
* Get options
* @param string $token Share token
*/
#[PublicPage]
public function getOptions(string $token): JSONResponse {
#[ShareTokenRequired]
public function getOptions(): JSONResponse {
return $this->response(fn () => [
'options' => $this->optionService->list()
], $token);
]);
}
/**
* Add options
* @param string $token Share token
* @param int $timestamp timestamp for datepoll
* @param string $text Option text for text poll
* @param int duration duration of option
*/
#[PublicPage]
public function addOption(string $token, int $timestamp = 0, string $text = '', int $duration = 0): JSONResponse {
#[ShareTokenRequired]
public function addOption(int $timestamp = 0, string $text = '', int $duration = 0): JSONResponse {
return $this->responseCreate(fn () => [
'option' => $this->optionService->addForCurrentPoll(
timestamp: $timestamp,
pollOptionText: $text,
duration: $duration,
)
], $token);
]);
}
/**
* Delete option
* @param string $token Share token
* @param int $optionId Option Id to delete
*/
#[PublicPage]
public function deleteOption(string $token, int $optionId): JSONResponse {
#[ShareTokenRequired]
public function deleteOption(int $optionId): JSONResponse {
return $this->response(fn () => [
'option' => $this->optionService->delete($optionId)
], $token);
]);
}
/**
* Restore option
* @param string $token Share token
* @param int $optionId Option Id to restore
*/
#[PublicPage]
public function restoreOption(string $token, int $optionId): JSONResponse {
#[ShareTokenRequired]
public function restoreOption(int $optionId): JSONResponse {
return $this->response(fn () => [
'option' => $this->optionService->delete($optionId, true)
], $token);
]);
}
/**
* Set Vote
* @param int $optionId poll id
* @param string $setTo Answer string
* @param string $token Share token
*/
#[PublicPage]
public function setVote(int $optionId, string $setTo, string $token): JSONResponse {
#[ShareTokenRequired]
public function setVote(int $optionId, string $setTo): JSONResponse {
return $this->response(fn () => [
'vote' => $this->voteService->set($optionId, $setTo)
], $token);
]);
}
/**
* Get Comments
* @param string $token Share token
*/
#[PublicPage]
public function getComments(string $token): JSONResponse {
#[ShareTokenRequired]
public function getComments(): JSONResponse {
return $this->response(fn () => [
'comments' => $this->commentService->list()
], $token);
]);
}
/**
* Write a new comment to the db and returns the new comment as array
* @param string $token Share token
* @param string $message Comment text to add
*/
#[PublicPage]
public function addComment(string $token, string $message): JSONResponse {
#[ShareTokenRequired]
public function addComment(string $message): JSONResponse {
return $this->response(fn () => [
'comment' => $this->commentService->add($message)
], $token);
]);
}
/**
* Delete Comment
* @param string $token Share token
* @param int $commentId Id of comment to delete
*/
#[PublicPage]
public function deleteComment(int $commentId, string $token): JSONResponse {
#[ShareTokenRequired]
public function deleteComment(int $commentId): JSONResponse {
$comment = $this->commentService->get($commentId);
return $this->response(fn () => [
'comment' => $this->commentService->delete($comment)
], $token);
]);
}
/**
* Restore deleted Comment
* @param string $token Share token
* @param int $commentId Id of comment to restore
*/
#[PublicPage]
public function restoreComment(int $commentId, string $token): JSONResponse {
#[ShareTokenRequired]
public function restoreComment(int $commentId): JSONResponse {
$comment = $this->commentService->get($commentId);
return $this->response(fn () => [
'comment' => $this->commentService->delete($comment, true)
], $token);
]);
}
/**
* Get subscription status
* @param string $token Share token
*/
#[PublicPage]
public function getSubscription(string $token): JSONResponse {
#[ShareTokenRequired]
public function getSubscription(): JSONResponse {
return $this->response(fn () => [
'subscribed' => $this->subscriptionService->get()
], $token);
]);
}
/**
* subscribe
* @param string $token Share token
*/
#[PublicPage]
public function subscribe(string $token): JSONResponse {
#[ShareTokenRequired]
public function subscribe(): JSONResponse {
return $this->response(fn () => [
'subscribed' => $this->subscriptionService->set(true)
], $token);
]);
}
/**
* Unsubscribe
* @param string $token Share token
*/
#[PublicPage]
public function unsubscribe(string $token): JSONResponse {
#[ShareTokenRequired]
public function unsubscribe(): JSONResponse {
return $this->response(fn () => [
'subscribed' => $this->subscriptionService->set(false)
], $token);
]);
}
/**
@ -315,22 +318,23 @@ class PublicController extends BasePublicController {
* @param string $token Share token
*/
#[PublicPage]
#[ShareTokenRequired]
public function validatePublicDisplayName(string $displayName, string $token): JSONResponse {
return $this->response(fn () => [
'name' => $this->systemService->validatePublicUsernameByToken($displayName, $token)
], $token);
]);
}
/**
* Validate email address (simple validation)
* @param string $emailAddress Email address string to check for validation
* @param string $token Share token
*/
#[PublicPage]
public function validateEmailAddress(string $emailAddress, string $token = ''): JSONResponse {
#[ShareTokenRequired]
public function validateEmailAddress(string $emailAddress): JSONResponse {
return $this->response(fn () => [
'result' => MailService::validateEmailAddress($emailAddress), 'emailAddress' => $emailAddress
], $token);
]);
}
/**
@ -339,10 +343,11 @@ class PublicController extends BasePublicController {
* @param string $token Share token
*/
#[PublicPage]
#[ShareTokenRequired]
public function setDisplayName(string $token, string $displayName): JSONResponse {
return $this->response(fn () => [
'share' => $this->shareService->setDisplayname($displayName, $token)
], $token);
]);
}
@ -352,10 +357,11 @@ class PublicController extends BasePublicController {
* @param string $emailAddress New email address
*/
#[PublicPage]
#[ShareTokenRequired]
public function setEmailAddress(string $token, string $emailAddress = ''): JSONResponse {
return $this->response(fn () => [
'share' => $this->shareService->setEmailAddress($this->shareService->get($token), $emailAddress)
], $token);
]);
}
/**
@ -363,10 +369,11 @@ class PublicController extends BasePublicController {
* @param string $token Share token
*/
#[PublicPage]
#[ShareTokenRequired]
public function deleteEmailAddress(string $token): JSONResponse {
return $this->response(fn () => [
'share' => $this->shareService->deleteEmailAddress($this->shareService->get($token))
], $token);
]);
}
/**
@ -378,10 +385,11 @@ class PublicController extends BasePublicController {
* @param string $timeZone timezone string
*/
#[PublicPage]
#[ShareTokenRequired]
public function register(string $token, string $displayName, string $emailAddress = '', string $timeZone = ''): JSONResponse {
return $this->responseCreate(fn () => [
'share' => $this->shareService->register($token, $displayName, $emailAddress, $timeZone),
], $token);
]);
}
/**
@ -390,11 +398,12 @@ class PublicController extends BasePublicController {
* @param string $token Share token
*/
#[PublicPage]
#[ShareTokenRequired]
public function resendInvitation(string $token): JSONResponse {
$share = $this->shareService->get($token);
return $this->response(fn () => [
'share' => $share,
'sentResult' => $this->mailService->sendInvitation($share)
], $token);
]);
}
}

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

@ -27,6 +27,7 @@ namespace OCA\Polls\Db;
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\UserBase;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\Entity;
/**
@ -60,7 +61,7 @@ abstract class EntityWithUser extends Entity {
* Anonymized the user completely (ANON_FULL) or just strips out personal information
*/
public function getAnonymizeLevel(): string {
$currentUserId = Container::queryClass(UserMapper::class)->getCurrentUser()->getId();
$currentUserId = Container::queryClass(UserSession::class)->getCurrentUserId();
// Don't censor for poll owner or it is the current user's entity
if ($this->getPollOwnerId() === $currentUserId || $this->getUserId() === $currentUserId) {
return self::ANON_NONE;

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

@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\Polls\Db;
use OCA\Polls\UserSession;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@ -41,7 +42,7 @@ class OptionMapper extends QBMapperWithUser {
*/
public function __construct(
IDBConnection $db,
private UserMapper $userMapper,
private UserSession $userSession,
) {
parent::__construct($db, Option::TABLE, Option::class);
}
@ -170,7 +171,7 @@ class OptionMapper extends QBMapperWithUser {
* @param bool $hideVotes Whether the votes should be hidden, skips vote counting
*/
protected function buildQuery(bool $hideVotes = false): IQueryBuilder {
$currentUserId = $this->userMapper->getCurrentUser()->getId();
$currentUserId = $this->userSession->getCurrentUserId();
$qb = $this->db->getQueryBuilder();
$qb->select(self::TABLE . '.*')

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

@ -31,7 +31,7 @@ use JsonSerializable;
use OCA\Polls\AppConstants;
use OCA\Polls\Exceptions\NoDeadLineException;
use OCA\Polls\Helper\Container;
use OCP\ISession;
use OCA\Polls\UserSession;
use OCP\IURLGenerator;
/**
@ -123,8 +123,7 @@ class Poll extends EntityWithUser implements JsonSerializable {
private IURLGenerator $urlGenerator;
protected UserMapper $userMapper;
private ISession $session;
protected UserSession $userSession;
// schema columns
public $id = null;
@ -187,8 +186,7 @@ class Poll extends EntityWithUser implements JsonSerializable {
$this->addType('currentUserCountOrphanedVotes', 'int');
$this->urlGenerator = Container::queryClass(IURLGenerator::class);
$this->userMapper = Container::queryClass(UserMapper::class);
$this->session = Container::queryClass(ISession::class);
$this->userSession = Container::queryClass(UserSession::class);
}
/**
@ -268,14 +266,14 @@ class Poll extends EntityWithUser implements JsonSerializable {
}
public function getUserRole(): string {
if ($this->userMapper->getCurrentUser()->getId() === $this->getOwner()) {
if ($this->userSession->getCurrentUserId() === $this->getOwner()) {
return self::ROLE_OWNER;
}
if ($this->getIsCurrentUserLocked() && $this->userRole === self::ROLE_ADMIN) {
return self::ROLE_USER;
}
if ($this->session->get(AppConstants::SESSION_KEY_SHARE_TYPE) === Share::TYPE_PUBLIC) {
if ($this->userSession->getShareType() === Share::TYPE_PUBLIC) {
return Share::TYPE_PUBLIC;
}

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

@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\Polls\Db;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
@ -43,7 +44,7 @@ class PollMapper extends QBMapper {
*/
public function __construct(
IDBConnection $db,
private UserMapper $userMapper,
private UserSession $userSession,
) {
parent::__construct($db, Poll::TABLE, Poll::class);
}
@ -171,7 +172,7 @@ class PollMapper extends QBMapper {
* Build the enhanced query with joined tables
*/
protected function buildQuery(): IQueryBuilder {
$currentUserId = $this->userMapper->getCurrentUser()->getId();
$currentUserId = $this->userSession->getCurrentUserId();
$qb = $this->db->getQueryBuilder();
$qb->select(self::TABLE . '.*')

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

@ -27,7 +27,6 @@ namespace OCA\Polls\Db;
use JsonSerializable;
use OCA\Polls\AppConstants;
use OCA\Polls\Model\Settings\AppSettings;
use OCP\IURLGenerator;
use OCP\Server;
@ -120,7 +119,6 @@ class Share extends EntityWithUser implements JsonSerializable {
];
protected IURLGenerator $urlGenerator;
protected AppSettings $appSettings;
// schema columns
public $id = null;
@ -147,7 +145,6 @@ class Share extends EntityWithUser implements JsonSerializable {
$this->addType('reminderSent', 'int');
$this->addType('deleted', 'int');
$this->urlGenerator = Server::get(IURLGenerator::class);
$this->appSettings = Server::get(AppSettings::class);
}
/**
@ -168,7 +165,6 @@ class Share extends EntityWithUser implements JsonSerializable {
'locked' => boolval($this->getDeleted() ? 0 : $this->getLocked()),
'label' => $this->getLabel(),
'URL' => $this->getURL(),
'showLogin' => $this->appSettings->getBooleanSetting(AppSettings::SETTING_SHOW_LOGIN),
'publicPollEmail' => $this->getPublicPollEmail(),
'voted' => boolval($this->getVoted()),
'deleted' => boolval($this->getDeleted()),

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

@ -26,7 +26,6 @@ declare(strict_types=1);
namespace OCA\Polls\Db;
use Exception;
use OCA\Polls\AppConstants;
use OCA\Polls\Exceptions\InvalidShareTypeException;
use OCA\Polls\Exceptions\ShareNotFoundException;
use OCA\Polls\Exceptions\UserNotFoundException;
@ -44,11 +43,8 @@ use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\ISession;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
/**
* @template-extends QBMapper<Share>
@ -57,67 +53,17 @@ use Psr\Log\LoggerInterface;
*/
class UserMapper extends QBMapper {
public const TABLE = Share::TABLE;
protected ?UserBase $currentUser = null;
/**
* @psalm-suppress PossiblyUnusedMethod
*/
public function __construct(
IDBConnection $db,
protected ISession $session,
protected IUserSession $userSession,
protected IUserManager $userManager,
protected LoggerInterface $logger,
) {
parent::__construct($db, Share::TABLE, Share::class);
}
/**
* Get current user
*
* Returns a UserBase child for the current (share|nextcloud) user based on
* - the session stored share token or
* - the user session stored userId
* and stores userId to session
*
*/
public function getCurrentUser(): UserBase {
if ($this->userSession->isLoggedIn()) {
$userId = $this->userSession->getUser()?->getUID();
if ($userId === null) {
throw new UserNotFoundException('User is reported to be logged in but has no user id');
}
if (!$this->currentUser || $userId !== $this->currentUser->getId()) {
$this->currentUser = $this->getUserFromUserBase($userId);
}
} else {
try {
$this->currentUser = $this->getUserFromShareToken($this->getSessionStoredShareToken());
} catch (DoesNotExistException $e) {
$this->logger->debug('no user found, returned fake user');
$this->currentUser = new GenericUser('', Share::TYPE_PUBLIC);
}
}
return $this->currentUser;
}
public function getCurrentUserId(): string {
return $this->getCurrentUser()->getId();
}
public function getCurrentUserCached(): UserBase {
return $this->currentUser ?? $this->getCurrentUser();
}
public function getSessionStoredShareToken(): string {
return (string) $this->session->get(AppConstants::SESSION_KEY_SHARE_TOKEN);
}
/**
* Get poll participant
*
@ -197,11 +143,9 @@ class UserMapper extends QBMapper {
);
}
private function getUserFromShareToken(string $token): UserBase {
public function getUserFromShareToken(string $token): UserBase {
$share = $this->getShareByToken($token);
if ($share->getType() === Share::TYPE_PUBLIC) {
throw new DoesNotExistException('Share type is <Share::' . $share->getType() . '> and has no user.');
}
return $this->getUserFromShare($share);
}

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

@ -26,9 +26,9 @@ declare(strict_types=1);
namespace OCA\Polls\Db;
use OCA\Polls\AppConstants;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\QBMapper;
use OCP\IDBConnection;
use OCP\ISession;
/**
* @template-extends QBMapper<Watch>
@ -41,7 +41,7 @@ class WatchMapper extends QBMapper {
*/
public function __construct(
IDBConnection $db,
protected ISession $session
protected UserSession $userSession,
) {
parent::__construct($db, Watch::TABLE, Watch::class);
}
@ -57,7 +57,7 @@ class WatchMapper extends QBMapper {
->from($this->getTableName())
->where($qb->expr()->gt('updated', $qb->createNamedParameter($offset)))
->andWhere(
$qb->expr()->neq('session_id', $qb->createNamedParameter(hash('md5', $this->session->get(AppConstants::CLIENT_ID))))
$qb->expr()->neq('session_id', $qb->createNamedParameter($this->userSession->getClientIdHashed()))
)
->andWhere($qb->expr()->orX(
$qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId)),
@ -83,7 +83,7 @@ class WatchMapper extends QBMapper {
$qb->expr()->eq('table', $qb->createNamedParameter($table))
)
->andWhere(
$qb->expr()->eq('session_id', $qb->createNamedParameter(hash('md5', $this->session->get(AppConstants::CLIENT_ID))))
$qb->expr()->eq('session_id', $qb->createNamedParameter($this->userSession->getClientIdHashed()))
);
return $this->findEntity($qb);

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

@ -30,6 +30,7 @@ use OCA\Polls\Db\Share;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Db\Vote;
use OCA\Polls\Helper\Container;
use OCA\Polls\UserSession;
use OCP\EventDispatcher\Event;
abstract class BaseEvent extends Event {
@ -39,6 +40,7 @@ abstract class BaseEvent extends Event {
protected bool $log = true;
protected Poll $poll;
protected UserMapper $userMapper;
protected UserSession $userSession;
public function __construct(
@ -47,6 +49,7 @@ abstract class BaseEvent extends Event {
parent::__construct();
$this->poll = Container::queryPoll($this->getPollId());
$this->userMapper = Container::queryClass(UserMapper::class);
$this->userSession = Container::queryClass(UserSession::class);
// Default
$this->activitySubjectParams['pollTitle'] = [
@ -70,8 +73,8 @@ abstract class BaseEvent extends Event {
}
public function getActor(): string {
if ($this->userMapper->getCurrentUserCached()->getId() !== '') {
return $this->userMapper->getCurrentUserCached()->getId();
if ($this->userSession->getCurrentUserId() !== '') {
return $this->userSession->getCurrentUserId();
}
return $this->eventObject->getUserId();
}

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

@ -2,9 +2,9 @@
namespace OCA\Polls\Middleware;
use OCA\Polls\AppConstants;
use OCA\Polls\Attribute\ShareTokenRequired;
use OCA\Polls\UserSession;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Middleware;
use OCP\IRequest;
use OCP\ISession;
@ -21,32 +21,49 @@ class RequestAttributesMiddleware extends Middleware {
public function __construct(
protected IRequest $request,
protected ISession $session,
protected UserSession $userSession,
) {
}
public function beforeController(Controller $controller, string $methodName): void {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$clientId = $this->request->getHeader(self::CLIENT_ID_KEY);
$clientTimeZone = $this->request->getHeader(self::TIME_ZONE_KEY);
// $shareToken = $this->request->getHeader(self::SHARE_TOKEN);
$this->userSession->cleanSession();
if (!$clientId) {
$clientId = $this->session->getId();
}
if ($clientId) {
$this->session->set(AppConstants::CLIENT_ID, $clientId);
$this->userSession->setClientId($clientId);
}
if ($clientTimeZone) {
$this->session->set(AppConstants::CLIENT_TZ, $clientTimeZone);
$this->userSession->setClientTimeZone($clientTimeZone);
}
if ($this->hasAttribute($reflectionMethod, ShareTokenRequired::class)) {
$this->userSession->setShareToken($this->getShareTokenFromURI());
}
}
private function getShareTokenFromURI(): string {
$uri = "$_SERVER[REQUEST_URI]";
$pattern = '/\/s\/(.*?)(\/|$)/';
if (preg_match($pattern, $uri, $matches)) {
return $matches[1];
}
if (!$this->hasAttribute($reflectionMethod, PublicPage::class)) {
// authenticated session don't use share tokens
$this->session->remove(AppConstants::SESSION_KEY_SHARE_TOKEN);
$this->session->remove(AppConstants::SESSION_KEY_SHARE_TYPE);
// Fallback: check sessionToken in header
if ($this->request->getHeader(self::SHARE_TOKEN)) {
return $this->request->getHeader(self::SHARE_TOKEN);
}
return '';
}
/**
* @template T

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

@ -27,16 +27,14 @@ declare(strict_types=1);
namespace OCA\Polls\Model;
use JsonSerializable;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\Poll;
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Exceptions\ForbiddenException;
use OCA\Polls\Exceptions\InsufficientAttributesException;
use OCA\Polls\Model\Settings\AppSettings;
use OCP\ISession;
use OCA\Polls\UserSession;
/**
* Class Acl
@ -65,7 +63,6 @@ class Acl implements JsonSerializable {
public const PERMISSION_ALL_ACCESS = 'allAccess';
private ?int $pollId = null;
private ?UserBase $currentUser = null;
/**
* @psalm-suppress PossiblyUnusedMethod
@ -73,11 +70,9 @@ class Acl implements JsonSerializable {
public function __construct(
private AppSettings $appSettings,
private PollMapper $pollMapper,
private ISession $session,
private ShareMapper $shareMapper,
private UserMapper $userMapper,
private UserSession $userSession,
private Poll $poll,
private Share $share,
) {
}
@ -119,8 +114,8 @@ class Acl implements JsonSerializable {
'displayName' => $this->getCurrentUser()->getDisplayName(),
'hasVoted' => $this->getIsParticipant(),
'isInvolved' => $this->getIsInvolved(),
'isLoggedIn' => $this->getCurrentUser()->getIsLoggedIn(),
'isNoUser' => !$this->getCurrentUser()->getIsLoggedIn(),
'isLoggedIn' => $this->userSession->getIsLoggedIn(),
'isNoUser' => !$this->userSession->getIsLoggedIn(),
'isOwner' => $this->getIsPollOwner(),
'userId' => $this->getUserId(),
];
@ -142,7 +137,7 @@ class Acl implements JsonSerializable {
* @throws InsufficientAttributesException Thrown if stored pollId is null
*/
public function getPoll(): Poll {
if ($this->isSessionStoredShareTokenSet()) {
if ($this->userSession->hasShare()) {
// if a share token is set, force usage of the share's pollId
$this->pollId = $this->getShare()->getPollId();
}
@ -163,30 +158,15 @@ class Acl implements JsonSerializable {
* Get share
* load share from db by session stored token or rely on cached share
*/
private function getShare(): Share {
if ($this->isSessionStoredShareTokenSet() &&
$this->getSessionStoredShareToken() !== $this->share->getToken()) {
// Session stored token differs from share's token,
// reload share from db for session stored share token
$this->share = $this->shareMapper->findByToken((string) $this->getSessionStoredShareToken());
}
return $this->share;
}
private function isSessionStoredShareTokenSet(): bool {
return boolval($this->getSessionStoredShareToken());
private function getShare(): Share|null {
return $this->userSession->getShare();
}
/**
* loads the current user from the userMapper or returns the cached one
* loads the current user from the userSession or returns the cached one
*/
private function getCurrentUser(): UserBase {
$this->currentUser = $this->userMapper->getCurrentUser();
return $this->currentUser;
}
private function getSessionStoredShareToken(): ?string {
return $this->session->get(AppConstants::SESSION_KEY_SHARE_TOKEN);
return $this->userSession->getUser();
}
/**
@ -257,7 +237,7 @@ class Acl implements JsonSerializable {
* Check, if poll settings is set to open access for internal users
*/
private function getIsOpenPoll(): bool {
return $this->getPoll()->getAccess() === Poll::ACCESS_OPEN && $this->getCurrentUser()->getIsLoggedIn();
return $this->getPoll()->getAccess() === Poll::ACCESS_OPEN && $this->userSession->getIsLoggedIn();
}
/**
@ -274,7 +254,7 @@ class Acl implements JsonSerializable {
* @return bool Returns true, if the current poll contains a group share with a group,
*/
private function getIsInvitedViaGroupShare(): bool {
if (!$this->getCurrentUser()->getIsLoggedIn()) {
if (!$this->userSession->getIsLoggedIn()) {
return false;
}
@ -351,7 +331,7 @@ class Acl implements JsonSerializable {
}
// grant access if poll poll is an open poll (for logged in users)
if ($this->getIsOpenPoll() && $this->getCurrentUser()->getIsLoggedIn()) {
if ($this->getIsOpenPoll() && $this->userSession->getIsLoggedIn()) {
return true;
}

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

@ -28,9 +28,8 @@ namespace OCA\Polls\Model\Settings;
use JsonSerializable;
use OCA\Polls\AppConstants;
use OCA\Polls\Model\Group\Group;
use OCA\Polls\UserSession;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserSession;
class AppSettings implements JsonSerializable {
public const SETTING_ALLOW_PUBLIC_SHARES = 'allowPublicShares';
@ -64,15 +63,11 @@ class AppSettings implements JsonSerializable {
public const SETTING_UPDATE_TYPE_PERIODIC_POLLING = 'periodicPolling';
public const SETTING_UPDATE_TYPE_DEFAULT = self::SETTING_UPDATE_TYPE_NO_POLLING;
private string $userId = '';
public function __construct(
private IConfig $config,
private IGroupManager $groupManager,
private IUserSession $session,
private UserSession $userSession,
) {
$this->userId = $this->session->getUser()?->getUId() ?? '';
}
// Getters
@ -98,7 +93,7 @@ class AppSettings implements JsonSerializable {
* Poll creation permission is controlled by app settings
*/
public function getPollCreationAllowed(): bool {
if ($this->session->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getBooleanSetting(self::SETTING_ALLOW_POLL_CREATION) || $this->isMember($this->getGroupSetting(self::SETTING_POLL_CREATION_GROUPS));
}
return false;
@ -108,7 +103,7 @@ class AppSettings implements JsonSerializable {
* Permission to see emailaddresses is controlled by app settings
*/
public function getAllowSeeMailAddresses(): bool {
if ($this->session->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getBooleanSetting(self::SETTING_SHOW_MAIL_ADDRESSES) || $this->isMember($this->getGroupSetting(self::SETTING_SHOW_MAIL_ADDRESSES_GROUPS));
}
return false;
@ -118,7 +113,7 @@ class AppSettings implements JsonSerializable {
* Permission to download emailaddresses is controlled by app settings
*/
public function getPollDownloadAllowed(): bool {
if ($this->session->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getBooleanSetting(self::SETTING_ALLOW_POLL_DOWNLOAD) || $this->isMember($this->getGroupSetting(self::SETTING_POLL_DOWNLOAD_GROUPS));
}
return false;
@ -128,7 +123,7 @@ class AppSettings implements JsonSerializable {
* Permission to share polls with all internal users is controlled by app settings (open poll)
*/
public function getAllAccessAllowed(): bool {
if ($this->session->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getBooleanSetting(self::SETTING_ALLOW_ALL_ACCESS) || $this->isMember($this->getGroupSetting(self::SETTING_ALL_ACCESS_GROUPS));
}
return false;
@ -138,7 +133,7 @@ class AppSettings implements JsonSerializable {
* Permission to create public shares is controlled by app settings
*/
public function getPublicSharesAllowed(): bool {
if ($this->session->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getBooleanSetting(self::SETTING_ALLOW_PUBLIC_SHARES) || $this->isMember($this->getGroupSetting(self::SETTING_PUBLIC_SHARES_GROUPS));
}
return false;
@ -148,7 +143,7 @@ class AppSettings implements JsonSerializable {
* Permission to combine polls is controlled by app settings and only for internal users
*/
public function getComboAllowed(): bool {
if ($this->session->isLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getBooleanSetting(self::SETTING_ALLOW_COMBO)
|| $this->isMember($this->getGroupSetting(self::SETTING_COMBO_GROUPS));
}
@ -261,7 +256,7 @@ class AppSettings implements JsonSerializable {
private function isMember(array $groups): bool {
foreach ($groups as $GID) {
if ($this->groupManager->isInGroup($this->userId, $GID)) {
if ($this->userSession->getUser()->getIsInGroup($GID)) {
return true;
}
}

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

@ -54,7 +54,7 @@ class Email extends UserBase {
* @psalm-suppress PossiblyUnusedMethod
*/
public function jsonSerialize(): array {
if ($this->userMapper->getCurrentUserCached()->getIsLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getRichUserArray();
}
return $this->getSimpleUserArray();

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

@ -28,7 +28,6 @@ namespace OCA\Polls\Model;
use DateTimeZone;
use JsonSerializable;
use OCA\Polls\Db\EntityWithUser;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\Group\Circle;
use OCA\Polls\Model\Group\ContactGroup;
@ -39,11 +38,11 @@ use OCA\Polls\Model\User\Contact;
use OCA\Polls\Model\User\Email;
use OCA\Polls\Model\User\Ghost;
use OCA\Polls\Model\User\User;
use OCA\Polls\UserSession;
use OCP\Collaboration\Collaborators\ISearch;
use OCP\IDateTimeZone;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUserSession;
use OCP\Server;
use OCP\Share\IShare;
@ -75,8 +74,7 @@ class UserBase implements JsonSerializable {
protected IDateTimeZone $timeZone;
protected IGroupManager $groupManager;
protected IL10N $l10n;
protected IUserSession $userSession;
protected UserMapper $userMapper;
protected UserSession $userSession;
protected AppSettings $appSettings;
public function __construct(
@ -92,8 +90,7 @@ class UserBase implements JsonSerializable {
$this->l10n = Container::getL10N();
$this->groupManager = Server::get(IGroupManager::class);
$this->timeZone = Server::get(IDateTimeZone::class);
$this->userMapper = Server::get(UserMapper::class);
$this->userSession = Server::get(IUserSession::class);
$this->userSession = Server::get(UserSession::class);
$this->appSettings = Server::get(AppSettings::class);
}
@ -385,7 +382,7 @@ class UserBase implements JsonSerializable {
}
// internal users may see the real userId
if ($this->getIsLoggedIn()) {
if ($this->userSession->getIsLoggedIn()) {
return $this->getId();
}
@ -421,12 +418,8 @@ class UserBase implements JsonSerializable {
return $this->organisation;
}
public function getIsLoggedIn(): bool {
return $this->userSession->isLoggedIn();
}
public function getIsCurrentUser(): bool {
return $this->getId() === $this->userMapper->getCurrentUserCached()->getId();
return $this->getId() === $this->userSession->getCurrentUserId();
}
public function getIsAdmin(): bool {

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

@ -27,13 +27,13 @@ namespace OCA\Polls\Service;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Event\BaseEvent;
use OCA\Polls\Event\CommentEvent;
use OCA\Polls\Event\OptionEvent;
use OCA\Polls\Event\PollEvent;
use OCA\Polls\Event\ShareEvent;
use OCA\Polls\Event\VoteEvent;
use OCA\Polls\UserSession;
use OCP\Activity\IEvent as ActivityEvent;
use OCP\Activity\IManager as ActivityManager;
use OCP\IL10N;
@ -58,7 +58,7 @@ class ActivityService {
private ActivityManager $activityManager,
private IL10N $l10n,
private IFactory $transFactory,
private UserMapper $userMapper,
private UserSession $userSession,
) {
}
@ -67,7 +67,7 @@ class ActivityService {
$this->l10n = $this->transFactory->get($this->activityEvent->getApp(), $language);
try {
$this->userIsActor = $this->activityEvent->getAuthor() === $this->userMapper->getCurrentUserCached()->getId();
$this->userIsActor = $this->activityEvent->getAuthor() === $this->userSession->getCurrentUserId();
} catch (\Exception $e) {
$this->userIsActor = false;
}

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

@ -30,14 +30,12 @@ use DateInterval;
use DateTime;
use DateTimeImmutable;
use DateTimeZone;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\Preferences;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Model\CalendarEvent;
use OCA\Polls\UserSession;
use OCP\Calendar\ICalendar;
use OCP\Calendar\IManager as CalendarManager;
use OCP\ISession;
use Psr\Log\LoggerInterface;
class CalendarService {
@ -49,11 +47,10 @@ class CalendarService {
*/
public function __construct(
private CalendarManager $calendarManager,
private ISession $session,
private OptionMapper $optionMapper,
private Preferences $preferences,
private PreferencesService $preferencesService,
private UserMapper $userMapper,
private UserSession $userSession,
private LoggerInterface $logger,
) {
$this->setUp();
@ -71,7 +68,7 @@ class CalendarService {
* getCalendars -
*/
private function getCalendarsForPrincipal(): void {
$principalUri = $this->userMapper->getCurrentUser()->getPrincipalUri();
$principalUri = $this->userSession->getUser()->getPrincipalUri();
if ($principalUri) {
$this->calendars = $this->calendarManager->getCalendarsForPrincipal($principalUri);
@ -109,11 +106,11 @@ class CalendarService {
}
private function searchEventsByTimeRange(DateTimeImmutable $from, DateTimeImmutable $to): array {
if (!$this->userMapper->getCurrentUser()->getPrincipalUri()) {
if (!$this->userSession->getUser()->getPrincipalUri()) {
return [];
}
$query = $this->calendarManager->newQuery($this->userMapper->getCurrentUser()->getPrincipalUri());
$query = $this->calendarManager->newQuery($this->userSession->getUser()->getPrincipalUri());
$query->setTimerangeStart($from);
$query->setTimerangeEnd($to);
@ -133,7 +130,7 @@ class CalendarService {
* @psalm-return list<CalendarEvent|null>
*/
public function getEvents(int $optionId): array {
$timezone = new DateTimeZone($this->session->get(AppConstants::CLIENT_TZ));
$timezone = new DateTimeZone($this->userSession->getClientTimeZone());
$timerange = $this->getTimerange($optionId, $timezone);
$events = [];

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

@ -27,7 +27,7 @@ namespace OCA\Polls\Service;
use OCA\Polls\Db\Log;
use OCA\Polls\Db\LogMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\UserSession;
class LogService {
/**
@ -36,7 +36,7 @@ class LogService {
public function __construct(
private LogMapper $logMapper,
private Log $log,
private UserMapper $userMapper,
private UserSession $userSession,
) {
}
@ -48,7 +48,7 @@ class LogService {
$this->log->setPollId($pollId);
$this->log->setCreated(time());
$this->log->setMessageId($messageId);
$this->log->setUserId($userId ?? $this->userMapper->getCurrentUser()->getId());
$this->log->setUserId($userId ?? $this->userSession->getCurrentUserId());
$this->logMapper->insert($this->log);
}

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

@ -27,8 +27,8 @@ namespace OCA\Polls\Service;
use DateTime;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Notification\Notifier;
use OCA\Polls\UserSession;
use OCP\Notification\IManager;
class NotificationService {
@ -37,13 +37,13 @@ class NotificationService {
*/
public function __construct(
protected IManager $notificationManager,
protected UserMapper $userMapper,
protected UserSession $userSession,
) {
}
public function removeNotification(int $pollId): void {
$notification = $this->notificationManager->createNotification();
$userId = $this->userMapper->getCurrentUser()->getId();
$userId = $this->userSession->getCurrentUserId();
$notification->setApp(AppConstants::APP_ID)
->setObject('poll', strval($pollId))

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

@ -26,7 +26,6 @@ declare(strict_types=1);
namespace OCA\Polls\Service;
use DateTimeZone;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\Option;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\Poll;
@ -39,10 +38,10 @@ use OCA\Polls\Event\PollOptionReorderedEvent;
use OCA\Polls\Exceptions\DuplicateEntryException;
use OCA\Polls\Exceptions\InvalidPollTypeException;
use OCA\Polls\Model\Acl;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ISession;
use Psr\Log\LoggerInterface;
class OptionService {
@ -58,7 +57,7 @@ class OptionService {
private LoggerInterface $logger,
private Option $option,
private OptionMapper $optionMapper,
private ISession $session,
private UserSession $userSession,
) {
$this->options = [];
}
@ -264,7 +263,7 @@ class OptionService {
return $this->optionMapper->findByPoll($this->option->getPollId());
}
$timezone = new DateTimeZone($this->session->get(AppConstants::CLIENT_TZ));
$timezone = new DateTimeZone($this->userSession->getClientTimeZone());
for ($i = 1; $i < ($amount + 1); $i++) {
$clonedOption = new Option();
@ -293,7 +292,7 @@ class OptionService {
*/
public function shift(int $pollId, int $step, string $unit): array {
$this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT);
$timezone = new DateTimeZone($this->session->get(AppConstants::CLIENT_TZ));
$timezone = new DateTimeZone($this->userSession->getClientTimeZone());
if ($this->acl->getPoll()->getType() !== Poll::TYPE_DATE) {
throw new InvalidPollTypeException('Shifting is only available in date polls');

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

@ -49,6 +49,7 @@ use OCA\Polls\Exceptions\UserNotFoundException;
use OCA\Polls\Model\Acl;
use OCA\Polls\Model\Settings\AppSettings;
use OCA\Polls\Model\UserBase;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Search\ISearchQuery;
@ -67,6 +68,7 @@ class PollService {
private Preferences $preferences,
private PreferencesService $preferencesService,
private UserMapper $userMapper,
private UserSession $userSession,
private VoteMapper $voteMapper,
) {
}
@ -77,7 +79,7 @@ class PollService {
public function list(): array {
$pollList = [];
try {
$polls = $this->pollMapper->findForMe($this->userMapper->getCurrentUserCached()->getId());
$polls = $this->pollMapper->findForMe($this->userSession->getCurrentUserId());
$this->preferences = $this->preferencesService->get();
foreach ($polls as $poll) {
try {
@ -133,9 +135,9 @@ class PollService {
*/
public function listForAdmin(): array {
$pollList = [];
if ($this->userMapper->getCurrentUserCached()->getIsAdmin()) {
if ($this->userSession->getUser()->getIsAdmin()) {
try {
$pollList = $this->pollMapper->findForAdmin($this->userMapper->getCurrentUserCached()->getId());
$pollList = $this->pollMapper->findForAdmin($this->userSession->getCurrentUserId());
} catch (DoesNotExistException $e) {
// silent catch
}
@ -149,7 +151,7 @@ class PollService {
*/
public function takeover(int $pollId, ?UserBase $targetUser = null): Poll {
if (!$targetUser) {
$targetUser = $this->userMapper->getCurrentUser();
$targetUser = $this->userSession->getUser();
}
$this->poll = $this->pollMapper->find($pollId);
@ -231,7 +233,7 @@ class PollService {
$this->poll = new Poll();
$this->poll->setType($type);
$this->poll->setCreated(time());
$this->poll->setOwner($this->userMapper->getCurrentUserCached()->getId());
$this->poll->setOwner($this->userSession->getCurrentUserId());
$this->poll->setTitle($title);
$this->poll->setDescription('');
$this->poll->setAccess(Poll::ACCESS_PRIVATE);
@ -379,7 +381,7 @@ class PollService {
$this->poll = new Poll();
$this->poll->setCreated(time());
$this->poll->setOwner($this->userMapper->getCurrentUserCached()->getId());
$this->poll->setOwner($this->userSession->getCurrentUserId());
$this->poll->setTitle('Clone of ' . $origin->getTitle());
$this->poll->setDeleted(0);
$this->poll->setAccess(Poll::ACCESS_PRIVATE);

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

@ -28,8 +28,8 @@ namespace OCA\Polls\Service;
use Exception;
use OCA\Polls\Db\Preferences;
use OCA\Polls\Db\PreferencesMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Exceptions\NotAuthorizedException;
use OCA\Polls\UserSession;
class PreferencesService {
@ -39,14 +39,14 @@ class PreferencesService {
public function __construct(
private PreferencesMapper $preferencesMapper,
private Preferences $preferences,
private UserMapper $userMapper,
private UserSession $userSession,
) {
$this->load();
}
public function load(): void {
try {
$this->preferences = $this->preferencesMapper->find($this->userMapper->getCurrentUser()->getId());
$this->preferences = $this->preferencesMapper->find($this->userSession->getCurrentUserId());
} catch (Exception $e) {
$this->preferences = new Preferences;
}
@ -60,13 +60,13 @@ class PreferencesService {
* Write references
*/
public function write(array $preferences): Preferences {
if (!$this->userMapper->getCurrentUserCached()->getId()) {
if (!$this->userSession->getCurrentUserId()) {
throw new NotAuthorizedException();
}
$this->preferences->setPreferences(json_encode($preferences));
$this->preferences->setTimestamp(time());
$this->preferences->setUserId($this->userMapper->getCurrentUserCached()->getId());
$this->preferences->setUserId($this->userSession->getCurrentUserId());
if ($this->preferences->getId() > 0) {
return $this->preferencesMapper->update($this->preferences);

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

@ -25,7 +25,6 @@ declare(strict_types=1);
namespace OCA\Polls\Service;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\UserMapper;
@ -47,10 +46,10 @@ use OCA\Polls\Exceptions\ShareNotFoundException;
use OCA\Polls\Model\Acl;
use OCA\Polls\Model\SentResult;
use OCA\Polls\Model\UserBase;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ISession;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
@ -65,7 +64,6 @@ class ShareService {
private LoggerInterface $logger,
private IEventDispatcher $eventDispatcher,
private ISecureRandom $secureRandom,
private ISession $session,
private ShareMapper $shareMapper,
private SystemService $systemService,
private Share $share,
@ -73,6 +71,7 @@ class ShareService {
private Acl $acl,
private NotificationService $notificationService,
private UserMapper $userMapper,
private UserSession $userSession,
) {
$this->shares = [];
}
@ -126,15 +125,15 @@ class ShareService {
try {
$this->share = $this->createNewShare(
$this->share->getPollId(),
$this->userMapper->getCurrentUserCached(),
$this->userSession->getUser(),
preventInvitation: true
);
} catch (ShareAlreadyExistsException $e) {
// replace share by existing personal share
$this->share = $e->getShare()
?? $this->share = $this->shareMapper->findByPollAndUser($this->share->getPollId(), $this->userMapper->getCurrentUserCached()->getId());
?? $this->share = $this->shareMapper->findByPollAndUser($this->share->getPollId(), $this->userSession->getCurrentUserId());
// remove the public token from session
$this->session->set(AppConstants::SESSION_KEY_SHARE_TOKEN, $this->share->getToken());
$this->userSession->setShareToken($this->share->getToken());
}
}
/**
@ -153,7 +152,7 @@ class ShareService {
}
// Exception: logged in user, accesses the poll via public share link
if ($this->share->getType() === Share::TYPE_PUBLIC && $this->userMapper->getCurrentUser()->getIsLoggedIn()) {
if ($this->share->getType() === Share::TYPE_PUBLIC && $this->userSession->getIsLoggedIn()) {
$this->convertPublicToPersonalShare();
}
return $this->share;
@ -535,10 +534,10 @@ class ShareService {
$valid = match ($this->share->getType()) {
Share::TYPE_PUBLIC, Share::TYPE_EMAIL, Share::TYPE_EXTERNAL => true,
Share::TYPE_USER => $this->share->getUserId() === $this->userMapper->getCurrentUser()->getId(),
Share::TYPE_ADMIN => $this->share->getUserId() === $this->userMapper->getCurrentUserCached()->getId(),
Share::TYPE_USER => $this->share->getUserId() === $this->userSession->getCurrentUserId(),
Share::TYPE_ADMIN => $this->share->getUserId() === $this->userSession->getCurrentUserId(),
// Note: $this->share->getUserId() is actually the group name in case of Share::TYPE_GROUP
Share::TYPE_GROUP => $this->userMapper->getCurrentUserCached()->getIsInGroup($this->share->getUserId()),
Share::TYPE_GROUP => $this->userSession->getUser()->getIsInGroup($this->share->getUserId()),
default => throw new ForbiddenException('Invalid share type ' . $this->share->getType()),
};
if (!$valid) {

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

@ -27,12 +27,12 @@ namespace OCA\Polls\Service;
use OCA\Polls\Db\Option;
use OCA\Polls\Db\OptionMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Db\Vote;
use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Event\VoteSetEvent;
use OCA\Polls\Exceptions\VoteLimitExceededException;
use OCA\Polls\Model\Acl;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\IEventDispatcher;
@ -46,7 +46,7 @@ class VoteService {
private OptionMapper $optionMapper,
private Vote $vote,
private VoteMapper $voteMapper,
private UserMapper $userMapper,
private UserSession $userSession,
) {
}
@ -63,7 +63,7 @@ class VoteService {
if (!$this->acl->getIsAllowed(Acl::PERMISSION_POLL_RESULTS_VIEW)) {
// Just return the participants votes, no further anoymizing or obfuscating is nessecary
return $this->voteMapper->findByPollAndUser($this->acl->getPoll()->getId(), ($this->userMapper->getCurrentUserId()));
return $this->voteMapper->findByPollAndUser($this->acl->getPoll()->getId(), ($this->userSession->getCurrentUserId()));
}
$votes = $this->voteMapper->findByPoll($this->acl->getPoll()->getId());
@ -101,7 +101,7 @@ class VoteService {
$deleteVoteInsteadOfNoVote = in_array(trim($setTo), [Vote::VOTE_NO, '']) && !boolval($this->acl->getPoll()->getUseNo());
try {
$this->vote = $this->voteMapper->findSingleVote($this->acl->getPoll()->getId(), $option->getPollOptionText(), $this->userMapper->getCurrentUserId());
$this->vote = $this->voteMapper->findSingleVote($this->acl->getPoll()->getId(), $option->getPollOptionText(), $this->userSession->getCurrentUserId());
if ($deleteVoteInsteadOfNoVote) {
$this->vote->setVoteAnswer('');
@ -115,7 +115,7 @@ class VoteService {
$this->vote = new Vote();
$this->vote->setPollId($this->acl->getPoll()->getId());
$this->vote->setUserId($this->userMapper->getCurrentUserId());
$this->vote->setUserId($this->userSession->getCurrentUserId());
$this->vote->setVoteOptionText($option->getPollOptionText());
$this->vote->setVoteOptionId($option->getId());
$this->vote->setVoteAnswer($setTo);
@ -136,7 +136,7 @@ class VoteService {
public function deleteCurrentUserFromPoll(bool $deleteOnlyOrphaned = false): string {
$this->acl->request(Acl::PERMISSION_VOTE_EDIT);
$pollId = $this->acl->getPoll()->getId();
$userId = $this->userMapper->getCurrentUser()->getId();
$userId = $this->userSession->getCurrentUserId();
return $this->delete($pollId, $userId, $deleteOnlyOrphaned);
}
@ -149,9 +149,9 @@ class VoteService {
*/
public function deletUserFromPoll(int $pollId, string $userId, bool $deleteOnlyOrphaned = false): string {
if ($userId === '') {
$userId = $this->userMapper->getCurrentUserId();
$userId = $this->userSession->getCurrentUserId();
}
if ($userId === $this->userMapper->getCurrentUserId()) {
if ($userId === $this->userSession->getCurrentUserId()) {
$this->acl->setPollId($pollId, Acl::PERMISSION_VOTE_EDIT);
} else {
$this->acl->setPollId($pollId, Acl::PERMISSION_POLL_EDIT);

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

@ -25,15 +25,14 @@ declare(strict_types=1);
namespace OCA\Polls\Service;
use OCA\Polls\AppConstants;
use OCA\Polls\Db\Watch;
use OCA\Polls\Db\WatchMapper;
use OCA\Polls\Exceptions\NoUpdatesException;
use OCA\Polls\Model\Acl;
use OCA\Polls\Model\Settings\AppSettings;
use OCA\Polls\UserSession;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;
use OCP\ISession;
class WatchService {
@ -41,11 +40,11 @@ class WatchService {
* @psalm-suppress PossiblyUnusedMethod
*/
public function __construct(
private ISession $session,
private WatchMapper $watchMapper,
private Acl $acl,
private AppSettings $appSettings,
private Watch $watch,
private UserSession $userSession,
) {
}
@ -89,11 +88,10 @@ class WatchService {
}
public function writeUpdate(int $pollId, string $table): void {
$sessionId = hash('md5', $this->session->get(AppConstants::CLIENT_ID));
$this->watch = new Watch();
$this->watch->setPollId($pollId);
$this->watch->setTable($table);
$this->watch->setSessionId($sessionId);
$this->watch->setSessionId($this->userSession->getClientIdHashed());
try {
$this->watch = $this->watchMapper->insert($this->watch);

191
lib/UserSession.php Normal file
Просмотреть файл

@ -0,0 +1,191 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2024 René Gieling <github@dartcafe.de>
*
* @author René Gieling <github@dartcafe.de>
*
* @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\Polls;
use OCA\Polls\Db\Share;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\UserMapper;
use OCA\Polls\Model\UserBase;
use OCP\ISession;
use OCP\IUserSession;
class UserSession {
/** @var string */
public const SESSION_KEY_USER_ID = 'ncPollsUserId';
/** @var string */
public const SESSION_KEY_SHARE_TOKEN = 'ncPollsPublicToken';
/** @var string */
public const SESSION_KEY_SHARE_TYPE = 'ncPollsShareType';
/** @var string */
public const CLIENT_ID = 'ncPollsClientId';
/** @var string */
public const CLIENT_TZ = 'ncPollsClientTimeZone';
public const TABLE = Share::TABLE;
protected ?UserBase $currentUser = null;
protected ?Share $share = null;
/**
* @psalm-suppress PossiblyUnusedMethod
*/
public function __construct(
protected ISession $session,
protected IUserSession $userSession,
protected UserMapper $userMapper,
protected ShareMapper $shareMapper,
) {
}
/**
* Get current user
*
* Returns a UserBase child for the current (share|nextcloud) user based on
* - the logged in user or
* - the stored session share token
*
*/
public function getUser(): UserBase {
if (!$this->currentUser) {
if ($this->getIsLoggedIn()) {
$this->currentUser = $this->userMapper->getUserFromUserBase($this->userSession->getUser()->getUID());
} else {
$this->currentUser = $this->userMapper->getUserFromShareToken($this->getShareToken());
}
}
return $this->currentUser;
}
public function getCurrentUserId(): string {
if (!$this->session->get(self::SESSION_KEY_USER_ID)) {
$this->session->set(self::SESSION_KEY_USER_ID, $this->getUser()->getId());
}
return (string) $this->session->get(self::SESSION_KEY_USER_ID);
}
public function getIsLoggedIn(): bool {
return $this->userSession->isLoggedIn();
}
public function cleanSession(): void {
$this->session->remove(self::SESSION_KEY_SHARE_TOKEN);
$this->session->remove(self::SESSION_KEY_SHARE_TYPE);
$this->session->remove(self::SESSION_KEY_USER_ID);
$this->share = null;
$this->currentUser = null;
}
/**
* Set share token in case user accesses via a share token
*
* @param string $token
*/
public function setShareToken(string $token): void {
if ($this->getShareToken() !== $token) {
// invalidate session if token changes
// $this->cleanSession();
}
$this->session->set(self::SESSION_KEY_SHARE_TOKEN, $token);
}
/**
* Get share token
*
* Returns the stored session share token
*
* @return string
*/
public function getShareToken(): string {
return (string) $this->session->get(self::SESSION_KEY_SHARE_TOKEN);
}
/**
* Has share
*
* Returns true if a share token is stored in the session
*
* @return bool
*/
public function hasShare(): bool {
return (bool) $this->getShareToken();
}
/**
* Get share
*
* Returns a Share object based on the stored session share token
*
* @return Share
*/
public function getShare(): ?Share {
if ($this->hasShare() && !$this->share) {
$this->share = $this->shareMapper->findByToken($this->getShareToken());
}
return $this->share;
}
/**
* Get share type
*
* Returns the stored session share type
*
* @return string
*/
public function getShareType(): string {
if (!$this->hasShare()) {
return '';
}
if (!$this->session->get(self::SESSION_KEY_SHARE_TYPE)) {
$this->session->set(self::SESSION_KEY_SHARE_TYPE, $this->getShare()->getType());
}
return (string) $this->session->get(self::SESSION_KEY_SHARE_TYPE);
}
public function getClientId(): string {
return (string) $this->session->get(self::CLIENT_ID);
}
public function getClientIdHashed(): string {
return hash('md5', $this->getClientId());
}
public function setClientId(string $clientId): void {
$this->session->set(self::CLIENT_ID, $clientId);
}
public function getClientTimeZone(): string {
return (string) $this->session->get(self::CLIENT_TZ);
}
public function setClientTimeZone(string $clientTimeZone): void {
$this->session->set(self::CLIENT_TZ, $clientTimeZone);
}
}

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

@ -40,6 +40,9 @@ const validators = {
displayName: name,
token: pollToken,
},
headers: {
'Nc-Polls-Share-Token': pollToken,
}
})
},
}

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

@ -76,7 +76,7 @@
</div>
</div>
<div v-if="share.showLogin" class="registration__login">
<div v-if="showLogin" class="registration__login">
<h2> {{ t('polls', 'Registered accounts') }} </h2>
<NcButton wide @click="login()">
<template #default>
@ -136,6 +136,7 @@ export default {
share: (state) => state.share,
privacyUrl: (state) => state.appSettings.usePrivacyUrl,
imprintUrl: (state) => state.appSettings.useImprintUrl,
showLogin: (state) => state.appSettings.useLogin,
}),
registrationIsValid() {