...
 
Commits (16)
......@@ -2,10 +2,7 @@
namespace Minds\Api;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain\Security as ProDomainSecurity;
use Minds\Interfaces;
use Minds\Helpers;
use Minds\Core\Security;
use Minds\Core\Session;
......@@ -111,11 +108,17 @@ class Factory
static::setCORSHeader();
$code = !Security\XSRF::validateRequest() ? 403 : 401;
if (isset($_SERVER['HTTP_APP_VERSION'])) {
$code = 401; // Mobile requires 401 errors
}
header('Content-type: application/json');
header('HTTP/1.1 401 Unauthorized', true, 401);
http_response_code($code);
echo json_encode([
'error' => 'Sorry, you are not authenticated',
'code' => 401,
'code' => $code,
'loggedin' => false
]);
exit;
......
<?php
namespace Minds\Controllers\api\v2\admin\rewards;
use Minds\Api\Exportable;
use Minds\Core\Rewards\Withdraw\Repository;
use Exception;
use Minds\Common\Repository\Response;
use Minds\Core\Di\Di;
use Minds\Core\Rewards\Withdraw\Manager;
use Minds\Core\Rewards\Withdraw\Request;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
......@@ -11,32 +14,38 @@ class withdrawals implements Interfaces\Api, Interfaces\ApiAdminPam
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function get($pages)
{
$repository = new Repository();
$username = $_GET['user'];
/** @var Manager $manager */
$manager = Di::_()->get('Rewards\Withdraw\Manager');
if (!$username) {
return Factory::response([
'withdrawals' => [],
'load-next' => '',
]);
$userGuid = null;
if ($_GET['user']) {
$userGuid = (new User(strtolower($_GET['user'])))->guid;
}
$user = new User(strtolower($username));
$status = $_GET['status'] ?? null;
$withdrawals = $repository->getList([
$opts = [
'status' => $status,
'user_guid' => $userGuid,
'limit' => isset($_GET['limit']) ? (int) $_GET['limit'] : 12,
'offset' => isset($_GET['offset']) ? $_GET['offset'] : '',
'user_guid' => $user->guid
]);
'hydrate' => true,
'admin' => true,
];
/** @var Response $withdrawals */
$withdrawals = $manager->getList($opts);
return Factory::response([
'withdrawals' => Exportable::_($withdrawals['withdrawals']),
'load-next' => (string) base64_encode($withdrawals['token']),
'withdrawals' => $withdrawals,
'load-next' => $withdrawals->getPagingToken(),
]);
}
......@@ -57,6 +66,37 @@ class withdrawals implements Interfaces\Api, Interfaces\ApiAdminPam
*/
public function put($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Rewards\Withdraw\Manager');
$request = $manager->get(
(new Request())
->setUserGuid((string) $pages[0] ?? null)
->setTimestamp((int) $pages[1] ?? null)
->setTx((string) $pages[2] ?? null)
);
if (!$request) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Missing request',
]);
}
try {
$success = $manager->approve($request);
} catch (Exception $exception) {
$success = false;
$errorMessage = $exception->getMessage();
}
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Cannot approve request',
]);
}
return Factory::response([]);
}
......@@ -67,6 +107,37 @@ class withdrawals implements Interfaces\Api, Interfaces\ApiAdminPam
*/
public function delete($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Rewards\Withdraw\Manager');
$request = $manager->get(
(new Request())
->setUserGuid((string) $pages[0] ?? null)
->setTimestamp((int) $pages[1] ?? null)
->setTx((string) $pages[2] ?? null)
);
if (!$request) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Missing request',
]);
}
try {
$success = $manager->reject($request);
} catch (Exception $exception) {
$success = false;
$errorMessage = $exception->getMessage();
}
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Cannot reject request',
]);
}
return Factory::response([]);
}
}
......@@ -11,6 +11,14 @@ class analytics implements Interfaces\Api, Interfaces\ApiIgnorePam
{
public function get($pages)
{
// Temporary require admin
if (!Core\Session::isAdmin()) {
return Factory::response([
'status' => 'error',
'message' => 'Only admins can view these analytics. Use the dashboards instead.',
]);
}
if (!isset($pages[0])) {
return Factory::response([
'status' => 'error',
......
......@@ -123,10 +123,11 @@ class transactions implements Interfaces\Api
break;
case "withdraw":
$request = new Withdraw\Request();
$request->setTx($_POST['tx'])
$request
->setUserGuid(Session::getLoggedInUser()->guid)
->setAddress($_POST['address'])
->setTimestamp(time())
->setTx($_POST['tx'])
->setAddress($_POST['address'])
->setGas($_POST['gas'])
->setAmount((string) BigNumber::_($_POST['amount']));
......
......@@ -14,6 +14,7 @@ use Zend\Diactoros\ServerRequestFactory;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\Response\SapiEmitter;
use Sentry;
class token implements Interfaces\Api, Interfaces\ApiIgnorePam
{
......@@ -70,9 +71,10 @@ class token implements Interfaces\Api, Interfaces\ApiIgnorePam
$refreshTokenRepository->revokeRefreshToken($tokenId);
$response = new JsonResponse([]);
} catch (\Exception $e) {
Sentry\captureException($e); // Log to sentry
$body = [
'status' => 'error',
'message' => $exception->getMessage(),
'message' => $e->getMessage(),
];
$response = new JsonResponse($body, 500);
}
......
......@@ -28,8 +28,6 @@ class channel implements Interfaces\Api
*/
public function get($pages)
{
$currentUser = Session::getLoggedinUser();
$channel = new User(strtolower($pages[0]));
$channel->fullExport = true; //get counts
$channel->exportCounts = true;
......@@ -41,6 +39,8 @@ class channel implements Interfaces\Api
]);
}
$currentUser = Session::getLoggedinUser();
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Manager');
$manager->setUser($channel);
......
<?php
/**
* authorize
* @author edgebal
*/
namespace Minds\Controllers\api\v2\sso;
use Exception;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\SSO\Manager;
use Minds\Entities\User;
use Minds\Interfaces;
use Zend\Diactoros\ServerRequest;
class authorize implements Interfaces\Api, Interfaces\ApiIgnorePam
{
/** @var ServerRequest */
public $request;
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function post($pages)
{
$origin = $this->request->getServerParams()['HTTP_ORIGIN'] ?? '';
if (!$origin) {
return Factory::response([
'status' => 'error',
'message' => 'No HTTP Origin header'
]);
}
$domain = parse_url($origin, PHP_URL_HOST);
/** @var Manager $sso */
$sso = Di::_()->get('SSO');
$sso
->setDomain($domain);
try {
$sso
->authorize($_POST['token']);
} catch (Exception $e) {
error_log((string) $e);
return Factory::response([
'status' => 'error',
'message' => 'Cannot authorize',
]);
}
/** @var User $currentUser */
$currentUser = Session::getLoggedinUser();
return Factory::response([
'user' => $currentUser ? $currentUser->export() : null,
]);
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
/**
* connect
* @author edgebal
*/
namespace Minds\Controllers\api\v2\sso;
use Exception;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\SSO\Manager;
use Minds\Interfaces;
use Zend\Diactoros\ServerRequest;
class connect implements Interfaces\Api, Interfaces\ApiIgnorePam
{
/** @var ServerRequest */
public $request;
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function post($pages)
{
$origin = $this->request->getServerParams()['HTTP_ORIGIN'] ?? '';
if (!$origin) {
return Factory::response([
'status' => 'error',
'message' => 'No HTTP Origin header'
]);
}
$domain = parse_url($origin, PHP_URL_HOST);
/** @var Manager $sso */
$sso = Di::_()->get('SSO');
$sso
->setDomain($domain);
try {
return Factory::response([
'token' => $sso->generateToken()
]);
} catch (Exception $e) {
error_log((string) $e);
return Factory::response([
'status' => 'error',
'message' => 'Cannot connect',
]);
}
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -8,30 +8,39 @@
namespace Minds\Core\Blockchain\Events;
use Minds\Core\Blockchain\Contracts\MindsToken;
use Minds\Core\Blockchain\Transactions\Manager;
use Exception;
use Minds\Core\Blockchain\Transactions\Repository as TransactionsRepository;
use Minds\Core\Blockchain\Transactions\Transaction;
use Minds\Core\Blockchain\Util;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Rewards\Withdraw;
use Minds\Core\Rewards\Withdraw\Manager;
use Minds\Core\Rewards\Withdraw\Request;
use Minds\Core\Util\BigNumber;
class WithdrawEvent implements BlockchainEventInterface
{
/** @var array $eventsMap */
/** @var array */
public static $eventsMap = [
'0x317c0f5ab60805d3e3fb6aaa61ccb77253bbb20deccbbe49c544de4baa4d7f8f' => 'onRequest',
'blockchain:fail' => 'withdrawFail',
];
/** @var Manager $manager */
private $manager;
/** @var Manager */
protected $manager;
/** @var Repository $repository **/
/** @var TransactionsRepository **/
protected $txRepository;
/** @var Config $config */
private $config;
/** @var Config */
protected $config;
/**
* WithdrawEvent constructor.
* @param Manager $manager
* @param TransactionsRepository $txRepository
* @param Config $config
*/
public function __construct($manager = null, $txRepository = null, $config = null)
{
$this->txRepository = $txRepository ?: Di::_()->get('Blockchain\Transactions\Repository');
......@@ -50,30 +59,31 @@ class WithdrawEvent implements BlockchainEventInterface
/**
* @param $topic
* @param array $log
* @throws \Exception
* @param $transaction
* @throws Exception
*/
public function event($topic, array $log, $transaction)
{
$method = static::$eventsMap[$topic];
if ($log['address'] != $this->config->get('blockchain')['contracts']['withdraw']['contract_address']) {
throw new \Exception('Event does not match address');
throw new Exception('Event does not match address');
}
if (method_exists($this, $method)) {
$this->{$method}($log, $transaction);
} else {
throw new \Exception('Method not found');
throw new Exception('Method not found');
}
}
public function onRequest($log, $transaction)
public function onRequest($log, Transaction $transaction)
{
$address = $log['address'];
if ($address != $this->config->get('blockchain')['contracts']['withdraw']['contract_address']) {
$this->withdrawFail($log, $transaction);
throw new \Exception('Incorrect address sent the withdraw event');
throw new Exception('Incorrect address sent the withdraw event');
}
$tx = $log['transactionHash'];
......@@ -82,29 +92,43 @@ class WithdrawEvent implements BlockchainEventInterface
$gas = (string) BigNumber::fromHex($gas);
$amount = (string) BigNumber::fromHex($amount);
//double check the details of this transaction match with what the user actually requested
$request = new Withdraw\Request();
$request
->setTx($tx)
->setAddress($address)
->setUserGuid($user_guid)
->setGas($gas)
->setTimestamp($transaction->getTimestamp())
->setAmount($amount);
try {
$this->manager->complete($request, $transaction);
} catch (\Exception $e) {
error_log(print_r($e, true));
$request = $this->manager->get(
(new Request())
->setUserGuid($user_guid)
->setTimestamp($transaction->getTimestamp())
->setTx($tx)
);
if (!$request) {
throw new \Exception('Unknown withdrawal');
}
if ((string) $address !== (string) $request->getAddress()) {
throw new \Exception('Wrong address value');
} elseif ((string) $gas !== (string) $request->getGas()) {
throw new \Exception('Wrong gas value');
} elseif ((string) $amount !== (string) $request->getAmount()) {
throw new \Exception('Wrong amount value');
}
$this->manager->confirm($request, $transaction);
} catch (Exception $e) {
$this->manager->fail(
(new Request())
->setUserGuid($user_guid)
->setTimestamp($transaction->getTimestamp())
->setTx($tx)
);
error_log($e);
}
}
public function withdrawFail($log, $transaction)
{
if ($transaction->getContract() !== 'withdraw') {
throw new \Exception("Failed but not a withdrawal");
return;
throw new Exception("Failed but not a withdrawal");
}
$transaction->setFailed(true);
......
......@@ -28,12 +28,6 @@ class Homepage121119 implements HypothesisInterface
(new Bucket)
->setId('form')
->setWeight(25),
(new Bucket)
->setId('base-take-back-control')
->setWeight(25),
(new Bucket)
->setId('form-take-back-control')
->setWeight(25),
];
}
}
......@@ -16,6 +16,7 @@ class Minds extends base
private $modules = [
Events\Module::class,
SSO\Module::class,
Email\Module::class,
Experiments\Module::class,
Helpdesk\Module::class,
......
......@@ -71,6 +71,16 @@ class Domain
return !$settings || ((string) $settings->getUserGuid() === $userGuid);
}
/**
* @param string $domain
* @return bool
*/
public function isRoot(string $domain): bool
{
$rootDomains = $this->config->get('pro')['root_domains'] ?? [];
return in_array(strtolower($domain), $rootDomains, true);
}
/**
* @param Settings $settings
* @param User|null $owner
......
<?php
/**
* Security
* @author edgebal
*/
namespace Minds\Core\Pro\Domain;
use Exception;
use Minds\Common\Cookie;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Zend\Diactoros\ServerRequest;
class Security
{
/** @var string */
const JWT_COOKIE_NAME = 'PRO-XSRF-JWT';
/** @var string */
const XSRF_COOKIE_NAME = 'XSRF-TOKEN';
/** @var Cookie */
protected $cookie;
/** @var Jwt */
protected $jwt;
/** @var Config */
protected $config;
/**
* Security constructor.
* @param Cookie $cookie
* @param Jwt $jwt
* @param Config $config
*/
public function __construct(
$cookie = null,
$jwt = null,
$config = null
) {
$this->cookie = $cookie ?: new Cookie();
$this->jwt = $jwt ?: new Jwt();
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param string $domain
* @return string
* @throws Exception
*/
public function setUp($domain): string
{
$nonce = $this->jwt->randomString();
$nbf = time();
$exp = $nbf + 60;
$jwt = $this->jwt
->setKey($this->getEncryptionKey())
->encode([
'nonce' => $nonce,
], $exp, $nbf);
$this->cookie
->setName(static::JWT_COOKIE_NAME)
->setValue($jwt)
->setExpire($exp)
->setPath('/')
->setHttpOnly(false)
->create();
$this->cookie
->setName(static::XSRF_COOKIE_NAME)
->setValue($nonce)
->setExpire(0)
->setPath('/')
->setHttpOnly(false)
->create();
return $jwt;
}
/**
* @param ServerRequest $request
*/
public function syncCookies(ServerRequest $request): void
{
$jwt = $request->getServerParams()['HTTP_X_PRO_XSRF_JWT'] ?? '';
if (!$jwt) {
return;
}
try {
$data = $this->jwt
->setKey($this->getEncryptionKey())
->decode($jwt);
if (($_COOKIE[static::XSRF_COOKIE_NAME] ?? null) === $data['nonce']) {
return;
}
$this->cookie
->setName(static::XSRF_COOKIE_NAME)
->setValue($data['nonce'])
->setExpire(0)
->setPath('/')
->setHttpOnly(false)
->create();
} catch (Exception $e) {
// Invalid or expired JWT
}
}
/**
* @return string
*/
protected function getEncryptionKey(): string
{
return $this->config->get('oauth')['encryption_key'] ?? '';
}
}
......@@ -24,10 +24,6 @@ class ProProvider extends Provider
return new Domain();
}, ['useFactory' => true]);
$this->di->bind('Pro\Domain\Security', function ($di) {
return new Domain\Security();
}, ['useFactory' => true]);
$this->di->bind('Pro\Domain\Subscription', function ($di) {
return new Domain\Subscription();
}, ['useFactory' => true]);
......
......@@ -1546,3 +1546,12 @@ CREATE TABLE minds.experiments (
AND read_repair_chance = 0.0
AND speculative_retry = '99PERCENTILE';
CREATE INDEX experiments_key_idx ON minds.experiments (key);
ALTER TABLE minds.withdrawals ADD (status text, address text, gas varint);
CREATE MATERIALIZED VIEW minds.withdrawals_by_status AS
SELECT *
FROM minds.withdrawals
WHERE status IS NOT NULL AND user_guid IS NOT NULL AND timestamp IS NOT NULL AND tx IS NOT NULL
PRIMARY KEY (status, timestamp, user_guid, tx)
WITH CLUSTERING ORDER BY (timestamp ASC, user_guid ASC, tx ASC);
<?php
/**
* NotificationsDelegate
* @author edgebal
*/
namespace Minds\Core\Rewards\Withdraw\Delegates;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Events\EventsDispatcher;
use Minds\Core\Rewards\Withdraw\Request;
use Minds\Core\Util\BigNumber;
class NotificationsDelegate
{
/** @var EventsDispatcher */
protected $dispatcher;
/**
* NotificationsDelegate constructor.
* @param EventsDispatcher $dispatcher
*/
public function __construct(
$dispatcher = null
) {
$this->dispatcher = $dispatcher ?: Di::_()->get('EventsDispatcher');
}
/**
* @param Request $request
*/
public function onRequest(Request $request): void
{
$message = 'Your token withdrawal request was submitted successfully.';
$this->dispatcher->trigger('notification', 'all', [
'to' => [ $request->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message' => $message],
'message' => $message,
]);
}
/**
* @param Request $request
*/
public function onConfirm(Request $request): void
{
$message = 'Your token withdrawal request transaction was confirmed by the blockchain and has been placed onto the review queue.';
$this->dispatcher->trigger('notification', 'all', [
'to' => [ $request->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message' => $message],
'message' => $message,
]);
}
/**
* @param Request $request
*/
public function onFail(Request $request): void
{
$message = 'Your token withdrawal request transaction failed. Please contact an administrator.';
$this->dispatcher->trigger('notification', 'all', [
'to' => [ $request->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message' => $message],
'message' => $message,
]);
}
/**
* @param Request $request
* @throws Exception
*/
public function onApprove(Request $request): void
{
$message = sprintf(
"Your withdrawal request has been approved and %g OnChain token(s) were issued.",
BigNumber::fromPlain($request->getAmount(), 18)->toDouble()
);
$this->dispatcher->trigger('notification', 'all', [
'to' => [ $request->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message' => $message],
'message' => $message,
]);
}
/**
* @param Request $request
* @throws Exception
*/
public function onReject(Request $request): void
{
$message = sprintf(
"Your withdrawal request has been rejected. Your %g OffChain token(s) were refunded.",
BigNumber::fromPlain($request->getAmount(), 18)->toDouble()
);
$this->dispatcher->trigger('notification', 'all', [
'to' => [ $request->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message' => $message],
'message' => $message,
]);
}
}
<?php
/**
* RequestHydrationDelegate
* @author edgebal
*/
namespace Minds\Core\Rewards\Withdraw\Delegates;
use Exception;
use Minds\Core\Rewards\Withdraw\Request;
use Minds\Entities\User;
class RequestHydrationDelegate
{
/**
* @param Request $request
* @return Request
* @throws Exception
*/
public function hydrate(Request $request)
{
$userGuid = $request->getUserGuid();
if (!$userGuid) {
return $request;
}
try {
$user = new User($userGuid);
} catch (Exception $exception) {
$user = null;
}
return $request
->setUser($user);
}
public function hydrateForAdmin(Request $request)
{
if (!$request->getUser()) {
$request = $this->hydrate($request);
if (!$request->getUser()) {
return $request;
}
}
$referrerGuid = $request->getUser()->referrer;
if (!$referrerGuid) {
return $request;
}
try {
$user = new User($referrerGuid);
} catch (Exception $exception) {
// Faux user in case of banned/deleted accounts
$user = new User();
$user->guid = $referrerGuid;
$user->username = $referrerGuid;
}
return $request
->setReferrer($user);
}
}
This diff is collapsed.
......@@ -3,93 +3,83 @@
namespace Minds\Core\Rewards\Withdraw;
use Cassandra;
use Cassandra\Varint;
use Cassandra\Decimal;
use Cassandra\Timestamp;
use Minds\Core\Blockchain\Transactions\Transaction;
use Exception;
use Minds\Core\Data\Cassandra\Client;
use Minds\Core\Data\Cassandra\Prepared\Custom;
use Minds\Core\Di\Di;
use Minds\Core\Rewards\Transactions;
use Minds\Core\Util\BigNumber;
use Minds\Entities\User;
class Repository
{
/** @var Client */
private $db;
protected $db;
/**
* Repository constructor.
* @param Client $db
*/
public function __construct($db = null)
{
$this->db = $db ? $db : Di::_()->get('Database\Cassandra\Cql');
}
/**
* @param Transaction[]|Transaction $transactions
* @return $this
* @param array $opts
* @return array
*/
public function add($requests)
{
if (!is_array($requests)) {
$requests = [ $requests ];
}
$queries = [];
$template = "INSERT INTO withdrawals (user_guid, timestamp, amount, tx, completed, completed_tx) VALUES (?,?,?,?,?,?)";
foreach ($requests as $request) {
$queries[] = [
'string' => $template,
'values' => [
new Varint($request->getUserGuid()),
new Timestamp($request->getTimestamp()),
new Varint($request->getAmount()),
$request->getTx(),
(bool) $request->isCompleted(),
$request->getCompletedTx()
]
];
}
$this->db->batchRequest($queries, Cassandra::BATCH_UNLOGGED);
return $this;
}
public function getList($options)
public function getList(array $opts): array
{
$options = array_merge([
$opts = array_merge([
'status' => null,
'user_guid' => null,
'from' => null,
'to' => null,
'completed' => null,
'completed_tx' => null,
'limit' => 12,
'offset' => null
], $options);
'offset' => null,
], $opts);
$cql = "SELECT * from withdrawals";
$where = [];
$values = [];
if ($options['user_guid']) {
if ($opts['status']) {
$cql = "SELECT * from withdrawals_by_status";
$where[] = 'status = ?';
$values[] = (string) $opts['status'];
}
if ($opts['user_guid']) {
$where[] = 'user_guid = ?';
$values[] = new Varint($options['user_guid']);
$values[] = new Varint($opts['user_guid']);
}
if ($opts['timestamp']) {
$where[] = 'timestamp = ?';
$values[] = new Timestamp($opts['timestamp']);
}
if ($opts['tx']) {
$where[] = 'tx = ?';
$values[] = (string) $opts['tx'];
}
if ($options['from']) {
if ($opts['from']) {
$where[] = 'timestamp >= ?';
$values[] = new Timestamp($options['from']);
$values[] = new Timestamp($opts['from']);
}
if ($options['to']) {
if ($opts['to']) {
$where[] = 'timestamp <= ?';
$values[] = new Timestamp($options['to']);
$values[] = new Timestamp($opts['to']);
}
if ($options['completed']) {
if ($opts['completed']) {
$where[] = 'completed = ?';
$values[] = (string) $options['completed'];
$values[] = (string) $opts['completed'];
}
if ($where) {
......@@ -99,47 +89,81 @@ class Repository
$query = new Custom();
$query->query($cql, $values);
$query->setOpts([
'page_size' => (int) $options['limit'],
'paging_state_token' => base64_decode($options['offset'], true)
'page_size' => (int) $opts['limit'],
'paging_state_token' => base64_decode($opts['offset'], true),
]);
try {
$rows = $this->db->request($query);
} catch (\Exception $e) {
$requests = [];
foreach ($rows ?: [] as $row) {
$request = new Request();
$request
->setUserGuid((string) $row['user_guid']->value())
->setTimestamp($row['timestamp']->time())
->setTx($row['tx'])
->setAddress($row['address'] ?: '')
->setAmount((string) BigNumber::_($row['amount']))
->setCompleted((bool) $row['completed'])
->setCompletedTx($row['completed_tx'] ?: null)
->setGas((string) BigNumber::_($row['gas']))
->setStatus($row['status'] ?: '')
;
$requests[] = $request;
}
return [
'withdrawals' => $requests,
'token' => $rows->pagingStateToken(),
];
} catch (Exception $e) {
error_log($e->getMessage());
return [];
}
}
if (!$rows) {
return [];
}
/**
* @param Request $request
* @return bool
*/
public function add(Request $request)
{
$cql = "INSERT INTO withdrawals (user_guid, timestamp, tx, address, amount, completed, completed_tx, gas, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$values = [
new Varint($request->getUserGuid()),
new Timestamp($request->getTimestamp()),
$request->getTx(),
(string) $request->getAddress(),
new Varint($request->getAmount()),
(bool) $request->isCompleted(),
((string) $request->getCompletedTx()) ?: null,
new Varint($request->getGas()),
(string) $request->getStatus(),
];
$requests = [];
foreach ($rows as $row) {
$request = new Request();
$request->setUserGuid((string) $row['user_guid']->value());
$request->setTimestamp($row['timestamp']->time());
$request->setAmount((string) BigNumber::_($row['amount']));
$request->setTx($row['tx']);
$request->setCompleted((bool) $row['completed']);
$request->setCompletedTx($row['completed_tx']);
$requests[] = $request;
}
$prepared = new Custom();
$prepared->query($cql, $values);
return [
'withdrawals' => $requests,
'token' => $rows->pagingStateToken()
];
return $this->db->request($prepared, true);
}
public function update($key, $guids)
/**
* @param Request $request
* @return bool
*/
public function update(Request $request)
{
// TODO: Implement update() method.
return $this->add($request);
}
public function delete($entity)
/**
* @param Request $request
* @throws Exception
*/
public function delete(Request $request)
{
// TODO: Implement delete() method.
throw new Exception('Not allowed');
}
}
<?php
namespace Minds\Core\Rewards\Withdraw;
use JsonSerializable;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
class Request
/**
* Class Request
* @package Minds\Core\Rewards\Withdraw
* @method string getTx()
* @method Request setTx(string $tx)
* @method string getCompletedTx
* @method Request setCompletedTx(string $completedTx)
* @method string getAddress()
* @method Request setAddress(string $address)
* @method int|string getUserGuid()
* @method Request setUserGuid(int|string $userGuid)
* @method double getGas()
* @method Request setGas(double $gas)
* @method string getAmount()
* @method Request setAmount(string $amount)
* @method string getStatus()
* @method Request setStatus(string $status)
* @method bool isCompleted()
* @method Request setCompleted(bool $completed)
* @method int getTimestamp()
* @method Request setTimestamp(int $timestamp)
* @method User|null getUser()
* @method Request setUser(User|null $user)
* @method User|null getReferrer()
* @method Request setReferrer(User|null $referrer)
*/
class Request implements JsonSerializable
{
use MagicAttributes;
/** @var string $tx **/
private $tx;
/** @var string **/
protected $tx;
/** @var string $completed_tx **/
private $completed_tx;
/** @var string **/
protected $completedTx;
/** @var string $address **/
private $address;
/** @var string **/
protected $address;
/** @var int $user_guid **/
private $user_guid;
/** @var int|string **/
protected $userGuid;
/** @var double $gas **/
private $gas;
/** @var double **/
protected $gas;
/** @var string $amount **/
private $amount;
/** @var string **/
protected $amount;
/** @var bool $completed **/
private $completed;
/** @var string */
protected $status;
/** @var int $timestamp **/
private $timestamp;
/** @var bool **/
protected $completed;
public function setUserGuid($user_guid)
{
$this->user_guid = $user_guid;
return $this;
}
/** @var int **/
protected $timestamp;
public function getUserGuid()
{
return $this->user_guid;
}
/** @var User */
protected $user;
public function setCompletedTx($completed_tx)
{
$this->completed_tx = $completed_tx;
return $this;
}
public function getCompletedTx()
{
return $this->completed_tx;
}
/** @var User */
protected $referrer;
/**
* @return array
*/
public function export()
{
return [
$data = [
'timestamp' => $this->timestamp,
'amount' => $this->amount,
'user_guid' => $this->user_guid,
'user_guid' => $this->userGuid,
'tx' => $this->tx,
'status' => $this->status,
'completed' => $this->completed,
'completed_tx' => $this->completed_tx
'completed_tx' => $this->completedTx,
];
if ($this->user) {
$data['user'] = $this->user->export();
}
if ($this->referrer) {
$data['referrer'] = $this->referrer->export();
}
return $data;
}
/**
* Specify data which should be serialized to JSON
* @link https://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return $this->export();
}
}
......@@ -20,9 +20,6 @@ class ProMiddleware implements RouterMiddleware
/** @var Domain */
protected $domain;
/** @var Domain\Security */
protected $domainSecurity;
/** @var Manager */
protected $manager;
......@@ -35,20 +32,17 @@ class ProMiddleware implements RouterMiddleware
/**
* ProMiddleware constructor.
* @param Domain $domain
* @param Domain\Security $domainSecurity
* @param Manager $manager
* @param SEO $seo
* @param EntitiesBuilder $entitiesBuilder
*/
public function __construct(
$domain = null,
$domainSecurity = null,
$manager = null,
$seo = null,
$entitiesBuilder = null
) {
$this->domain = $domain ?: Di::_()->get('Pro\Domain');
$this->domainSecurity = $domainSecurity ?: Di::_()->get('Pro\Domain\Security');
$this->manager = $manager ?: Di::_()->get('Pro\Manager');
$this->seo = $seo ?: Di::_()->get('Pro\SEO');
$this->entitiesBuilder = $entitiesBuilder ?: Di::_()->get('EntitiesBuilder');
......@@ -65,8 +59,8 @@ class ProMiddleware implements RouterMiddleware
$serverParams = $request->getServerParams() ?? [];
$originalHost = $serverParams['HTTP_HOST'];
$scheme = $request->getUri()->getScheme();
$host = parse_url($serverParams['HTTP_ORIGIN'] ?? '', PHP_URL_HOST) ?: $originalHost;
$scheme = parse_url($serverParams['HTTP_ORIGIN'] ?? '', PHP_URL_SCHEME) ?: $request->getUri()->getScheme();
if (!$host) {
return null;
......@@ -104,17 +98,6 @@ class ProMiddleware implements RouterMiddleware
->setUser($user)
->setup($settings);
// Initialize XRSF JWT cookie, only if we're on Pro domain's scope
// If not and within 1 minute, update XSRF cookie to match it
if ($originalHost === $settings->getDomain()) {
$this->domainSecurity
->setUp($settings->getDomain());
} else {
$this->domainSecurity
->syncCookies($request);
}
return null;
}
}
<?php
/**
* ProDelegate
* @author edgebal
*/
namespace Minds\Core\SSO\Delegates;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain as ProDomain;
class ProDelegate
{
/** @var ProDomain */
protected $proDomain;
/**
* ProDelegate constructor.
* @param ProDomain $proDomain
*/
public function __construct(
$proDomain = null
) {
$this->proDomain = $proDomain ?: Di::_()->get('Pro\Domain');
}
/**
* @param string $domain
* @return bool
*/
public function isAllowed(string $domain): bool
{
return $this->proDomain->isRoot($domain)
|| (bool) $this->proDomain->lookup($domain);
}
}
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\SSO;
use Exception;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Di\Di;
use Minds\Core\Sessions\Manager as SessionsManager;
class Manager
{
/** @var int */
const JWT_EXPIRE = 300;
/** @var Config */
protected $config;
/** @var abstractCacher */
protected $cache;
/** @var Jwt */
protected $jwt;
/** @var SessionsManager */
protected $sessions;
/** @var Delegates\ProDelegate */
protected $proDelegate;
/** @var string */
protected $domain;
/**
* Manager constructor.
* @param Config $config
* @param abstractCacher $cache
* @param Jwt $jwt
* @param SessionsManager $sessions
* @param Delegates\ProDelegate $proDelegate
*/
public function __construct(
$config = null,
$cache = null,
$jwt = null,
$sessions = null,
$proDelegate = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->cache = $cache ?: Di::_()->get('Cache');
$this->jwt = $jwt ?: new Jwt();
$this->sessions = $sessions ?: Di::_()->get('Sessions\Manager');
$this->proDelegate = $proDelegate ?: new Delegates\ProDelegate();
}
/**
* @param string $domain
* @return Manager
*/
public function setDomain(string $domain): Manager
{
$this->domain = $domain;
return $this;
}
/**
* @return bool
*/
protected function isAllowed(): bool
{
if ($this->proDelegate->isAllowed($this->domain)) {
return true;
}
return false;
}
/**
* @return string
* @throws Exception
*/
public function generateToken(): ?string
{
if (!$this->isAllowed()) {
throw new Exception('Invalid domain');
}
$now = time();
$session = $this->sessions->getSession();
if (!$session || !$session->getUserGuid()) {
return null;
}
$key = $this->config->get('oauth')['encryption_key'] ?? '';
if (!$key) {
throw new Exception('Invalid encryption key');
}
$sessionToken = (string) $session->getToken();
$sessionTokenHash = hash('sha256', $key . $sessionToken);
$ssoKey = implode(':', ['sso', $this->domain, $sessionTokenHash, $this->jwt->randomString()]);
$jwt = $this->jwt
->setKey($key)
->encode([
'key' => $ssoKey,
'domain' => $this->domain,
], $now, $now + static::JWT_EXPIRE);
$this->cache
->set($ssoKey, $sessionToken, static::JWT_EXPIRE * 2);
return $jwt;
}
/**
* @param string $jwt
* @return void
* @throws Exception
*/
public function authorize(string $jwt): void
{
if (!$jwt) {
throw new Exception('Invalid JTW');
}
if (!$this->isAllowed()) {
throw new Exception('Invalid domain');
}
$key = $this->config->get('oauth')['encryption_key'] ?? '';
if (!$key) {
throw new Exception('Invalid encryption key');
}
$data = $this->jwt
->setKey($key)
->decode($jwt);
if ($this->domain !== $data['domain']) {
throw new Exception('Domain mismatch');
}
$ssoKey = $data['key'];
$sessionToken = $this->cache
->get($ssoKey);
if ($sessionToken) {
$this->sessions
->withString($sessionToken)
->save();
$this->cache
->destroy($ssoKey);
}
}
}
<?php
/**
* Module
* @author edgebal
*/
namespace Minds\Core\SSO;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
/**
* Executed onInit
* @return void
*/
public function onInit()
{
(new Provider())->register();
}
}
<?php
/**
* Provider
* @author edgebal
*/
namespace Minds\Core\SSO;
use Minds\Core\Di\Provider as DiProvider;
class Provider extends DiProvider
{
public function register()
{
$this->di->bind('SSO', function () {
return new Manager();
});
}
}
......@@ -81,17 +81,27 @@ class Manager
/**
* Build session from jwt cookie
* @return $this
* @param $request
* @return Manager
*/
public function withRouterRequest($request)
public function withRouterRequest($request): Manager
{
$cookies = $request->getCookieParams();
if (!isset($cookies['minds_sess'])) {
return $this;
}
return $this->withString((string) $cookies['minds_sess']);
}
/**
* @param string $sessionToken
* @return Manager
*/
public function withString(string $sessionToken): Manager
{
try {
$token = $this->jwtParser->parse((string) $cookies['minds_sess']); // Collect from cookie
$token = $this->jwtParser->parse($sessionToken);
$token->getHeaders();
$token->getClaims();
} catch (\Exception $e) {
......
......@@ -2,10 +2,24 @@
/**
* Minds Session
*/
namespace Minds\Core\Sessions;
use Lcobucci\JWT\Token as JwtToken;
use Minds\Traits\MagicAttributes;
/**
* Class Session
* @package Minds\Core\Sessions
* @method string getId()
* @method Session setId(string $id)
* @method string|JwtToken getToken()
* @method Session setToken(string|JwtToken $token)
* @method int|string getUserGuid()
* @method Session setUserGuid(int|string $userGuid)
* @method int getExpires()
* @method Session setExpires(int $expires)
*/
class Session
{
use MagicAttributes;
......@@ -13,7 +27,7 @@ class Session
/** @var string $id */
private $id;
/** @var string $token */
/** @var string|JwtToken $token */
private $token;
/** @var int $userGuid */
......
......@@ -6,6 +6,7 @@ use Minds\Core\Blockchain\Transactions\Repository;
use Minds\Core\Blockchain\Transactions\Transaction;
use Minds\Core\Config\Config;
use Minds\Core\Rewards\Withdraw\Manager;
use Minds\Core\Rewards\Withdraw\Request;
use Minds\Core\Util\BigNumber;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
......@@ -31,9 +32,9 @@ class WithdrawEventSpec extends ObjectBehavior
->willReturn([
'contracts' => [
'withdraw' => [
'contract_address' => '0xasd'
]
]
'contract_address' => '0xasd',
],
],
]);
}
......@@ -46,30 +47,53 @@ class WithdrawEventSpec extends ObjectBehavior
{
$this->getTopics()->shouldReturn([
'0x317c0f5ab60805d3e3fb6aaa61ccb77253bbb20deccbbe49c544de4baa4d7f8f',
'blockchain:fail'
'blockchain:fail',
]);
}
public function it_should_complete_withdrawal_on_event()
{
$this->manager->complete(Argument::that(function ($request) {
return $request->getTx() == '0x62a70ccf3b37b9368efa3dd4785e715139c994ba9957a125e299b14a8eccd00c'
&& $request->getAddress() == '0x177fd9efd24535e73b81e99e7f838cdef265e6cb'
&& $request->getUserGuid() == (string) BigNumber::_('786645648014315523')
&& $request->getGas() == (string) BigNumber::_('67839000000000')
&& $request->getAmount() == (string) BigNumber::_('10000000000000000000');
}), Argument::type('\Minds\Core\Blockchain\Transactions\Transaction'))
public function it_should_complete_withdrawal_on_event(
Request $request,
Transaction $transaction
) {
$transaction->getTimestamp()
->shouldBeCalled()
->willReturn(123456789);
$request->getAddress()
->shouldBeCalled()
->willReturn('0x177fd9efd24535e73b81e99e7f838cdef265e6cb');
$request->getGas()
->shouldBeCalled()
->willReturn('67839000000000');
$request->getAmount()
->shouldBeCalled()
->willReturn('10000000000000000000');
$this->manager->get(
Argument::that(function (Request $request) {
return (string) $request->getUserGuid() === '786645648014315523' &&
$request->getTimestamp() === 123456789 &&
$request->getTx() === '0x62a70ccf3b37b9368efa3dd4785e715139c994ba9957a125e299b14a8eccd00c';
})
)
->shouldBeCalled()
->willReturn($request);
$this->manager->confirm($request, Argument::type('\Minds\Core\Blockchain\Transactions\Transaction'))
->shouldBeCalled();
$data = "0x000000000000000000000000177fd9efd24535e73b81e99e7f838cdef265e6cb"
. "0000000000000000000000000000000000000000000000000aeaba0c8e001003"
. "00000000000000000000000000000000000000000000000000003db2ff7f3600"
. "0000000000000000000000000000000000000000000000008ac7230489e80000";
$this->onRequest([
'address' => '0xasd',
'data' => $data,
'transactionHash' => '0x62a70ccf3b37b9368efa3dd4785e715139c994ba9957a125e299b14a8eccd00c'
], new Transaction);
'transactionHash' => '0x62a70ccf3b37b9368efa3dd4785e715139c994ba9957a125e299b14a8eccd00c',
], $transaction);
}
public function it_should_send_a_blockchain_fail_event(Transaction $transaction)
......@@ -115,8 +139,8 @@ class WithdrawEventSpec extends ObjectBehavior
'contracts' => [
'withdraw' => [
'contract_address' => '0x277fd9efd24535e73b81e99e7f838cdef265e6cb',
]
]
],
],
]);
$data = "0x000000000000000000000000177fd9efd24535e73b81e99e7f838cdef265e6cb"
......@@ -127,7 +151,7 @@ class WithdrawEventSpec extends ObjectBehavior
->duringOnRequest([
'address' => '0x177fd9efd24535e73b81e99e7f838cdef265e6cb',
'data' => $data,
'transactionHash' => '0x62a70ccf3b37b9368efa3dd4785e715139c994ba9957a125e299b14a8eccd00c'
'transactionHash' => '0x62a70ccf3b37b9368efa3dd4785e715139c994ba9957a125e299b14a8eccd00c',
], (new Transaction())->setContract('withdraw'));
}
}
<?php
namespace Spec\Minds\Core\Pro\Domain;
use Minds\Common\Cookie;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Pro\Domain\Security;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class SecuritySpec extends ObjectBehavior
{
/** @var Cookie */
protected $cookie;
/** @var Jwt */
protected $jwt;
/** @var Config */
protected $config;
public function let(
Cookie $cookie,
Jwt $jwt,
Config $config
) {
$this->cookie = $cookie;
$this->jwt = $jwt;
$this->config = $config;
$this->beConstructedWith($cookie, $jwt, $config);
}
public function it_is_initializable()
{
$this->shouldHaveType(Security::class);
}
public function it_should_set_up()
{
$this->jwt->randomString()
->shouldBeCalled()
->willReturn('~random~');
$this->config->get('oauth')
->shouldBeCalled()
->willReturn([
'encryption_key' => 'phpspec'
]);
$this->jwt->setKey('phpspec')
->shouldBeCalled()
->willReturn($this->jwt);
$this->jwt->encode(Argument::type('array'), Argument::type('int'), Argument::type('int'))
->shouldBeCalled()
->willReturn('~encoded~');
$this->cookie->setName(Security::JWT_COOKIE_NAME)
->shouldBeCalled()
->willReturn($this->cookie);
$this->cookie->setName(Security::XSRF_COOKIE_NAME)
->shouldBeCalled()
->willReturn($this->cookie);
$this->cookie->setValue('~encoded~')
->shouldBeCalled()
->willReturn($this->cookie);
$this->cookie->setValue('~random~')
->shouldBeCalled()
->willReturn($this->cookie);
$this->cookie->setExpire(Argument::type('int'))
->shouldBeCalledTimes(2)
->willReturn($this->cookie);
$this->cookie->setPath('/')
->shouldBeCalledTimes(2)
->willReturn($this->cookie);
$this->cookie->setHttpOnly(false)
->shouldBeCalledTimes(2)
->willReturn($this->cookie);
$this->cookie->create()
->shouldBeCalledTimes(2)
->willReturn(true);
$this
->setUp('phpspec.test')
->shouldReturn('~encoded~');
}
}
......@@ -173,6 +173,23 @@ class DomainSpec extends ObjectBehavior
->shouldReturn(false);
}
public function it_should_check_if_root_domain()
{
$this->config->get('pro')
->shouldBeCalled()
->willReturn([
'root_domains' => ['phpspec.test']
]);
$this
->isRoot('phpspec.test')
->shouldReturn(true);
$this
->isRoot('not-a-root-phpspec.test')
->shouldReturn(false);
}
public function it_should_get_icon(
Settings $settings,
User $owner
......
This diff is collapsed.
<?php
namespace Spec\Minds\Core\SSO;
use Exception;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Sessions\Manager as SessionsManager;
use Minds\Core\Sessions\Session;
use Minds\Core\SSO\Delegates;
use Minds\Core\SSO\Manager;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
/** @var Config */
protected $config;
/** @var abstractCacher */
protected $cache;
/** @var Jwt */
protected $jwt;
/** @var SessionsManager */
protected $sessions;
/** @var Delegates\ProDelegate */
protected $proDelegate;
public function let(
Config $config,
abstractCacher $cache,
Jwt $jwt,
SessionsManager $sessions,
Delegates\ProDelegate $proDelegate
) {
$this->config = $config;
$this->cache = $cache;
$this->jwt = $jwt;
$this->sessions = $sessions;
$this->proDelegate = $proDelegate;
$this->config->get('oauth')
->willReturn([
'encryption_key' => '~key~'
]);
$this->beConstructedWith(
$config,
$cache,
$jwt,
$sessions,
$proDelegate
);
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_generate_token(
Session $session
) {
$this->proDelegate->isAllowed('phpspec.test')
->shouldBeCalled()
->willReturn(true);
$this->sessions->getSession()
->shouldBeCalled()
->willReturn($session);
$session->getUserGuid()
->shouldBeCalled()
->willReturn(1000);
$session->getToken()
->shouldBeCalled()
->willReturn('~token~');
$this->jwt->randomString()
->shouldBeCalled()
->willReturn('~random~');
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$ssoKey = sprintf(
"sso:%s:%s:%s",
'phpspec.test',
hash('sha256', '~key~~token~'),
'~random~'
);
$this->jwt->encode([
'key' => $ssoKey,
'domain' => 'phpspec.test'
], Argument::type('int'), Argument::type('int'))
->shouldBeCalled()
->willReturn('~jwt~');
$this->cache->set($ssoKey, '~token~', Argument::type('int'))
->shouldBeCalled()
->willReturn(true);
$this
->setDomain('phpspec.test')
->generateToken()
->shouldReturn('~jwt~');
}
public function it_should_not_generate_a_token_if_logged_out()
{
$this->proDelegate->isAllowed('phpspec.test')
->shouldBeCalled()
->willReturn(true);
$this->sessions->getSession()
->shouldBeCalled()
->willReturn(null);
$this
->setDomain('phpspec.test')
->generateToken()
->shouldReturn(null);
}
public function it_should_authorize()
{
$this->proDelegate->isAllowed('phpspec.test')
->shouldBeCalled()
->willReturn(true);
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$this->jwt->decode('~jwt~')
->shouldBeCalled()
->willReturn([
'key' => 'sso:key',
'domain' => 'phpspec.test'
]);
$this->cache->get('sso:key')
->shouldBeCalled()
->willReturn('~token~');
$this->sessions->withString('~token~')
->shouldBeCalled()
->willReturn($this->sessions);
$this->sessions->save()
->shouldBeCalled()
->willReturn(true);
$this->cache->destroy('sso:key')
->shouldBeCalled()
->willReturn(true);
$this
->setDomain('phpspec.test')
->shouldNotThrow(Exception::class)
->duringAuthorize('~jwt~');
}
public function it_should_not_authorize_if_domain_mismatches()
{
$this->proDelegate->isAllowed('other-phpspec.test')
->shouldBeCalled()
->willReturn(true);
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$this->jwt->decode('~jwt~')
->shouldBeCalled()
->willReturn([
'key' => 'sso:key',
'domain' => 'phpspec.test'
]);
$this->sessions->withString(Argument::cetera())
->shouldNotBeCalled();
$this
->setDomain('other-phpspec.test')
->shouldThrow(new Exception('Domain mismatch'))
->duringAuthorize('~jwt~');
}
}
......@@ -20,6 +20,16 @@ trait MagicAttributes
if (strpos($name, 'set', 0) === 0) {
$attribute = preg_replace('/^set/', '', $name);
$attribute = lcfirst($attribute);
if (!property_exists($this, $attribute)) {
error_log(sprintf(
"Attribute %s is not defined in %s (%s)",
$attribute,
get_class($this),
$name
));
}
$this->$attribute = $args[0];
// DirtyChecking interop
......@@ -32,13 +42,40 @@ trait MagicAttributes
$attribute = preg_replace('/^get/', '', $name);
$attribute = lcfirst($attribute);
if (!property_exists($this, $attribute)) {
error_log(sprintf(
"Attribute %s is not defined in %s (%s)",
$attribute,
get_class($this),
$name
));
}
return $this->$attribute;
} elseif (strpos($name, 'is', 0) === 0) {
$attribute = preg_replace('/^is/', '', $name);
$attribute = lcfirst($attribute);
if (!property_exists($this, $attribute)) {
error_log(sprintf(
"Attribute %s is not defined in %s (%s)",
$attribute,
get_class($this),
$name
));
}
return (bool) $this->$attribute;
} elseif (strpos($name, 'has', 0) === 0) {
if (!property_exists($this, $name)) {
error_log(sprintf(
"Attribute %s is not defined in %s (%s)",
$name,
get_class($this),
$name
));
}
return (bool) $this->$name;
}
......
......@@ -323,8 +323,8 @@ class ElggFile extends ElggObject {
/**
* Executed prior to object serialization
*/
public function __sleep()
{
unset($this->handle);
}
// public function __sleep()
// {
// unset($this->handle);
// }
}
......@@ -480,6 +480,7 @@ $CONFIG->set('features', [
'allow-comments-toggle' => false,
'permissions' => false,
'pro' => false,
'webtorrent' => false,
]);
$CONFIG->set('email', [
......