...
 
Commits (29)
......@@ -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;
......
......@@ -3,7 +3,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title></title>
<style>
p {
p, li {
font-family: Roboto,Arial,sans-serif;
font-size: 18px;
line-height: 1.5;
......
<?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([]);
}
}
......@@ -10,7 +10,7 @@ use Minds\Interfaces;
use Minds\Core\Media\Video\Manager;
use Minds\Entities;
class transcode implements Interfaces\Api, Interfaces\ApiAdminPam
class transcode implements Interfaces\Api //, Interfaces\ApiAdminPam
{
/**
* Not yet implemented GET.
......@@ -30,7 +30,6 @@ class transcode implements Interfaces\Api, Interfaces\ApiAdminPam
public function post($pages)
{
$guid = intval($_POST['guid']);
if (!$guid) {
return Factory::response(['status' => 'error', 'message' => 'You must send a GUID.']);
}
......@@ -41,15 +40,9 @@ class transcode implements Interfaces\Api, Interfaces\ApiAdminPam
return Factory::response(['status' => 'error', 'message' => 'Entity not found.']);
}
$user = $entity->getOwnerEntity();
if (!$user) {
return Factory::response(['status' => 'error', 'message' => 'User not found.']);
}
$videoManager = new Manager();
if (!$videoManager->queueTranscoding($guid, $user->isPro())) {
if (!$videoManager->transcode($entity)) {
return Factory::response(['status' => 'error', 'message' => 'Failed to add video to transcoding queue.']);
}
......
......@@ -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);
......
......@@ -43,9 +43,12 @@ class content implements Interfaces\Api
}
$type = '';
$algorithm = strtolower($_GET['algorithm'] ?? 'top');
switch ($pages[1]) {
case 'activities':
$type = 'activity';
$algorithm = 'latest';
break;
case 'images':
$type = 'object:image';
......@@ -127,7 +130,7 @@ class content implements Interfaces\Api
'custom_type' => null,
'limit' => $limit,
'type' => $type,
'algorithm' => 'top',
'algorithm' => $algorithm,
'period' => '7d',
'sync' => $sync,
'from_timestamp' => $fromTimestamp,
......@@ -159,7 +162,7 @@ class content implements Interfaces\Api
try {
$result = $this->getData($entities, $opts, $asActivities, $sync);
if ($result->count() <= static::MIN_COUNT) {
if ($opts['algorithm'] !== 'latest' && $result->count() <= static::MIN_COUNT) {
$opts['algorithm'] = 'latest';
$result = $this->getData($entities, $opts, $asActivities, $sync);
}
......
<?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([]);
}
}
......@@ -59,6 +59,13 @@ class wire implements Interfaces\Api
$amount = BigNumber::_($_POST['amount']);
$recurring = isset($_POST['recurring']) ? $_POST['recurring'] : false;
$recurringInterval = $_POST['recurring_interval'] ?? 'once';
if ($recurring && $recurringInterval === 'once') {
$recurringInterval = 'monthly';
// Client side bug we need to track down, so lets log in Sentry
\Sentry\captureMessage("Recurring Subscription was created with 'once' interval");
}
if (!$amount) {
return Factory::response(['status' => 'error', 'message' => 'you must send an amount']);
......@@ -80,6 +87,7 @@ class wire implements Interfaces\Api
$manager
->setAmount((string) BigNumber::toPlain($amount, $digits))
->setRecurring($recurring)
->setRecurringInterval($recurringInterval)
->setSender(Core\Session::getLoggedInUser())
->setEntity($entity)
->setPayload((array) $_POST['payload']);
......
......@@ -17,7 +17,7 @@ class SalesEarningsMetric extends AbstractEarningsMetric
protected $label = 'Sales';
/** @var string */
protected $description = "Total earnings for the sales you have referred. You earn a 25% commission when your referrals purchase Plus, Pro or Minds Tokens.";
protected $description = "Total earnings for the sales you have referred. You earn a 25% commission when your referrals purchase Pro or Minds Tokens.";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
......
......@@ -17,7 +17,7 @@ class ViewsEarningsMetric extends AbstractEarningsMetric
protected $label = 'Pageviews';
/** @var string */
protected $description = "Total earnings for the pageviews on your channel's assets. You earn $1 for every 1,000 pageviews.";
protected $description = "Total earnings for the pageviews on your channel’s assets. You earn a variable RPM for every 1,000 pageviews.";
/** @var array */
protected $permissions = [ 'user', 'admin' ];
......
......@@ -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);
......
Our team has been heads down in the lab developing open source technology that is changing the world. As always, our goal is to provide you with a platform that enables the free exchange of ideas, protects your digital rights and fairly compensates you for your contributions to the network.
Introducing Minds Pro (beta), a new revenue model for content creators.
- Get paid for your traffic and referrals
- Launch your own website
- Receive multi-currency tips and subscription payments from fans
- Supports video, images, blogs and more
| |
|:--:|
| [![Upgrade to Pro](https://cdn-assets.minds.com/emails/upgrade-to-pro.png){=150x}](https://www.minds.com/pro?__e_ct_guid=<?= $vars['guid']?>&campaign=<?= $vars['campaign']?>&topic=<?= $vars['topic'] ?>&validator=<?= $vars['validator'] ?>) |
| |
Thank you for your support!
......@@ -42,6 +42,9 @@ class FeedSyncEntity implements JsonSerializable
/** @var Entity */
protected $entity;
/** @var bool */
protected $deleted = false;
/**
* Export to public API
* @return array
......
......@@ -80,15 +80,14 @@ class Manager
/**
* Add a video to the transcoding queue
*
* @param Integer $guid - the guid of the video.
* @param boolean $fullhd - whether to transcode full_hd.
* @param Entity $entity - the entity to be re-transcoded.
* @return boolean true if video added to transcode queue.
*/
public function queueTranscoding($guid, $fullhd = false)
public function transcode($entity): bool
{
try {
$this->transcoder->setKey($guid);
$this->transcoder->setFullHD($fullhd ?? false);
$this->transcoder->setKey($entity->guid);
$this->transcoder->setFullHD($entity->getOwnerEntity()->isPro() ?? false);
$this->transcoder->transcode();
return true;
} catch (\Exception $e) {
......
......@@ -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]);
......
......@@ -1525,3 +1525,11 @@ CREATE TABLE minds.notification_batches (
primary key (user_guid, batch_id)
);
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();
});
}
}
This diff is collapsed.
<?php
namespace Minds\Core\Security;
/**
* Domains listed here has been blacklisted due to spam.
* Short urls are also not allowed due to security issues.
*/
class ProhibitedDomains
{
/** @var array */
const DOMAINS = [
'movieblog.tumblr.com',
'moviehdstream.wordpress.com',
'moviehq.tumblr.com',
'moviehq.webs.com',
'moviehq.wordpress.com',
'movieo.wordpress.com',
'movieonline.tumblr.com',
'movieonline.webs.com',
'movieonline.wordpress.com',
'movieonlinehd.tumblr.com',
'movieonlinehd.webs.com',
'movieonlinehd.wordpress.com',
'movies.tumblr.com',
'moviesf.tumblr.com',
'moviesgodetia.com',
'movieslinks4u',
'moviesmount.com',
'moviesmonster.biz',
'moviesondesktop',
'moviesonlinefree.biz',
'moviestream.wordpress.com',
'movieontop.com',
'afllivestreaming.com.au',
'londonolympiccorner',
'nrllivestreaming.com.au',
'24x7livestreamtvchannels.com',
'www.edogo.us',
'all4health.in',
'watches4a.co.uk',
'es.jennyjoseph.com',
'allsportslive24x7.blogspot.com',
'boxing-tv-2014-live-stream.blogspot.com',
'amarblogdalima.blogspot.com',
'www.officialtvstream.com.es',
'topsalor.com',
'busybo.org',
'www.nowvideo.sx',
'180upload.com',
'allmyvideos.net',
'busybo.org',
'hdmovieshouse.biz',
'sportblog.info',
'psport.space',
'discus.space',
'euro2016.it.ua',
'neymar.space',
'espnstream.space',
'2016.vn.u',
'blogstream.space',
'liveextratime.xyz',
'thebestlive.xyz',
'streamoffside.xyz',
'sportmaster2014.page.tl',
'bloggersdelight.dk',
'watchsportslive.space',
'freeforward.xyz',
'live4sports.xyz',
'streamfun.xyz',
'angelfire.com',
'streamtime.xyz',
'futebol2star.com',
'live2sport.com',
'newssports.space',
'onlineolympics.xyz',
'liveolympics.xyz',
'streamontv.xyz',
'londonschedule.com',
'onlineolympics.space',
'sportwinning.xyz',
'streamworld.xyz',
'streamtop.xyz',
'livechampion.xyz',
'playstreams.xyz',
'live4sport.xyz',
'streampage.xyz',
'calendarsport.space',
'fsport.space',
'euro2016.od.ua',
'streambig.xyz',
'sportprediction.xyz',
'streamwork.xyz',
'r041.donnael.com',
'2016.lt.ua',
'vipleague.se',
'liveonline.company',
'liveolympics.space',
'seoandvideomarketing.com.au',
'vipbox.sx',
'germanypolandlivestream.club',
'sportgoal.xyz',
'ggdbsale.com',
'gorillasteroids.eu',
'watchlivesports.space',
'penaltyshootout.xyz',
'streamgroup.xyz',
'streamnew.xyz',
'cottonsport.space',
'gosport.space',
'streambest.xyz',
'penaltyspot.xyz',
'streamthe.xyz',
'liveevents.name',
'londonblog.work',
'testcollections.com',
'alfagy.com',
'teravide1974.full-design.com',
'selfnarhasbllaq1980-blog.logdown.com',
'neipononchoi1984.suomiblog.com',
'gemttranlonthe1985.blogzet.com',
'pitchero.com',
'blogolize.com',
'lisbopholsven1974.thezenweb.com',
'blogocial.com',
'tinyblogging.com',
'share.pho.to',
'community.vietfun.com',
'ockuderla1985.full-design.com',
'unmosimla1978.total-blog.com',
'gemttranlonthe1985.blogzet.com',
'rapptubizboe1978.blogminds.com',
'descduclighgon1973.full-design.com',
'ricphosati1972.full-design.com',
'fuddbluslanmaa1975.blogdigy.com',
'smarforcute1976.blogdigy.com',
'xn--90aizihgi.xn--p1ai',
'tinyurl.com',
'bit.ly',
'bit.do',
'123football.space',
'bitly.com',
'j.mp',
'livestreaming.one',
'livestreaming.life',
'forbest.pw',
'olizev.tdska2ll.ru',
'tdska2ll.ru',
'tdska1ll.ru',
'tdska3ll.ru',
'tdska4ll.ru',
'ihmail.ru',
'tdska5ll.ru',
'tdska6ll.ru',
'll.ru',
'shorl.com',
'scorestream.space',
'bestsplayer.xyz',
'worldwideevents.space',
'worldseries.space',
'best247chemist.net',
'9tn.ru',
'futbolkin2013.ru',
'playnowstore.com',
'qr-url.tk',
'watchonlinerugby.net',
'esecuritys.com',
'rufile.no-ip.ca',
'imzonline.com',
'femeedia.com',
'mediomatic.com',
'savemoneyeasily.com',
'option1pro.com',
'perron07.nl',
'movieonrails.com',
'topmoviestoday.com',
'playnowstore.com',
'g-files.biz',
'dawnloadonline.com',
'thedirsite.com',
'siteslocate.com',
'mydrugdir.com',
'find24hs.com',
'veeble.org',
'movieonrails.com',
'bestmoviehd.net',
'putmovies.info',
'awarefinance.com',
'shurll.com',
'acceptsearch.com',
'signforcover.com',
'raisengine.com',
'rocketcarrental.com',
'godsearchs.com',
'listenhanced.com',
'find24hs.com',
'findinform.com',
'sitesworlds.com',
'rocketcarrental.com',
'thedirsite.com',
'getboook.com',
'pokerarena88.com',
'aquamelia.com',
'beautyskintalks.com',
'getmooovie.com',
'getdriversss.com',
'getsoooft.com',
'getgamesss.com',
'abrts.pro',
'leadbit.biz',
'efght.pro',
'qyresearcheurope.com',
'plusfreemaxfr.com',
'getappmac.com',
'getharlemhealthy.org',
'goo.gl',
'getmooovie.com',
'marketreportscenter.com',
'getsooft.com',
'myowndom.ru',
'print-mgn.ru',
'wiki-data.ru',
'velobog.ru',
'mobisony.ru',
'dzeroki.ru',
'slimkor.ru',
'kak-brosit-kyrit.ru',
'jinyurl.com',
'urlin.us',
'capillus.com',
'siteprofissional.com',
'mitersawjudge.com',
'mohajreen-jeeda.com',
'jobberies.com',
'bestfilms.site',
'baystudios.ch',
'elvenarhack.bid',
'essencephskincare.com',
'blog2learn.com',
'superrugbyonline.net',
'superrugby18.livejournal.com',
'expertairco.com',
'draesthetica.co.uk',
'sphere.social',
'saveabookmarks.xyz',
'/t.co',
'samuelsconstruction.build',
'pmwares.com',
'watchesofwales.co.uk',
'zotero.org',
'speakerdeck.com',
'freesiteslike.com',
'pusha.se',
'vrootdownload.org',
'rubberwebshop.nl',
'restaurerlecorps.info',
'discretthemes.info',
'bride-forever.com',
'simplesmetamorphoses.info',
'mp3gain.com',
'mp4gain.com',
'ttlink.com',
'onepost.cf',
'getmefunds.com',
'vikinail.pl',
'typesofbeauty.info',
'joie6portia93.bloglove.cc',
'htgtea.com',
'tblogz.com',
'liveinternet.ru',
'.diowebhost.com',
'/yoursite.com',
'reworkedgames.eu',
'mp3gain.sourceforge.net',
'pages10.com',
'nudegirIs.info',
'aidagirls.com',
'alsoloves.com',
'hotswishes.com',
'instaphoto.club',
'intimspace.com',
'pornopoisk.info',
'localmodels.online',
'kaikki-mallit.com',
'hotswishes.com',
];
}
This diff is collapsed.
......@@ -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 */
......
......@@ -35,7 +35,8 @@ class RecurringDelegate
->setPaymentMethod($wire->getMethod())
->setAmount($wire->getAmount())
->setUser($wire->getSender())
->setEntity($wire->getReceiver());
->setEntity($wire->getReceiver())
->setInterval($wire->getRecurringInterval());
$this->subscriptionsManager->setSubscription($subscription);
$this->subscriptionsManager->create();
......
......@@ -46,6 +46,9 @@ class Manager
/** @var bool $recurring */
protected $recurring;
/** @var string $recurringInterval */
protected $recurringInterval;
/** @var array $payload */
protected $payload;
......@@ -160,6 +163,17 @@ class Manager
return $this;
}
/**
* Recurring interval
* @param string $interval
* @return self
*/
public function setRecurringInterval(string $interval): self
{
$this->recurringInterval = $interval;
return $this;
}
/**
* Set the payload of the transaction.
*
......@@ -192,7 +206,8 @@ class Manager
->setReceiver($this->receiver)
->setEntity($this->entity)
->setAmount($this->amount)
->setTimestamp(time());
->setTimestamp(time())
->setRecurringInterval($this->recurringInterval);
switch ($this->payload['method']) {
case 'onchain':
......
......@@ -29,6 +29,9 @@ class Wire
/** @var bool **/
private $recurring = false;
/** @var string **/
private $recurringInterval;
/** @var string $method */
private $method = 'tokens';
......
......@@ -90,4 +90,26 @@ class Text
{
return (string) $value;
}
/**
* Runs through a body of text, checking it for values.
*
* @param [type] $haystack - Body of text.
* @param [type] $needles - Array of values to be searched for.
* @param integer $offset - offset to start.
* @return boolean|string - The matching value.
*/
public static function strposa($haystack, $needles, $offset = 0)
{
if (!is_array($needles)) {
$needles = [$needles];
}
foreach ($needles as $query) {
if (stripos($haystack, $query, $offset) !== false) {
// stop on first true result
return $query;
}
}
return false;
}
}
......@@ -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'));
}
}
......@@ -275,28 +275,26 @@ class ManagerSpec extends ObjectBehavior
->shouldReturn(true);
}
public function it_should_abort_if_spam(Blog $blog)
public function it_should_check_for_spam(Blog $blog, Spam $spam)
{
$this->beConstructedWith(
$this->repository,
$this->paywallReview,
$this->slug,
$this->feeds,
null,
$this->spam,
$this->search
);
$spamUrl = 'movieblog.tumblr.com';
$blog->getType()
->willReturn('object');
->willReturn('object');
$blog->getSubtype()
->willReturn('blog');
$blog->getBody()
->shouldBeCalled()
->willReturn('movieblog.tumblr.com');
->willReturn('blog');
$this->shouldThrow(new \Exception('Sorry, you included a reference to a domain name linked to spam. You can not use short urls (eg. bit.ly). Please remove it and try again'))
->duringAdd($blog);
$this->spam->check(Argument::any())->shouldBeCalled()->willReturn(true);
$this->add($blog);
}
}
......@@ -10,6 +10,8 @@ use Psr\Http\Message\RequestInterface;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Media\Services\FFMpeg;
use Minds\Entities\Entity;
use Minds\Entities\User;
class ManagerSpec extends ObjectBehavior
{
......@@ -99,29 +101,37 @@ class ManagerSpec extends ObjectBehavior
->shouldBe('https://url.com/cinemr123/720.mp4');
}
public function it_should_manually_transcode_a_non_hd_video(FFMpeg $transcoder)
public function it_should_manually_transcode_a_non_hd_video(FFMpeg $transcoder, Entity $entity, User $owner)
{
$guid = '123';
$hd = false;
$owner->isPro()->shouldBeCalled()->willReturn($hd);
$entity->getOwnerEntity()->shouldBeCalled()->willReturn($owner);
$entity->get('guid')->shouldBeCalled()->willReturn($guid);
$transcoder->setKey($guid)->shouldBeCalled();
$transcoder->setFullHD($hd)->shouldBeCalled();
$transcoder->transcode()->shouldBeCalled();
$this->queueTranscoding($guid, $hd)
$this->transcode($entity)
->shouldReturn(true);
}
public function it_should_manually_transcode_a_hd_video(FFMpeg $transcoder)
public function it_should_manually_transcode_a_hd_video(FFMpeg $transcoder, Entity $entity, User $owner)
{
$guid = '123';
$hd = true;
$owner->isPro()->shouldBeCalled()->willReturn($hd);
$entity->getOwnerEntity()->shouldBeCalled()->willReturn($owner);
$entity->get('guid')->shouldBeCalled()->willReturn($guid);
$transcoder->setKey($guid)->shouldBeCalled();
$transcoder->setFullHD($hd)->shouldBeCalled();
$transcoder->transcode()->shouldBeCalled();
$this->queueTranscoding($guid, $hd)
$this->transcode($entity)
->shouldReturn(true);
}
}
<?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.
This diff is collapsed.
<?php
namespace Spec\Minds\Core\Security;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Minds\Core\Blogs\Blog;
use Minds\Core\Config;
use Minds\Core\Comments\Comment;
use Minds\Entities\User;
use Minds\Entities\Group;
use Minds\Entities\Entity;
use Minds\Core\Security\ProhibitedDomains;
class SpamSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType('Minds\Core\Security\Spam');
}
public function it_should_detect_spam_in_a_blog(
Blog $blog,
ProhibitedDomains $prohibitedDomains
) {
$blog->getBody()->shouldBeCalled()->willReturn('test bit.ly test');
$blog->getType()->shouldBeCalled()->willReturn('object');
$blog->getSubtype()->shouldBeCalled()->willReturn('blog');
$this->shouldThrow(new \Exception("Sorry, you included a reference to a domain name linked to spam (bit.ly)"))
->duringCheck($blog);
}
public function it_should_detect_spam_in_a_comment(
Comment $comment,
ProhibitedDomains $prohibitedDomains
) {
$comment = new Comment();
$comment->setBody('test bit.ly test');
$comment->setType('comment');
$this->shouldThrow(new \Exception("Sorry, you included a reference to a domain name linked to spam (bit.ly)"))
->duringCheck($comment);
}
public function it_should_detect_spam_in_a_user(
User $user,
ProhibitedDomains $prohibitedDomains
) {
$user = new User('123');
$user['briefdescription'] = 'test bit.ly test';
$user['type'] = 'user';
$this->shouldThrow(new \Exception("Sorry, you included a reference to a domain name linked to spam (bit.ly)"))
->duringCheck($user);
}
public function it_should_detect_spam_in_a_group(
Group $group,
ProhibitedDomains $prohibitedDomains
) {
$group = new Group();
$group->setBriefdescription('test bit.ly test');
$group->setType('group');
$this->shouldThrow(new \Exception("Sorry, you included a reference to a domain name linked to spam (bit.ly)"))
->duringCheck($group);
}
public function it_should_detect_NO_spam_in_a_blog(
Blog $blog,
ProhibitedDomains $prohibitedDomains
) {
$blog->getBody()->shouldBeCalled()->willReturn('test bit.nospam test');
$blog->getType()->shouldBeCalled()->willReturn('object');
$blog->getSubtype()->shouldBeCalled()->willReturn('blog');
$this->check($blog)->shouldReturn(false);
}
public function it_should_detect_NO_spam_in_a_comment(
Comment $comment,
ProhibitedDomains $prohibitedDomains
) {
$comment = new Comment();
$comment->setBody('test bit.nospam test');
$comment->setType('comment');
$this->check($comment)->shouldReturn(false);
}
public function it_should_detect_NO_spam_in_a_user(
User $user,
ProhibitedDomains $prohibitedDomains
) {
$user = new User('123');
$user['briefdescription'] = 'test bit.nospam test';
$user['type'] = 'user';
$this->check($user)->shouldReturn(false);
}
public function it_should_detect_NO_spam_in_a_group(
Group $group,
ProhibitedDomains $prohibitedDomains
) {
$group = new Group();
$group->setBriefdescription('test bit.nospam test');
$group->setType('group');
$this->check($group)->shouldReturn(false);
}
}
......@@ -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;
}
......
This diff is collapsed.
log_errors = On
error_log = /dev/stderr
error_reporting = 2
upload_max_filesize = 1G
post_max_size = 1G
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.