OpenTok JWT Authenticacion Bug - opentok

When performing a REST request to the opentok rest API I was getting that my jwt token was "expired".
Wondering around a little bit, I performed a dummy request to the server
just for fetching the server date, by using the same date from the server as the token expiration time I was able to list videos belonging to a session.
This is clearly wrong, the iat time and the exp time should not match the server date.
Possible solutions:
A) The user should be able to specify his server time zone and the OpenTok REST server should match those dates regarding the time zone configured for a given project.
B) Disregard the iat and consider the expiration time in seconds.
Thanks

This is an indication that the clock on your server is not synced correctly. The PHP SDK from version 2.5.0 onwards has JWT implemented and has been proven to work correctly. I recommend you upgrade to v2.5.0 and ensure your server clock is accurate.

Patch
/**
* Useless class used to fix bugs and solve single session archive fetching
* issue in opentok.
*
* This class also implements JWT in order to comply with the new authentication
* system that will be in use during July of 2017.
*
* A problem was also detected when trying to authenticate (date issue)
*
* #see https://github.com/opentok/OpenTok-PHP-SDK/issues/172
* #see https://stackoverflow.com/questions/44768499/opentok-jwt-authenticacion-bug
*
* #author Federico Stange <jpfstange#gmail.com>
*/
namespace stange\opentok;
use \Firebase\JWT\JWT;
use \Guzzle\Common\Event;
use \OpenTok\Util\Client as OpenTokClient;
class OTAuthPlugin extends \OpenTok\Util\Plugin\PartnerAuth{
private $timestamp = null;
public static function getSubscribedEvents(){
return array('request.before_send' => 'onBeforeSend');
}
public function setTimestamp($time){
$this->timestamp =$time;
return $this;
}
public function getTimestamp(){
return $this->timestamp;
}
public function onBeforeSend(Event $event){
$event['request']->addHeader(
'X-OPENTOK-AUTH',
$this->createAuthHeader()
);
}
private function createAuthHeader(){
$token = array(
'ist' => 'project',
'iss' => $this->apiKey,
'iat' => $this->timestamp,
'exp' => $this->timestamp+180,
'jti' => uniqid()
);
return JWT::encode($token, $this->apiSecret);
}
}
class Client extends OpenTokClient{
public function configure($apiKey, $apiSecret, $apiUrl){
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->setBaseUrl($apiUrl);
$this->setUserAgent(OPENTOK_SDK_USER_AGENT, true);
$opentokAuthPlugin = new OTAuthPlugin($apiKey, $apiSecret);
$opentokAuthPlugin->setTimestamp($this->getServerDate());
$this->addSubscriber($opentokAuthPlugin);
$this->configured = true;
}
/**
* Make a request for getting the server date
* this is a bug and it has been reported to the opentok team.
* and to the tech support department.
*
*
*/
public function getServerDate(){
try{
$response = $this->get(
"/v2/project/". md5(uniqid())
)->send();
} catch (\Exception $e) {
$date = $e->getResponse()->getHeader('Date')->toArray();
$date = $date[0];
$serverDate = \DateTime::createFromFormat(
"D, d M Y H:i:s e",
$date
);
return $serverDate->getTimestamp();
}
return $serverDate;
}
public function listArchivesInSession($sessionId){
$url = "/v2/project/{$this->apiKey}/archive?sessionId=$sessionId";
$request = $this->get($url);
return $request->send()->json();
}
}

Related

Unable to retrieve the file_size for file when using Storage::fake for testing

