...
 
Commits (16)
......@@ -176,15 +176,16 @@ production:runners:
stage: deploy:production
image: minds/ci:latest
script:
- IMAGE_LABEL="production"
- $(aws ecr get-login --no-include-email --region us-east-1)
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
- docker pull $CI_REGISTRY_IMAGE/runners:$CI_BUILD_REF
# Push to production register
- docker tag $CI_REGISTRY_IMAGE/runners:$CI_BUILD_REF $ECR_REPOSITORY_URL_RUNNERS
- docker push $ECR_REPOSITORY_URL_RUNNERS:production
- docker tag $CI_REGISTRY_IMAGE/runners:$CI_BUILD_REF $ECR_REPOSITORY_URL_RUNNERS:$IMAGE_LABEL
- docker push $ECR_REPOSITORY_URL_RUNNERS:$IMAGE_LABEL
# Push gitlab registry
- docker tag $CI_REGISTRY_IMAGE/runners:$CI_BUILD_REF $CI_REGISTRY_IMAGE/runners:latest
- docker push $CI_REGISTRY_IMAGE/runners:latest
- docker tag $CI_REGISTRY_IMAGE/runners:$CI_BUILD_REF $CI_REGISTRY_IMAGE/runners:$IMAGE_LABEL
- docker push $CI_REGISTRY_IMAGE/runners:$IMAGE_LABEL
- aws ecs update-service --service=$ECS_RUNNERS_PRODUCTION_SERVICE --force-new-deployment --region us-east-1 --cluster=$ECS_CLUSTER
only:
refs:
......
......@@ -2,6 +2,8 @@
namespace Minds\Api;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain;
use Minds\Interfaces;
use Minds\Helpers;
use Minds\Core\Security;
......@@ -78,15 +80,22 @@ class Factory
*/
public static function pamCheck($request, $response)
{
if ( $request->getAttribute('oauth_user_id')
|| Security\XSRF::validateRequest()
/** @var Domain $proDomain */
$proDomain = Di::_()->get('Pro\Domain');
if (
$request->getAttribute('oauth_user_id') ||
Security\XSRF::validateRequest() ||
$proDomain->validateRequest($request)
) {
return true;
} else {
//error_log('failed authentication:: OAUTH via API');
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
header('HTTP/1.1 401 Unauthorized', true, 401);
echo json_encode([
'error' => 'Sorry, you are not authenticated',
......@@ -108,8 +117,10 @@ class Factory
} else {
error_log('security: unauthorized access to admin api');
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
header('HTTP/1.1 401 Unauthorized', true, 401);
echo json_encode(array('error'=>'You are not an admin', 'code'=>401));
exit;
......@@ -126,8 +137,10 @@ class Factory
return true;
} else {
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
header('HTTP/1.1 401 Unauthorized', true, 401);
echo json_encode([
'status' => 'error',
......@@ -151,11 +164,26 @@ class Factory
ob_end_clean();
static::setCORSHeader();
header('Content-type: application/json');
header("Access-Control-Allow-Origin: *");
echo json_encode($data);
}
/**
* Sets the CORS header, if not already set
*/
public static function setCORSHeader()
{
$wasSet = count(array_filter(headers_list(), function ($header) {
return stripos($header, 'Access-Control-Allow-Origin:') === 0;
})) > 0;
if (!$wasSet) {
header("Access-Control-Allow-Origin: *");
}
}
/**
* Returns the exportable form of the entities
* @param array $entities - an array of entities
......
......@@ -146,9 +146,9 @@ class comments implements Interfaces\Api
default:
$entity = Core\Di\Di::_()->get('EntitiesBuilder')->single($pages[0]);
if ($entity instanceof Entities\Activity && $entity->remind_object) {
$entity = (object) $entity->remind_object;
}
// if ($entity instanceof Entities\Activity && $entity->remind_object) {
// $entity = (object) $entity->remind_object;
// }
if (!$pages[0] || !$entity || $entity->type == 'comment') {
return Factory::response([
......
......@@ -47,6 +47,7 @@ class feed implements Interfaces\Api
$rating = intval($_GET['rating'] ?? $currentUser->getBoostRating());
$platform = $_GET['platform'] ?? 'other';
$quality = 0;
$isBoostFeed = $_GET['boostfeed'] ?? false;
if ($limit === 0) {
return Factory::response([
......@@ -59,6 +60,10 @@ class feed implements Interfaces\Api
$cacher = Core\Data\cache\factory::build('Redis');
$offset = $cacher->get(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator');
if ($isBoostFeed) {
$offset = $_GET['from_timestamp'] ?? 0;
}
// Options specific to newly created users (<=1 hour) and iOS users
if ($platform === 'ios') {
......@@ -112,14 +117,21 @@ class feed implements Interfaces\Api
$next = $iterator->getOffset();
if (isset($boosts[1])) { // Always offset to 2rd in list
$next = $boosts[1]->getTimestamp();
if (isset($boosts[1]) && !$isBoostFeed) { // Always offset to 2rd in list if in rotator
if (!$offset) {
$next = $boosts[1]->getTimestamp();
} else {
$next = 0;
}
} elseif ($isBoostFeed) {
$len = count($boosts);
$next = $boosts[$len -1]->getTimestamp();
}
$ttl = 1800; // 30 minutes
if (($next / 1000) < strtotime('48 hours ago')) {
$ttl = 300; // 5 minutes;
}
// $ttl = 1800; // 30 minutes
// if (($next / 1000) < strtotime('48 hours ago')) {
$ttl = 150; // 2.5 minutes;
// }
$cacher->set(Core\Session::getLoggedinUser()->guid . ':boost-offset-rotator', $next, $ttl);
break;
......
......@@ -61,6 +61,9 @@ class channel implements Interfaces\Api
$type = 'group';
$container_guid = null;
break;
case 'all':
$type = 'all';
break;
}
//
......@@ -101,6 +104,8 @@ class channel implements Interfaces\Api
$forcePublic = (bool) ($_GET['force_public'] ?? false);
$exclude = explode(',', $_GET['exclude'] ?? '');
$query = null;
if (isset($_GET['query'])) {
......@@ -134,6 +139,7 @@ class channel implements Interfaces\Api
'query' => $query,
'single_owner_threshold' => 0,
'pinned_guids' => $type === 'activity' ? array_reverse($container->getPinnedPosts()) : null,
'exclude' => $exclude,
];
if (isset($_GET['nsfw'])) {
......@@ -141,6 +147,20 @@ class channel implements Interfaces\Api
$opts['nsfw'] = explode(',', $nsfw);
}
$hashtag = null;
if (isset($_GET['hashtag'])) {
$hashtag = strtolower($_GET['hashtag']);
}
if ($hashtag) {
$opts['hashtags'] = [$hashtag];
$opts['filter_hashtags'] = true;
} elseif (isset($_GET['hashtags']) && $_GET['hashtags']) {
$opts['hashtags'] = explode(',', $_GET['hashtags']);
$opts['filter_hashtags'] = true;
}
try {
$result = $this->getData($entities, $opts, $asActivities, $sync);
......
<?php
/**
* channel
* @author edgebal
*/
namespace Minds\Controllers\api\v2\pro;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Channel\Manager;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
class channel implements Interfaces\Api
{
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function get($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Pro\Channel\Manager');
$manager->setUser(new User($pages[0]));
switch ($pages[1] ?? '') {
case 'content':
return Factory::response([
'content' => $manager->getAllCategoriesContent(),
]);
}
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
*/
public function post($pages)
{
return Factory::response([]);
}
/**
* 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([]);
}
}
......@@ -7,6 +7,7 @@
namespace Minds\Core\Feeds;
use JsonSerializable;
use Minds\Traits\Exportable;
use Minds\Traits\MagicAttributes;
......@@ -22,7 +23,7 @@ use Minds\Traits\MagicAttributes;
* @method string getUrn()
* @method FeedSyncEntity setUrn(string $urn)
*/
class FeedSyncEntity
class FeedSyncEntity implements JsonSerializable
{
use MagicAttributes;
......@@ -55,4 +56,16 @@ class FeedSyncEntity
'entity' => $this->entity ? $this->entity->export() : null,
];
}
/**
* 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();
}
}
......@@ -96,6 +96,7 @@ class Manager
'filter_hashtags' => false,
'pinned_guids' => null,
'as_activities' => false,
'exclude' => null,
], $opts);
if (isset($opts['query']) && $opts['query']) {
......@@ -107,7 +108,7 @@ class Manager
$response = new Response($result);
return $response;
}
}
$feedSyncEntities = [];
$scores = [];
......
......@@ -53,6 +53,7 @@ class Repository
'exclude_moderated' => false,
'moderation_reservations' => null,
'pinned_guids' => null,
'exclude' => null,
], $opts);
if (!$opts['type']) {
......@@ -67,6 +68,8 @@ class Repository
throw new \Exception('Unsupported period');
}
$type = $opts['type'];
$body = [
'_source' => array_unique([
'guid',
......@@ -75,7 +78,7 @@ class Repository
'time_created',
'access_id',
'moderator_guid',
$this->getSourceField($opts['type']),
$this->getSourceField($type),
]),
'query' => [
'function_score' => [
......@@ -98,7 +101,7 @@ class Repository
'sort' => [],
];
/*if ($opts['type'] === 'group' && false) {
/*if ($type === 'group' && false) {
if (!isset($body['query']['function_score']['query']['bool']['must_not'])) {
$body['query']['function_score']['query']['bool']['must_not'] = [];
}
......@@ -107,7 +110,7 @@ class Repository
'access_id' => ['0', '1', '2'],
],
];
} elseif ($opts['type'] === 'user') {
} elseif ($type === 'user') {
$body['query']['function_score']['query']['bool']['must'][] = [
'term' => [
'access_id' => '2',
......@@ -236,7 +239,7 @@ class Repository
}
}
if ($opts['type'] !== 'group' && $opts['access_id'] !== null) {
if ($type !== 'group' && $opts['access_id'] !== null) {
$body['query']['function_score']['query']['bool']['must'][] = [
'terms' => [
'access_id' => Text::buildArray($opts['access_id']),
......@@ -294,6 +297,14 @@ class Repository
}
}
if ($opts['exclude']) {
$body['query']['function_score']['query']['bool']['must_not'][] = [
'terms' => [
'guid' => Text::buildArray($opts['exclude']),
],
];
}
// firehose options
......@@ -338,9 +349,15 @@ class Repository
//
$esType = $opts['type'];
if ($esType === 'all') {
$esType = 'object:image,object:video,object:blog';
}
$query = [
'index' => $this->index,
'type' => $opts['type'],
'type' => $esType,
'body' => $body,
'size' => $opts['limit'],
'from' => $opts['offset'],
......
......@@ -66,6 +66,7 @@ class Minds extends base
(new \Minds\Entities\EntitiesProvider())->register();
(new Config\ConfigProvider())->register();
(new Router\RouterProvider())->register();
(new OAuth\OAuthProvider())->register();
(new Sessions\SessionsProvider())->register();
(new Boost\BoostProvider())->register();
......
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Pro\Channel;
use Exception;
use Minds\Core\Feeds\Top\Manager as TopManager;
use Minds\Core\Pro\Repository;
use Minds\Core\Pro\Settings;
use Minds\Entities\User;
class Manager
{
/** @var Repository */
protected $repository;
/** @var TopManager */
protected $top;
/** @var User */
protected $user;
/**
* Manager constructor.
* @param Repository $repository
* @param TopManager $top
*/
public function __construct(
$repository = null,
$top = null
)
{
$this->repository = $repository ?: new Repository();
$this->top = $top ?: new TopManager();
}
/**
* @param User $user
* @return $this
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/**
* @return array
* @throws Exception
*/
public function getAllCategoriesContent()
{
if (!$this->user) {
throw new Exception('No user set');
}
/** @var Settings $settings */
$settings = $this->repository->getList([
'user_guid' => $this->user->guid
])->first();
if (!$settings) {
throw new Exception('Invalid Pro user');
}
$tags = $settings->getTagList() ?: [];
$output = [];
$container = (string) $this->user->guid;
foreach ($tags as $tag) {
$opts = [
'container_guid' => $container,
'access_id' => [2, $container],
'hashtags' => [strtolower($tag['tag'])],
'filter_hashtags' => true,
'limit' => 4,
'type' => 'all',
'algorithm' => 'top',
'period' => '7d',
'sync' => true,
'single_owner_threshold' => 0,
];
$content = $this->top->getList($opts)->toArray();
if (count($content) < 2) {
$opts['algorithm'] = 'latest';
$content = $this->top->getList($opts)->toArray();
}
$output[] = [
'tag' => $tag,
'content' => $content,
];
}
return $output;
}
}
......@@ -12,6 +12,7 @@ use Minds\Core\EntitiesBuilder;
use Minds\Core\Pro\Settings;
use Minds\Entities\Object\Carousel;
use Minds\Entities\User;
use Minds\Helpers\Text;
class HydrateSettingsDelegate
{
......@@ -74,6 +75,28 @@ class HydrateSettingsDelegate
error_log($e);
}
try {
if ($user->getPinnedPosts()) {
$pinnedPosts = $this->entitiesBuilder->get(['guids' => Text::buildArray($user->getPinnedPosts())]);
uasort($pinnedPosts, function ($a, $b) {
if (!$a || !$b) {
return 0;
}
return ($a->time_created < $b->time_created) ? 1 : -1;
});
$featuredContent = Text::buildArray(array_values(array_filter(array_map(function ($pinnedPost) {
return $pinnedPost->entity_guid ?: $pinnedPost->guid ?: null;
}, $pinnedPosts))));
$settings->setFeaturedContent($featuredContent);
}
} catch (\Exception $e) {
error_log($e);
}
return $settings;
}
}
<?php
/**
* Domain
* @author edgebal
*/
namespace Minds\Core\Pro;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Zend\Diactoros\ServerRequest;
class Domain
{
/** @var Config */
protected $config;
/** @var Repository */
protected $repository;
/**
* Domain constructor.
* @param Config $config
* @param Repository $repository
*/
public function __construct(
$config = null,
$repository = null
)
{
$this->config = $config ?: Di::_()->get('Config');
$this->repository = $repository ?: new Repository();
}
/**
* @param string $domain
* @return Settings|null
*/
public function lookup(string $domain)
{
$rootDomains = $this->config->get('root_domains') ?: [];
if (in_array(strtolower($domain), $rootDomains)) {
return null;
}
$settings = $this->repository->getList([
'domain' => $domain,
])->first();
return $settings;
}
public function validateRequest(ServerRequest $request)
{
return true;
}
}
......@@ -19,5 +19,13 @@ class ProProvider extends Provider
$this->di->bind('Pro\Manager', function ($di) {
return new Manager();
}, ['useFactory' => true]);
$this->di->bind('Pro\Domain', function ($di) {
return new Domain();
}, ['useFactory' => true]);
$this->di->bind('Pro\Channel\Manager', function ($di) {
return new Channel\Manager();
}, ['useFactory' => true]);
}
}
......@@ -33,13 +33,15 @@ use Minds\Traits\MagicAttributes;
* @method array getFooterLinks()
* @method Settings setFooterLinks(array $footerLinks)
* @method array getTagList()
* @method Settings setTagList(array $footerLinks)
* @method Settings setTagList(array $tagList)
* @method string getScheme()
* @method Settings setScheme(string $scheme)
* @method string getBackgroundImage()
* @method Settings setBackgroundImage(string $backgroundImage)
* @method string getLogoImage()
* @method Settings setLogoImage(string $logoImage)
* @method array getFeaturedContent()
* @method Settings setFeaturedContent(array $featuredContent)
*/
class Settings implements JsonSerializable
{
......@@ -93,6 +95,9 @@ class Settings implements JsonSerializable
/** @var string */
protected $scheme;
/** @var array */
protected $featuredContent = [];
/**
* @return array
*/
......@@ -116,6 +121,7 @@ class Settings implements JsonSerializable
'logo_guid' => (string) $this->logoGuid,
'background_image' => $this->backgroundImage,
'logo_image' => $this->logoImage,
'featured_content' => $this->featuredContent,
'scheme' => $this->scheme,
'styles' => $this->buildStyles(),
];
......
......@@ -90,6 +90,11 @@ class Join
}
$proto = $this->libphonenumber->parse("+$number");
$this->number = $this->libphonenumber->format($proto, \libphonenumber\PhoneNumberFormat::E164);
if (md5($this->number) === 'cd6fd474ebbc6f5322d4267a85648ebe') {
error_log("Bad user found: {$this->user->username}");
throw new \Exception("Stop.");
}
return $this;
}
......
......@@ -70,6 +70,15 @@ class Router
$request = ServerRequestFactory::fromGlobals();
$response = new JsonResponse([]);
/** @var Router\Manager $manager */
$manager = Di::_()->get('Router\Manager');
$result = $manager->handle($request, $response);
if ($result === false) {
return null;
}
if ($request->getMethod() === 'OPTIONS') {
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
......@@ -112,6 +121,7 @@ class Router
if (isset($_GET['referrer'])) {
Helpers\Campaigns\Referrals::register($_GET['referrer']);
}
$loop = count($segments);
while ($loop >= 0) {
$offset = $loop - 1;
......
<?php
/**
* Manager
* @author edgebal
*/
namespace Minds\Core\Router;
use Minds\Core\Router\Middleware;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
class Manager
{
/** @var Middleware\RouterMiddleware[] */
protected $middleware;
/**
* Manager constructor.
* @param Middleware\RouterMiddleware[] $middleware
*/
public function __construct(
$middleware = null
)
{
$this->middleware = $middleware ?: [
new Middleware\ProMiddleware(),
];
}
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return false|null|void
*/
public function handle(ServerRequest &$request, JsonResponse &$response)
{
$result = null;
foreach ($this->middleware as $middleware) {
$result = $middleware->onRequest($request, $response);
if ($result === false) {
break;
}
}
return $result;
}
}
<?php
/**
* ProMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
use Minds\Core\Di\Di;
use Minds\Core\Pro\Domain;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
class ProMiddleware implements RouterMiddleware
{
/** @var Domain */
protected $proDomain;
/**
* ProMiddleware constructor.
* @param Domain $proDomain
*/
public function __construct(
$proDomain = null
)
{
$this->proDomain = $proDomain ?: Di::_()->get('Pro\Domain');
}
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return false|null|void
*/
public function onRequest(ServerRequest $request, JsonResponse &$response)
{
$origin = $request->getServerParams()['HTTP_ORIGIN'];
$host = parse_url($origin, PHP_URL_HOST);
if (!$host) {
return;
}
$settings = $this->proDomain->lookup($host);
if (!$settings) {
return;
}
header(sprintf("Access-Control-Allow-Origin: %s", $origin));
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With,X-No-Cache,x-xsrf-token,x-minds-origin');
if ($request->getMethod() === 'OPTIONS') {
return false;
}
}
}
<?php
/**
* RouterMiddleware
* @author edgebal
*/
namespace Minds\Core\Router\Middleware;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\ServerRequest;
interface RouterMiddleware
{
/**
* @param ServerRequest $request
* @param JsonResponse $response
* @return false|null
*/
public function onRequest(ServerRequest $request, JsonResponse &$response);
}
<?php
/**
* RouterProvider
* @author edgebal
*/
namespace Minds\Core\Router;
use Minds\Core\Di\Provider;
class RouterProvider extends Provider
{
public function register()
{
$this->di->bind('Router\Manager', function ($di) {
return new Manager();
}, [ 'useFactory' => true ]);
}
}
......@@ -358,6 +358,8 @@ class Events
$twofactor = new TwoFactor();
$secret = $twofactor->createSecret(); //we have a new secret for each request
error_log('2fa - sending SMS to ' . $user->guid);
$this->sms->send($user->telno, $twofactor->getCode($secret));
// create a lookup of a random key. The user can then use this key along side their twofactor code
......
......@@ -390,16 +390,16 @@ class User extends \ElggUser
public function addPinned($guid)
{
$pinned = $this->getPinnedPosts();
if (!$pinned) {
$pinned = [];
} else if (count($pinned) > 2) {
array_shift($pinned);
}
if (array_search($guid, $pinned) === false) {
$pinned[] = (string)$guid;
$this->setPinnedPosts($pinned);
$pinned[] = (string) $guid;
}
$this->setPinnedPosts($pinned);
}
/**
......@@ -425,10 +425,10 @@ class User extends \ElggUser
* @return $this
*/
public function setPinnedPosts($pinned) {
if (count($pinned) > 3) {
$pinned = array_slice($pinned, 0, 3);
}
$this->pinned_posts = $pinned;
$maxPinnedPosts = $this->isPro() ? 12 : 3;
$this->pinned_posts = array_slice($pinned, -$maxPinnedPosts, null, false);
return $this;
}
......
......@@ -569,4 +569,4 @@ $CONFIG->set('gitlab', [
'private_key' => ''
]);
$CONFIG->set('root_domains', [ 'minds.com', 'www.minds.com', 'localhost' ]);