I am working in a project where I must call SOAP WS. I used WSDLtoPHP, that's really helpful.
I can READ data, and now I would like to create new item with the web services. But when I tried to insert data in the field Nom, I have an error, because the soap server thinks I am trying to insert my data ($nom) inside the attribute NomVide of the field Nom, of the generated xml structure with my code:
<ven1:Nom NomVide="$nom"></ven1:Nom>
$createClient = $SC->S001_Creation_Client(new \StructType\S001_Creation_Client(
new \StructType\RootWSReturnError(
new \StructType\Header("","","","","",array(),"","","","","","","","","","","",array(),"",0,0,"",array(),array(),"","","","","","","","","","","","","","","","","","","","",0,0,"","",array(),"","","","",0,0,0,0,0,0,0),null),
new \StructType\RootWSVenteParametres (array( new \StructType\Vente (array("secret_key")))),
new \StructType\RootWSVenteClient(
array(new \StructType\Client(
new \StructType\General(
"3333",
array(),
array(),
null,
new \StructType\Nom ($nom),
new \StructType\NomRecherche ($nomrecherche),
new \StructType\Nom2 ($nom2),
new \StructType\Marque1($marque1),
new \StructType\Marque2 ($marque2),
new \StructType\Adresse1 (),
new \StructType\Adresse2 (),
new \StructType\CodePostal(),
new \StructType\Ville (),
new \StructType\County (),
new \StructType\CountryRegion (),
new \StructType\CurrencyCode ($currencyCode),
new \StructType\CreditLimit ($creditLimit),
new \StructType\Blocked ($blocked),
new \StructType\PaymentMethodCode ($paymentMethodCode),
new \StructType\CustDiscGroup ($custDiscGroup),
new \StructType\SalespersonCode ($salespersonCode),
new \StructType\EquipeAgentCode ($equipeAgentCode),
new \StructType\LocationCode($locationCode),[...]
Logs of the SOAP server gave to me then I am trying to do this:
<ven1:Nom NomVide="$nom"></ven1:Nom>
And it is false of course...
BUT I NEED :
<ven1:Nom NomVide="">$nom</ven1:Nom>
Here is my StructType\Nom class code:
class Nom extends AbstractStructBase
{
/**
* The NomVide
* Meta information extracted from the WSDL
* - use: optional
* #var string|null
*/
protected ?string $NomVide = null;
/**
* Constructor method for Nom
* #uses Nom::setNomVide()
* #param string $nomVide
*/
public function __construct(?string $nomVide = null)
{
$this
->setNomVide($nomVide);
}
/**
* Get NomVide value
* #return string|null
*/
public function getNomVide(): ?string
{
return $this->NomVide;
}
/**
* Set NomVide value
* #param string $nomVide
* #return \StructType\Nom
*/
public function setNomVide(?string $nomVide = null): self
{
// validation for constraint: string
if (!is_null($nomVide) && !is_string($nomVide)) {
throw new InvalidArgumentException(sprintf('Invalid value %s, please provide a string, %s given', var_export($nomVide, true), gettype($nomVide)), __LINE__);
}
$this->NomVide = $nomVide;
return $this;
}
}
Per example there is no problem with the value "3333", which is just a simple string type.
I have this problem with all StructType type.
If someone knows how to help me
I have finally found the solution.
In the WSDL source, there were multiple node with the same name, and in my code only one class were generated by name with WSDLtoPHP.
We asked WSDL owner to change the name of node in the WSDL and we generated new class with WSDLtoPHP librairy.
Now it is work !
Related
I'm trying to add usage records to my subscription. using the stripe create usage record endpoint (https://stripe.com/docs/api#usage_record_create).
running my function im getting an error returning saying Class 'Stripe\UsageRecord' not found in file I havent defined the namespace because I have beeen accessing it directly referencing stripe using \Stripe\ which I brought in using composer. I've tried a composer update but that didnt seem to do the trick. I'm guessing it's missing the UsageRecord.php file from the composer install but I have no clue where to add a copy of the file to the stripe package
public function stripeUsageRecord()
{
$authUser = auth()->user();
$business = $authUser['business_id'];
$user_amount = Transactions::select("user_id")
->where("business_id", "=", $business)
->groupBy("user_id")->count();
$current_time = Carbon::now()->toDateTimeString();
\Stripe\Stripe::setApiKey(env("STRIPE_SECRET"));
\Stripe\UsageRecord::create(array(
"quantity" => $user_amount,
"timestamp" => $current_time,
"subscription_item" => 'sub_DnAKVwNY2Sc4zf',
"action" => 'set'
));
}
Most likely you're using too old version of this library. Stripe\UsageRecord was introduced in version 6.6.0, so I suggest to update library to the last version:
composer require "stripe/stripe-php:^6.19"
You definitely should not modify content of vendor directory and copy&paste classes from different version of library.
Seemed like I was missing a part of the stripe package.
I found a copy of UsageRecord.php online and created the file in path vendor\stripe\stripe-php\lib\UsageRecord.php
I then added the contents of the code that I found online and added them to the file and it worked. The contents are posted below:
<?php
namespace Stripe;
/**
* Class UsageRecord
*
* #package Stripe
*
* #property string $id
* #property string $object
* #property bool $livemode
* #property int $quantity
* #property string $subscription_item
* #property int $timestamp
*/
class UsageRecord extends ApiResource
{
const OBJECT_NAME = "usage_record";
/**
* #param array|null $params
* #param array|string|null $options
*
* #return \Stripe\ApiResource The created resource.
*/
public static function create($params = null, $options = null)
{
self::_validateParams($params);
if (!array_key_exists('subscription_item', $params)) {
throw new Error\InvalidRequest("Missing subscription_item param in request", null);
}
$subscription_item = $params['subscription_item'];
$url = "/v1/subscription_items/$subscription_item/usage_records";
$request_params = $params;
unset($request_params['subscription_item']);
list($response, $opts) = static::_staticRequest('post', $url, $request_params, $options);
$obj = \Stripe\Util\Util::convertToStripeObject($response->json, $opts);
$obj->setLastResponse($response);
return $obj;
}
}
I'm creating a subscription-based SaaS platform in Laravel, where Laravel Cashier does not suit my needs. Therefore I need to implement the subscription-engine myself using the Stripe library.
I found it easy to implement the connection between Laravel and Stripe via hooking into the creation and deletion events of a Subscription class, and then create or cancel a Stripe subscription accordingly.
The Stripe library is unfortunately largely based on calling static methods on some predefined classes (.. like \Stripe\Charge::create()).
This makes it hard for me to test, as you normally would allow dependency injection of some custom client for mocking, but since the Stripe library is referenced statically, there is no client to inject. Is there any way of creating a Stripe client class or such, that I can mock?
Hello from the future!
I was just digging into this. All those classes extend from Stripe's ApiResource class, keep digging and you'll discover that when the library is about to make an HTTP request it calls $this->httpClient(). The httpClient method returns a static reference to a variable called $_httpClient. Conveniently, there is also a static method on the Stripe ApiRequestor class called setHttpClient which accepts an object which is supposed to implement the Stripe HttpClient\ClientInterface (this interface only describes a single method called request).
Soooooo, in your test you can make a call to ApiRequestor::setHttpClient passing it an instance of your own http client mock. Then whenever Stripe makes an HTTP request it will use your mock instead of its default CurlClient. Your responsibility is then have your mock return well-formed Stripe-esque responses and your application will be none the wiser.
Here is a very dumb fake that I've started using in my tests:
<?php
namespace Tests\Doubles;
use Stripe\HttpClient\ClientInterface;
class StripeHttpClientFake implements ClientInterface
{
private $response;
private $responseCode;
private $headers;
public function __construct($response, $code = 200, $headers = [])
{
$this->setResponse($response);
$this->setResponseCode($code);
$this->setHeaders($headers);
}
/**
* #param string $method The HTTP method being used
* #param string $absUrl The URL being requested, including domain and protocol
* #param array $headers Headers to be used in the request (full strings, not KV pairs)
* #param array $params KV pairs for parameters. Can be nested for arrays and hashes
* #param boolean $hasFile Whether or not $params references a file (via an # prefix or
* CURLFile)
*
* #return array An array whose first element is raw request body, second
* element is HTTP status code and third array of HTTP headers.
* #throws \Stripe\Exception\UnexpectedValueException
* #throws \Stripe\Exception\ApiConnectionException
*/
public function request($method, $absUrl, $headers, $params, $hasFile)
{
return [$this->response, $this->responseCode, $this->headers];
}
public function setResponseCode($code)
{
$this->responseCode = $code;
return $this;
}
public function setHeaders($headers)
{
$this->headers = $headers;
return $this;
}
public function setResponse($response)
{
$this->response = file_get_contents(base_path("tests/fixtures/stripe/{$response}.json"));
return $this;
}
}
Hope this helps :)
Based off Colin's answer, here is an example that uses a mocked interface to test creating a subscription in Laravel 8.x.
/**
* #test
*/
public function it_subscribes_to_an_initial_plan()
{
$client = \Mockery::mock(ClientInterface::class);
$paymentMethodId = Str::random();
/**
* Creates initial customer...
*/
$customerId = 'somecustomerstripeid';
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params, $opts) use ($paymentMethodId) {
return $path === "https://api.stripe.com/v1/customers";
})->andReturn([
"{\"id\": \"{$customerId}\" }", 200, []
]);
/**
* Retrieves customer
*/
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params) use ($customerId) {
return $path === "https://api.stripe.com/v1/customers/{$customerId}";
})->andReturn([
"{\"id\": \"{$customerId}\", \"invoice_settings\": {\"default_payment_method\": \"{$paymentMethodId}\"}}", 200, [],
]);
/**
* Set payment method
*/
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params) use ($paymentMethodId) {
return $path === "https://api.stripe.com/v1/payment_methods/{$paymentMethodId}";
})->andReturn([
"{\"id\": \"$paymentMethodId\"}", 200, [],
]);
$subscriptionId = Str::random();
$itemId = Str::random();
$productId = Str::random();
$planName = Plan::PROFESSIONAL;
$plan = Plan::withName($planName);
/**
* Subscription request
*/
$client->shouldReceive('request')
->withArgs(function ($method, $path, $params, $opts) use ($paymentMethodId, $plan) {
$isSubscriptions = $path === "https://api.stripe.com/v1/subscriptions";
$isBasicPrice = $opts["items"][0]["price"] === $plan->stripe_price_id;
return $isSubscriptions && $isBasicPrice;
})->andReturn([
"{
\"object\": \"subscription\",
\"id\": \"{$subscriptionId}\",
\"status\": \"active\",
\"items\": {
\"object\": \"list\",
\"data\": [
{
\"id\": \"{$itemId}\",
\"price\": {
\"object\": \"price\",
\"id\": \"{$plan->stripe_price_id}\",
\"product\": \"{$productId}\"
},
\"quantity\": 1
}
]
}
}", 200, [],
]);
ApiRequestor::setHttpClient($client);
$this->authenticate($this->user);
$res = $this->putJson('/subscribe', [
'plan' => $planName,
'payment_method_id' => $paymentMethodId,
]);
$res->assertSuccessful();
// Actually interesting assertions go here
}
In a symfony projects, I'm trying to persist a line of an association table (profil_role) composed of two objects (profil and role).
First, I developed The create action in the ProfilRoleController of the second project this way:
/** #var Roles $role */
$em = $this->getDoctrine()->getManager('main_project');
$role = $em->getRepository("MyBundle\Entity\Roles")->find($roleId);
$profil = $em->getRepository("MyBundle\Entity\Profil")->find($profilId);
$profilRole = new ProfilRoles();
$profilRole->setRoleId($role->getId());
$profilRole->setProfilId($profil->getId());
$em->persist($profilRole);
$em->flush();
This part of code, call then the post entity action present in the main project:
/**
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Rest\Post("/profil_roles")
*/
public function postEntityAction(ProfilRoles $profilRole)
{
$em = $this->getDoctrine()->getManager();
$em->persist($profilRole);
$em->flush();
return $profilRole;
}
When I try to execute my code i'm getting this king of error:
Execution failed for request: POST /api/profil_roles? HTTP/1.1 {"profil":{"id":"12"},"role":{"id":"3"}}: HTTPCode 500, body {"code":500,"message":"Unable to guess how to get a Doctrine instance from the request information."}
I've tried to use the #ParamConverter annotation, but I don't how to use it my case.
try this:
public function postEntityAction() {
$postData = $request->request->all();
$profileRole = $postData['profile_role']
Instead of this:
public function postEntityAction(ProfilRoles $profilRole)
#AlessandroMinoccheri I've tried to be inspired by your reply to do this and i'ts workin, i don't know if it's the correct way.
/**
* #param ProfilRoles $profilRole
* #param Request $request
* #return ProfilRoles
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Rest\Post("/profil_roles")
*/
public function postEntityAction(Request $request)
{
$profilRole = new ProfilRoles();
$em = $this->getDoctrine()->getManager();
$requete = $request->request->all();
$profilRole->setProfilId($requete['profil']['id']);
$profilRole->setRoleId($requete['role']['id']);
$em->persist($profilRole);
$em->flush();
return $profilRole;
}
I'm trying to create a kind of flash-message class that will "flush" messages when they are fetched the first time. So not on the next HTTP request if, for example, forwarded from one controller action to another (of the same HTTP request)
Here is my class design:
<?php
namespace MartynBiz;
/**
* Flash messages. Slight variation in that this will store a message until it
* is accessed - whether that is a next http request, or same request. Simply
* when get method is called the message is wiped from session.
* TODO move this to martynbiz\php-flash
*/
class Flash
{
/**
* Message storage
*
* #var ArrayObject
*/
protected $storage;
/**
* #param string $storage Name to store messages in session
*/
public function __construct($storage=null)
{
// if storage is not defined, create ArrayObject (not persistent)
if (is_null($storage)) {
$storage = new \ArrayObject();
}
$this->storage = $storage;
}
/**
* Add flash message
*
* #param string $key The key to store the message under
* #param string $message Message to show on next request
*/
public function addMessage($key, $message)
{
// create entry in the session
$this->storage[$key] = $message;
}
/**
* Get flash messages, and reset storage
* #return array Messages to show for current request
*/
public function flushMessages()
{
$messages = $this->storage->getArrayCopy();
// clear storage items
foreach ($this->storage as $key => $value) {
unset($this->storage[$key]);
}
return $messages;
}
}
I've written some PHPUnit tests that also demonstrate how the Flash class might be used:
<?php
// TODO test with an ArrayAccess/Object $storage passed in
use MartynBiz\Flash;
use Zend\Session\Container;
class FlashTest extends PHPUnit_Framework_TestCase
{
protected $flash;
public function testInstantiation()
{
$flash = new Flash();
$this->assertTrue($flash instanceof Flash);
}
public function testGettingSetting()
{
$flash = new Flash();
$flash->addMessage('key1', 'value1');
$flash->addMessage('key2', 'value2');
$flash->addMessage('key2', 'value3');
$expected = array(
'key1' => 'value1',
'key2' => 'value3',
);
// assert first time to access messages
$messages = $flash->flushMessages();
$this->assertEquals($expected, $messages);
// assert messages have been cleared
$messages = $flash->flushMessages();
$this->assertEquals(array(), $messages);
}
public function testCustomStorage()
{
$container = new Container('mycontainer');
$flash = new Flash($container);
$flash->addMessage('key1', 'value1');
$expected = array(
'key1' => 'value1',
);
// assert first time to access messages
$messages = $flash->flushMessages();
$this->assertEquals($expected, $messages);
// assert messages have been cleared
$messages = $flash->flushMessages();
$this->assertEquals(array(), $messages);
}
}
You can see that I'm also passing in a custom $storage (Zend session container instance) which is one option I'd like. These tests appear to pass, however I get the following error on the other ones and I don't really understand what the issue is:
$ ./vendor/bin/phpunit tests/library/FlashTest.php 1
PHPUnit 4.8.21-4-g7f07877 by Sebastian Bergmann and contributors.
.E.
Time: 68 ms, Memory: 4.50Mb
There was 1 error:
1) FlashTest::testGettingSetting
MartynBiz\Flash::flushMessages(): ArrayIterator::next(): Array was modified outside object and internal position is no longer valid
/var/www/crsrc-slimmvc/app/library/MartynBiz/Flash/Flash.php:53
/var/www/crsrc-slimmvc/tests/library/FlashTest.php:33
FAILURES!
Tests: 3, Assertions: 3, Errors: 1.
I've searched around for this error and tried a few alternatives (e.g. $this->storage->getIterator() ) but still I get the same error. Any ideas where I'm going wrong? I guess I'm a little new to ArrayObject.
I currently ran into the problem of handling the authentification of a user on the server, using Laravel and RachetPHP.
What I tried so far:
I changed the driver type of the session to database, giving me an id and payload column. Using \Session::getId() returns a 40 character string.
The cookie information, sent by the WebSocket-Connection does contain a XSRF-TOKEN and a laravel_session, both containing > 200 characters string. The database ID of the users session differs from the id, returned by \Session::getId().
I am already sending the current CSRF-token via the websocket message, but I have no clue how to verify it (the built-in verifier uses requests - which I don't have in the websocket server scope).
Generic Use case:
A User posts a comment in thread. The payload of the sent object would then be:
Something to verify the user (an ID or a token).
The comment itself
If you were to send the user ID, anyone could temper the packet and send the message under another ones user.
My use case:
A user can have n-characters. A character has an avatar, an id, a name, etc.
The user is only used to:
authenticate at the server.
access his characters, and thus perform basic CRUD operations on his characters.
I also have a table locations - a "virtual place", a character can be in... so I got a one-to-one relationship between character and location. The user (character) can then send messages in a location via websocket. The message is inserted at the database on the server. At this point, I need to know:
If the user is authenticated (csrf-token ?)
If the user is the owner of the character (it's very simple to spoof the request with another user's character id)
If you need more information, please let me know.
So this is how I solved this a while ago. In my example, I'm working with Socket.IO, but I'm pretty sure you can easily rewrite the Socket.IO part to get it to work with RachetPHP as well.
Socket Server
The socket server depends on the files cookie.js and array.js, and the node modules express, http, socket.io, request and dotenv. I'm not the original author of cookie.js, but there is no author mentioned in the comments, so I'm not able to give any credits for this, sorry.
This is the server.js file which starts the server. It is a simple socket server that tracks who is currently online. The interesting part however is when the server makes a POST request to socket/auth on the Laravel application:
var express = require('express');
var app = express();
var server = require('http').Server(app)
var io = require('socket.io')(server);
var request = require('request');
var co = require('./cookie.js');
var array = require('./array.js');
// This loads the Laravel .env file
require('dotenv').config({path: '../.env'});
server.listen(process.env.SOCKET_SERVER_PORT);
var activeSockets = {};
var disconnectTimeouts = {};
// When a client connects
io.on('connection', function(socket)
{
console.log('Client connected...');
// Read the laravel_session cookie.
var cookieManager = new co.cookie(socket.handshake.headers.cookie);
var sess = cookieManager.get("laravel_session"); // Rename "laravel_session" to whatever you called it
// This is where the socket asks the Laravel app to authenticate the user
request.post('http://' + process.env.SOCKET_SERVER_HOST + '/socket/auth?s=' + sess, function(error, response, body)
{
try {
// Parse the response from the server
body = JSON.parse(body);
}
catch(e)
{
console.log('Error while parsing JSON', e);
error = true;
}
if ( ! error && response.statusCode == 200 && body.authenticated)
{
// Assign users ID to the socket
socket.userId = body.user.id;
if ( ! array.contains(activeSockets, socket.userId))
{
// The client is now 'active'
activeSockets.push(socket.userId);
var message = body.user.firstname + ' is now online!';
console.log(message);
// Tell everyone that the user has joined
socket.broadcast.emit('userJoined', socket.userId);
}
else if (array.hasKey(disconnectTimeouts, 'user_' + socket.userId))
{
clearTimeout(disconnectTimeouts['user_' + socket.userId]);
delete disconnectTimeouts['user_id' + socket.userId];
}
socket.on('disconnect', function()
{
// The client is 'inactive' if he doesn't reastablish the connection within 10 seconds
// For a 'who is online' list, this timeout ensures that the client does not disappear and reappear on each page reload
disconnectTimeouts['user_' + socket.userId] = setTimeout(function()
{
delete disconnectTimeouts['user_' + socket.userId];
array.remove(activeSockets, socket.userId);
var message = body.user.firstname + ' is now offline.';
console.log(message);
socket.broadcast.emit('userLeft', socket.userId);
}, 10000);
});
}
});
});
I added some comments to the code, so it should be pretty self-explanatory. Please note that I added SOCKET_SERVER_HOST and SOCKET_SERVER_PORT to my Laravel .env-file in order to be able to change the host and port without editing the code and run the server on different environments.
SOCKET_SERVER_HOST = localhost
SOCKET_SERVER_PORT = 1337
Authenticating a user by a session cookie with Laravel
This is the SocketController which parses the cookie and responds whether the user could be authenticated or not (JSON response). Its the same mechanism that you described in your answer. It's not the best design to handle the cookie parsing in the controller, but it should be OK in this case, because the controller only handles that one thing and its functionality isn't used at another point in the application.
/app/Http/Controllers/SocketController.php
<?php namespace App\Http\Controllers;
use App\Http\Requests;
use App\Users\UserRepositoryInterface;
use Illuminate\Auth\Guard;
use Illuminate\Database\DatabaseManager;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
/**
* Class SocketController
* #package App\Http\Controllers
*/
class SocketController extends Controller {
/**
* #var Encrypter
*/
private $encrypter;
/**
* #var DatabaseManager
*/
private $database;
/**
* #var UserRepositoryInterface
*/
private $users;
/**
* Initialize a new SocketController instance.
*
* #param Encrypter $encrypter
* #param DatabaseManager $database
* #param UserRepositoryInterface $users
*/
public function __construct(Encrypter $encrypter, DatabaseManager $database, UserRepositoryInterface $users)
{
parent::__construct();
$this->middleware('internal');
$this->encrypter = $encrypter;
$this->database = $database;
$this->users = $users;
}
/**
* Authorize a user from node.js socket server.
*
* #param Request $request
* #param ResponseFactory $response
* #param Guard $auth
* #return \Symfony\Component\HttpFoundation\Response
*/
public function authenticate(Request $request, ResponseFactory $response, Guard $auth)
{
try
{
$payload = $this->getPayload($request->get('s'));
} catch (\Exception $e)
{
return $response->json([
'authenticated' => false,
'message' => $e->getMessage()
]);
}
$user = $this->users->find($payload->{$auth->getName()});
return $response->json([
'authenticated' => true,
'user' => $user->toArray()
]);
}
/**
* Get session payload from encrypted laravel session.
*
* #param $session
* #return object
* #throws \Exception
*/
private function getPayload($session)
{
$sessionId = $this->encrypter->decrypt($session);
$sessionEntry = $this->getSession($sessionId);
$payload = base64_decode($sessionEntry->payload);
return (object) unserialize($payload);
}
/**
* Fetches base64 encoded session string from the database.
*
* #param $sessionId
* #return mixed
* #throws \Exception
*/
private function getSession($sessionId)
{
$sessionEntry = $this->database->connection()
->table('sessions')->select('*')->whereId($sessionId)->first();
if (is_null($sessionEntry))
{
throw new \Exception('The session could not be found. [Session ID: ' . $sessionId . ']');
}
return $sessionEntry;
}
}
In the constructor you can see that I refer to the internal middleware. I added this middleware to only allow the socket server to make requests to socket/auth.
This is what the middleware looks like:
/app/Http/Middleware/InternalMiddleware.php
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\ResponseFactory;
class InternalMiddleware {
/**
* #var ResponseFactory
*/
private $response;
/**
* #param ResponseFactory $response
*/
public function __construct(ResponseFactory $response)
{
$this->response = $response;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (preg_match(env('INTERNAL_MIDDLEWARE_IP'), $request->ip()))
{
return $next($request);
}
return $this->response->make('Unauthorized', 401);
}
}
To get this middleware to work, register it in the Kernel and add the INTERNAL_MIDDLEWARE_IP property - that is just a regular expression defining which IP addresses are allowed - to your .env-file:
Local testing (any IP):
INTERNAL_MIDDLEWARE_IP = /^.*$/
Production env:
INTERNAL_MIDDLEWARE_IP = /^192\.168\.0\.1$/
I'm sorry I could not help you out with RachetPHP, but I think you get a good idea how this can be solved.
I think I found a solution. Although not very clean, it does what it's supposed to do (I guess...)
The WebSocket-Server gets started by an Artisan Command (by mmochetti#github). I inject these classes into the Command:
Illuminate\Contracts\Encryption\Encrypter
App\Contracts\CsrfTokenVerifier - a custom CsrfTokenVerifier, that simply compares 2 strings (going to put more of the follow logic code in there)
I pass these instances from the command to the server. On the onMessage method, I parse the message sent, containing:
The CSRF-Token of the user
The character-id of the user
I then check if the token is valid, and if the user is the owner of the character.
public function onMessage(ConnectionInterface $from, NetworkMessage $message) {
if (!$this->verifyCsrfToken($from, $message)) {
throw new TokenMismatchException;
}
if (!$this->verifyUser($from, $message)) {
throw new \Exception('test');
}
...
}
private function verifyUser(ConnectionInterface $conn, NetworkMessage $message) {
$cookies = $conn->WebSocket->request->getCookies();
$laravel_session = rawurldecode($cookies['laravel_session']);
$id = $this->encrypter->decrypt($laravel_session);
$session = Session::find($id);
$payload = unserialize(base64_decode($session->payload));
$user_id = $payload['user_id'];
$user = User::find($user_id);
$characters = $this->characterService->allFrom($user);
$character_id = $message->getHeader()['character_id'];
return $characters->contains($character_id);
}
private function verifyCsrfToken($from, NetworkMessage $message) {
$header = $this->getHeaderToken($from);
return $this->verifier->tokensMatch($header, $message->getId());
}
The code could be cleaner for sure, but as a quick hack, it works. I think, instead of using a model for the Session, I should use the Laravel DatabaseSessionHandler.
For Laravel > 5 i use this code:
$cookies = $conn->WebSocket->request->getCookies();
$laravel_session = rawurldecode($cookies['laravel_session']);
$id = $this->encrypter->decrypt($laravel_session);
if(Config::get('session.driver', 'file') == 'file')
{
$session = File::get(storage_path('framework/sessions/' . $id));
}
$session = array_values(unserialize($session));
return $session[4]; // todo: Hack, please think another solution
To get cookies from client through websocket you must change domain in session config and change everywhere websocket host to your domain:
'domain' => 'your.domain.com',