I just upgraded my app from Laravel v8 to v9. Now I'm having an issue with the following code throwing this error:
League\Flysystem\UnableToRetrieveMetadata : Unable to retrieve the file_size for file at location: test_file.xlsx.
My controller:
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class ReportDownloadController extends Controller
{
/**
* Handle the incoming request.
*
* #param string $path
* #return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function __invoke(string $path): \Symfony\Component\HttpFoundation\StreamedResponse
{
return Storage::disk('temp')->download($path, request()->query('filename'));
}
}
The relevant test:
/** #test */
public function it_returns_a_file_download_with_the_provided_filename_presented_to_the_user()
{
$this->withoutExceptionHandling();
Storage::fake('temp');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx');
Storage::disk('temp')->put('test_file.xlsx', $fakeFile);
$response = $this->actingAs($this->user)
->getJson(route('reports.download', ['path' => 'test_file.xlsx', 'filename' => 'Appropriate.xlsx']));
$response->assertDownload('Appropriate.xlsx');
}
I know Flysystem was updated to v3, but I can't seem to figure out how to resolve this issue.
I even created an empty Laravel 9 app to test with the following code and still get the same error, so I don't think it is my app.
public function test_that_file_size_can_be_retrieved()
{
Storage::fake('local');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx', 10);
Storage::disk('local')->put('test_file.xlsx', $fakeFile);
$this->assertEquals(10, (Storage::disk('local')->size('test_file.xlsx') / 1024));
}
What am is missing?
I've had the same problem when switching from Laravel 8 to Laravel 9.
The error Unable to retrieve the file_size was actually thrown because it couldn't find the file at all at the path provided.
In my case, it turned out that the path that was provided to the download function started with a /, which wasn't cause for problem with Flysystem 2 but apparently is with Flysystem 3.
Given the tests you're showing it doesn't look like it's the issue here but maybe this will help anyway.
So it turns out my test was broken all along. I should have been using $fakeFile->hashName() and I had the filename where I should have had '/' for the path in my Storage::put().
Here are the corrected tests that pass as expected:
/** #test */
public function it_returns_a_file_download_with_the_provided_filename_presented_to_the_user()
{
$this->withoutExceptionHandling();
Storage::fake('temp');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx');
Storage::disk('temp')->put('/', $fakeFile);
$response = $this->actingAs($this->user)
->getJson(route('reports.download', ['path' => $fakeFile->hashName(), 'filename' => 'Appropriate.xlsx']));
$response->assertDownload('Appropriate.xlsx');
}
and
public function test_that_file_size_can_be_retrieved()
{
Storage::fake('local');
$fakeFile = UploadedFile::fake()->create('test_file.xlsx');
Storage::disk('local')->put('/', $fakeFile);
$this->assertEquals(0, Storage::disk('local')->size($fakeFile->hashName()));
}
Thanks, #yellaw. Your comment about it not being able to find the file at the path provided helped me realize I had done something stupid.

yii2-websocket issue for getting online users list

I am using this package for a chat application. I am facing issue to get the online users list. There is a way suggested by someone I tried that but no success.
Code below for getting the online users list.
/**
* Subscribe to messages
*
* #param ConnectionInterface $client
* #param string $msg
*/
public function commandSubscribe(ConnectionInterface $client, $msg)
{
$request = #json_decode($msg, true);
$client->talkId = $request['talk_id'] ?? null;
$client->userId = $request['user_id'] ?? null;
$this->clients = $client;
foreach ($this->clients as $key=>$chatClient) {
$onlineUsers[] = $chatClient->name;
}
$client->send( json_encode(['onlineUsers'=> $onlineUsers, 'room'=>$client->talkId, 'user' =>$client->userId ,'message'=> 'User added to room']) );
}
I get the below response:
Response:{"onlineUsers":{},"room":"provider","user":"hassan","message":"User added to room"}

Laravel: Is possible send notification with delay, but changing smtp settings dynamically?

I'm developing a Multi Tenant (multiple database) with Laravel v5.7 and I'm successful in sending queue emails.
In some specific situations, I'd like to send on-demand notifications with 'delay', similar to the guide On-Demand Notifications, but informing the SMTP settings that should be used before sending.
I've developed a class that changes the values of config().
app/Tenant/SmtpConfig.php
class SmtpConfig
{
public static function setConnection(SmtpConta $conta = null)
{
// get connection default settings
$config = config()->get("mail");
// populate connection default settings
foreach ($config as $key => $value) {
if ( $key == 'host' ) { $config[$key] = $conta->mail_host ?? $config[$key]; }
if ( $key == 'from' ) { $config[$key] = [
'address' => ( $conta->mail_host === 'smtp.mailtrap.io' ) ? $config[$key]['address'] : $conta->mail_username,
'name' => $conta->conta ?? $config[$key]['name']
]; }
if ( $key == 'username' ) { $config[$key] = $conta->mail_username ?? $config[$key]; }
if ( $key == 'password' ) { $config[$key] = !empty($conta->mail_password) ? $conta->mail_password : $config[$key]; }
}
$config['encryption'] = ( $conta->mail_host === 'smtp.mailtrap.io' ) ? null : 'ssl';
// set connection default settings
config()->set("mail", $config);
}
}
... and I call this SmtpConfig class in notification:
/**
* Create a new notification instance.
*
* #param $conta
* #param $subject
* #return void
*/
public function __construct(SmtpConta $conta = null, $subject = null)
{
$this->conta = $conta;
$this->subject = $subject;
$when = \Carbon\Carbon::now()->addSecond(100);
$this->delay($when);
app(\App\Tenant\SmtpConfig::class)::setConnection($this->conta);
}
I can send the 'delayed' notification successfully, but apparently it always uses the default values of the .env file.
Now I'm not sure if where I'm calling the class makes any sense or even how can I tell the notification what SMTP configuration it should use.
I'm currently facing a similar challenge, on a Laravel 5.2 codebase using the Notification backport library.
This is an example of my solution, similar to Kit Loong's suggestion. We just extend the Illuminate\Notifications\Channels\MailChannel class and override the send() method.
You'll need to be able to determine the SMTP config from the recipient(s), or notification objects, so you'll need to edit my example as necessary.
Also this assumes your app is using the default Swift_Mailer so YMMV...
<?php
declare (strict_types = 1);
namespace App\Notifications\Channels;
use Illuminate\Notifications\Channels\MailChannel;
use Illuminate\Notifications\Notification;
class DynamicSmtpMailChannel extends MailChannel
{
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
* #return void
*/
public function send($notifiable, Notification $notification)
{
//define this method on your model (note $notifiable could be an array or collection of notifiables!)
$customSmtp = $notifiable->getSmtpConfig();
if ($customSmtp) {
$previousSwiftMailer = $this->mailer->getSwiftMailer();
$swiftTransport = new \Swift_SmtpTransport(
$customSmtp->smtp_server,
$customSmtp->smtp_port,
$customSmtp->smtp_encryption
);
$swiftTransport->setUsername($customSmtp->smtp_user);
$swiftTransport->setPassword($customSmtp->smtp_password);
$this->mailer->setSwiftMailer(new \Swift_Mailer($swiftTransport));
}
$result = parent::send($notifiable, $notification);
if (isset($previousSwiftMailer)) {
//restore the previous mailer
$this->mailer->setSwiftMailer($previousSwiftMailer);
}
return $result;
}
}
It may also be beneficial to keep an ephemeral store of custom swift mailers so you can re-use them in the same invokation/request (think about long-running workers) - like a collection class where a hash of the smtp config is used as the item key.
Best of luck with it.
Edit:
I should probably mention you may need to bind this in the service container. Something like this should suffice:
// in a service provider
public function register()
{
$this->app->bind(
\Illuminate\Notifications\Channels\MailChannel::class
\App\Notifications\Channels\DynamicSmtpMailChannel::class
);
}
Or alternatively, register it as a seperate notification channel.
I think you can also refer to this implementation.
https://stackoverflow.com/a/46135925/6011908
You could execute by passing custom smtp configs.
$transport = new Swift_SmtpTransport(
$customSmtp->host,
$customSmtp->port,
$customSmtp->encryption
);

How to add auth user id or session to logging in Laravel 5.7?

My Goal
I'm willing to add the current authenticated user id to log lines (I'm using syslog driver) as a custom log formatting.
As part of my research, I've seen there are nice packages and tutorials to log user activity to database. But at least for now my intention is to move forward without extra packages and logging to syslog as I'm currently doing.
A typical log call in my app would look like:
logger()->info("Listing all items");
I could also do something like:
$uid = Auth::id();
logger()->info("Listing all items for {$uid}");
It would be similar to what a documentation example suggests:
I'm currently doing this for certain lines, but since I'd like to have it in all log calls it would become like repeating myself every time I log something.
Desired Output
Current:
Dec 10 23:54:05 trinsic Laravel[13606]: local.INFO: Hello world []
Desired:
Dec 10 23:54:05 trinsic Laravel[13606]: local.INFO [AUTH_USER_ID=2452]: Hello world []
My Approach
I have tried to tap the logger with success on changing the format, as suggested on docs.
But my current problem is that at the point the CustomizeFormatter class is excecuted, the Auth id seems not to be resolved just yet (not sure about this, but dd() returns null, so that's my guess):
<?php
namespace App\Logging;
use Illuminate\Support\Facades\Auth;
use Monolog\Formatter\LineFormatter;
class CustomizeFormatter
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
$authUser = Auth::id(); // null
foreach ($logger->getHandlers() as $handler) {
$formatter = new LineFormatter("%channel%.%level_name% [AUTH={$authUser}]: %message% %extra%");
$handler->setFormatter($formatter);
}
}
}
Setup
Laravel 5.7
Log Driver: syslog
// config/logging.php
'syslog' => [
'driver' => 'syslog',
'tap' => [ App\Logging\CustomizeFormatter::class ],
'level' => 'debug',
],
My Question(s)
Is there any way to resolve the auth user at this point, or any other approach to achieve this?
Try passing the request object as a constructor arg to your custom formatter class, then access the user from there:
<?php
namespace App\Logging;
use Illuminate\Support\Facades\Auth;
use Monolog\Formatter\LineFormatter;
class CustomizeFormatter
{
public $request;
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
$authUser = $this->request->user();
foreach ($logger->getHandlers() as $handler) {
$formatter = new LineFormatter("%channel%.%level_name% [AUTH={$authUser->id}]: %message% %extra%");
$handler->setFormatter($formatter);
}
}
}
Based on the answer of DigitalDrifter and part of this post from Emir Karşıyakalı I managed to get a fair enough solution.
I could not grab the User ID since (as per my understanding at this point) it can't be resolved just yet. But I felt satisfied with getting a more or less accurate client id and session id, so I can trace a user interaction thread on logs:
<?php
namespace App\Loggers;
use Monolog\Formatter\LineFormatter;
class LocalLogger
{
private $request;
public function __construct(\Illuminate\Http\Request $request)
{
$this->request = $request;
}
public function __invoke($logger)
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter($this->getLogFormatter());
}
}
protected function getLogFormatter()
{
$uniqueClientId = $this->getUniqueClientId();
$format = str_replace(
'[%datetime%] ',
sprintf('[%%datetime%%] %s ', $uniqueClientId),
LineFormatter::SIMPLE_FORMAT
);
return new LineFormatter($format, null, true, true);
}
protected function getUniqueClientId()
{
$clientId = md5($this->request->server('HTTP_USER_AGENT').'/'.$this->request->ip());
$sessionId = \Session::getId();
return "[{$clientId}:{$sessionId}]";
}
}
Config:
// config/logger.php
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
'tap' => [App\Loggers\LocalLogger::class],
],
Result
Now I can get something like:
Dec 11 13:54:13 trinsic Fimedi[13390]: [2018-12-11 16:54:13] c6c6cb03fafd4b31493478e76b490650 local.INFO: Hello world
or
Dec 11 13:55:44 trinsic Fimedi[13390]: [2018-12-11 16:55:44] [c6c6cb03fafd4b31493478e76b490650:xUs2WxIb3TvKcpCpFNPFyvBChE88Nk0YbwZI3KrY] local.INFO: Hello world
Depending on how I calculate the user/session ids.

How to cancel queued job in Laravel or Redis

How can I browse all the pending jobs within my Redis queue so that I could cancel the Mailable that has a certain emailAddress-sendTime pair?
I'm using Laravel 5.5 and have a Mailable that I'm using successfully as follows:
$sendTime = Carbon::now()->addHours(3);
Mail::to($emailAddress)
->bcc([config('mail.supportTeam.address'), config('mail.main.address')])
->later($sendTime, new MyCustomMailable($subject, $dataForMailView));
When this code runs, a job gets added to my Redis queue.
I've already read the Laravel docs but remain confused.
How can I cancel a Mailable (prevent it from sending)?
I'd love to code a webpage within my Laravel app that makes this easy for me.
Or maybe there are tools that already make this easy (maybe FastoRedis?)? In that case, instructions about how to achieve this goal that way would also be really helpful. Thanks!
Update:
I've tried browsing the Redis queue using FastoRedis, but I can't figure out how to delete a Mailable, such as the red arrow points to here:
UPDATE:
Look at the comprehensive answer I provided below.
Make it easier.
Don't send an email with the later option. You must dispatch a Job with the later option, and this job will be responsible to send the email.
Inside this job, before send the email, check the emailAddress-sendTime pair. If is correct, send the email, if not, return true and the email won't send and the job will finish.
Comprehensive Answer:
I now use my own custom DispatchableWithControl trait instead of the Dispatchable trait.
I call it like this:
$executeAt = Carbon::now()->addDays(7)->addHours(2)->addMinutes(17);
SomeJobThatWillSendAnEmailOrDoWhatever::dispatch($contactId, $executeAt);
namespace App\Jobs;
use App\Models\Tag;
use Carbon\Carbon;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Log;
class SomeJobThatWillSendAnEmailOrDoWhatever implements ShouldQueue {
use DispatchableWithControl,
InteractsWithQueue,
Queueable,
SerializesModels;
protected $contactId;
protected $executeAt;
/**
*
* #param string $contactId
* #param Carbon $executeAt
* #return void
*/
public function __construct($contactId, $executeAt) {
$this->contactId = $contactId;
$this->executeAt = $executeAt;
}
/**
* Execute the job.
*
* #return void
*/
public function handle() {
if ($this->checkWhetherShouldExecute($this->contactId, $this->executeAt)) {
//do stuff here
}
}
/**
* The job failed to process.
*
* #param Exception $exception
* #return void
*/
public function failed(Exception $exception) {
// Send user notification of failure, etc...
Log::error(static::class . ' failed: ' . $exception);
}
}
namespace App\Jobs;
use App\Models\Automation;
use Carbon\Carbon;
use Illuminate\Foundation\Bus\PendingDispatch;
use Log;
trait DispatchableWithControl {
use \Illuminate\Foundation\Bus\Dispatchable {//https://stackoverflow.com/questions/40299080/is-there-a-way-to-extend-trait-in-php
\Illuminate\Foundation\Bus\Dispatchable::dispatch as parentDispatch;
}
/**
* Dispatch the job with the given arguments.
*
* #return \Illuminate\Foundation\Bus\PendingDispatch
*/
public static function dispatch() {
$args = func_get_args();
if (count($args) < 2) {
$args[] = Carbon::now(TT::UTC); //if $executeAt wasn't provided, use 'now' (no delay)
}
list($contactId, $executeAt) = $args;
$newAutomationArray = [
'contact_id' => $contactId,
'job_class_name' => static::class,
'execute_at' => $executeAt->format(TT::MYSQL_DATETIME_FORMAT)
];
Log::debug(json_encode($newAutomationArray));
Automation::create($newAutomationArray);
$pendingDispatch = new PendingDispatch(new static(...$args));
return $pendingDispatch->delay($executeAt);
}
/**
* #param int $contactId
* #param Carbon $executeAt
* #return boolean
*/
public function checkWhetherShouldExecute($contactId, $executeAt) {
$conditionsToMatch = [
'contact_id' => $contactId,
'job_class_name' => static::class,
'execute_at' => $executeAt->format(TT::MYSQL_DATETIME_FORMAT)
];
Log::debug('checkWhetherShouldExecute ' . json_encode($conditionsToMatch));
$automation = Automation::where($conditionsToMatch)->first();
if ($automation) {
$automation->delete();
Log::debug('checkWhetherShouldExecute = true, so soft-deleted record.');
return true;
} else {
return false;
}
}
}
So, now I can look in my 'automations' table to see pending jobs, and I can delete (or soft-delete) any of those records if I want to prevent the job from executing.
Delete job by id.
$job = (new \App\Jobs\SendSms('test'))->delay(5);
$id = app(Dispatcher::class)->dispatch($job);
$res = \Illuminate\Support\Facades\Redis::connection()->zscan('queues:test_queue:delayed', 0, ['match' => '*' . $id . '*']);
$key = array_keys($res[1])[0];
\Illuminate\Support\Facades\Redis::connection()->zrem('queues:test_queue:delayed', $key);
Maybe instead of canceling it you can actually remove it from the Redis, from what Ive read from official docs about forget command on Redis and from Laravel official doc interacting with redis you can basically call any Redis command from the interface, if you could call the forget command and actually pass node_id which in this case I think it's that number you have in your image DEL 1517797158 I think you could achieve the "cancel".
hope this helps
$connection = null;
$default = 'default';
//For the delayed jobs
var_dump( \Queue::getRedis()->connection($connection)->zrange('queues:'.$default.':delayed' ,0, -1) );
//For the reserved jobs
var_dump( \Queue::getRedis()->connection($connection)->zrange('queues:'.$default.':reserved' ,0, -1) );
$connection is the Redis connection name which is null by default, and The $queue is the name of the queue / tube which is 'default' by default!
source : https://stackoverflow.com/a/42182586/6109499
One approach may be to have your job check to see if you've set a specific address/time to be canceled (deleted from queue). Setup a database table or cache a value forever with the address/time in an array. Then in your job's handle method check if anything has been marked for removal and compare it to the mailable's address/time it is processing:
public function handle()
{
if (Cache::has('items_to_remove')) {
$items = Cache::get('items_to_remove');
$removed = null;
foreach ($items as $item) {
if ($this->mail->to === $item['to'] && $this->mail->sendTime === $item['sendTime']) {
$removed = $item;
$this->delete();
break;
}
}
if (!is_null($removed)) {
$diff = array_diff($items, $removed);
Cache::set(['items_to_remove' => $diff]);
}
}
}
I highly recommend checking out the https://laravel.com/docs/master/redis (I run dev/master) but it shows you where they are headed. Most of it works flawlessly now.
Under laravel 8.65 you can just set various status's depending.
protected function listenForEvents()
{
$this->laravel['events']->listen(JobProcessing::class, function ($event) {
$this->writeOutput($event->job, 'starting');
});
$this->laravel['events']->listen(JobProcessed::class, function ($event) {
$this->writeOutput($event->job, 'success');
});
$this->laravel['events']->listen(JobFailed::class, function ($event) {
$this->writeOutput($event->job, 'failed');
$this->logFailedJob($event);
});
}
You can even do $this->canceled;
I highly recommend Muhammads Queues in action PDF. Trust me well worth the money if your using. queues for very important things.... especially with redis . At first TBH I was turned off a bit cause hes a Laravel employee and I thought he should just post things that are helpful but he goes into specific use cases that they do with forge and other items he does plus dives deep into the guts of how queue workers work whether its horizon or whatever. Total eyeopener for me.
Removing all queued jobs:
Redis::command('flushdb');
Using redis-cli I ran this command:
KEYS *queue*
on the Redis instance holding queued jobs,
then deleted whatever keys showed up in the response
DEL queues:default queues:default:reserved
Delete the job from the queue.
$this->delete();

Resources