Skip to content

Commit

Permalink
Merge pull request #366 from GDATASoftwareAG/php/for-stream
Browse files Browse the repository at this point in the history
Php/for stream
  • Loading branch information
lennartdohmann authored Feb 22, 2024
2 parents dbc7aed + 71c6396 commit 9832c78
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 12 deletions.
3 changes: 2 additions & 1 deletion php/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.lock
*.lock
package.xml
12 changes: 6 additions & 6 deletions php/src/vaas/ClientCredentialsGrantAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ class ClientCredentialsGrantAuthenticator
private HttpClient $_httpClient;

public function __construct(
string $clientId, string $clientSecret,
string $tokenEndpoint = "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token")
{
string $clientId,
string $clientSecret,
string $tokenEndpoint = "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"
) {
$this->_clientId = $clientId;
$this->_clientSecret = $clientSecret;
$this->_tokenEndpoint = $tokenEndpoint;
Expand All @@ -43,11 +44,10 @@ public function getToken(): string
if ($response->getStatusCode() != 200) {
throw new VaasAuthenticationException($response->getReasonPhrase(), $response->getStatusCode());
}
}
catch (ClientException $e) {
} catch (ClientException $e) {
throw new VaasAuthenticationException($e->getMessage(), $e->getCode());
}
$response_body = json_decode($response->getBody());
return $response_body->access_token;
}
}
}
4 changes: 4 additions & 0 deletions php/src/vaas/Message/Kind.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Kind implements JsonSerializable
public const VERDICT_REQUEST = "VerdictRequest";
public const VERDICT_RESPONSE = "VerdictResponse";
public const VERDICT_REQUEST_FOR_URL = "VerdictRequestForUrl";
public const VERDICT_REQUEST_FOR_STREAM = "VerdictRequestForStream";
public const ERROR = "Error";

private string $_kindString = "";
Expand All @@ -34,6 +35,9 @@ public function __construct(string $type)
case self::VERDICT_REQUEST_FOR_URL:
$this->_kindString = self::VERDICT_REQUEST_FOR_URL;
break;
case self::VERDICT_REQUEST_FOR_STREAM:
$this->_kindString = self::VERDICT_REQUEST_FOR_STREAM;
break;
case self::ERROR:
$this->_kindString = self::ERROR;
break;
Expand Down
19 changes: 19 additions & 0 deletions php/src/vaas/Message/VerdictRequestForStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace VaasSdk\Message;

use Ramsey\Uuid\Rfc4122\UuidV4;

class VerdictRequestForStream
{
public string $guid;
public Kind $kind;
public string $session_id;

public function __construct(string $SessionId, string $uuid = null)
{
$this->kind = new Kind(Kind::VERDICT_REQUEST_FOR_STREAM);
$this->guid = $uuid != null ? $uuid : UuidV4::getFactory()->uuid4()->toString();
$this->session_id = $SessionId;
}
}
88 changes: 86 additions & 2 deletions php/src/vaas/Vaas.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
namespace VaasSdk;

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Stream;
use InvalidArgumentException;
use JsonMapper;
use JsonMapper_Exception;
use Ramsey\Uuid\Rfc4122\UuidV4;
use VaasSdk\Exceptions\TimeoutException;
use VaasSdk\Exceptions\UploadFailedException;
use VaasSdk\Exceptions\VaasAuthenticationException;
Expand All @@ -19,11 +22,13 @@
use VaasSdk\Message\Verdict;
use VaasSdk\Message\Kind;
use VaasSdk\Message\VerdictRequest;
use VaasSdk\Message\VerdictRequestForStream;
use VaasSdk\Message\VerdictResponse;
use VaasSdk\Message\VerdictRequestForUrl;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use VaasSdk\Message\VaasVerdict;
use WebSocket\BadOpcodeException;

class Vaas
{
Expand Down Expand Up @@ -101,7 +106,7 @@ public function ForSha256(string $hashString, string $uuid = null): VaasVerdict
* @throws TimeoutException
* @throws InvalidArgumentException
*/
public function ForUrl(string $url, string $uuid = null): VaasVerdict
public function ForUrl(?string $url, string $uuid = null): VaasVerdict
{
if ($this->_logger != null) $this->_logger->debug("ForUrl", ["URL:" => $url]);

Expand Down Expand Up @@ -154,6 +159,41 @@ public function ForFile(string $path, bool $upload = true, string $uuid = null):
return new VaasVerdict($verdictResponse);
}

/**
* @throws JsonMapper_Exception
* @throws VaasClientException
* @throws TimeoutException
* @throws VaasServerException
* @throws BadOpcodeException
* @throws VaasInvalidStateException
* @throws GuzzleException
* @throws UploadFailedException
*/
public function ForStream(Stream $stream, string $uuid = null): VaasVerdict
{
if ($uuid == null) {
$uuid = UuidV4::getFactory()->uuid4()->toString();
}

$verdictResponse = $this->_verdictResponseForStream($uuid);

if ($verdictResponse->verdict != Verdict::UNKNOWN) {
throw new VaasServerException("Server returned verdict without receiving content.");
}
if ($verdictResponse->upload_token == null || $verdictResponse->upload_token == "") {
throw new JsonMapper_Exception("VerdictResponse missing UploadToken for stream upload.");
}
if ($verdictResponse->url == null || $verdictResponse->url == "") {
throw new JsonMapper_Exception("VerdictResponse missing URL for stream upload.");
}

$this->UploadStream($stream, $verdictResponse->url, $verdictResponse->upload_token);

$verdictResponse = $this->_waitForVerdict($uuid);

return new VaasVerdict($verdictResponse);
}

/**
* @return AuthResponse
* @throws VaasConnectionClosedException
Expand Down Expand Up @@ -295,7 +335,7 @@ private function _handleWebSocketErrorResponse(Error $errorResponse): void
$details = null;
}
$errorType = $errorResponse->getType();
if ($errorType == "ClientError"){
if ($errorType == "ClientError") {
throw new VaasClientException($details);
}
throw new VaasServerException($details);
Expand Down Expand Up @@ -349,6 +389,33 @@ private function _verdictResponseForUrl(string $url, string $uuid = null): Verdi
return $this->_waitForVerdict($request->guid);
}

/**
* @throws JsonMapper_Exception
* @throws VaasClientException
* @throws TimeoutException
* @throws VaasServerException
* @throws BadOpcodeException
* @throws VaasInvalidStateException
*/
private function _verdictResponseForStream(string $uuid = null): VerdictResponse
{
if ($this->_logger != null)
$this->_logger->debug("_verdictResponseForStream");

if (!isset($this->_vaasConnection)) {
throw new VaasInvalidStateException("connect() was not called");
}
$websocket = $this->_vaasConnection->GetAuthenticatedWebsocket();

$request = new VerdictRequestForStream($this->_vaasConnection->SessionId, $uuid);
$websocket->send(json_encode($request));

if ($this->_logger != null)
$this->_logger->debug("verdictResponse", ["VerdictResponse" => json_encode($request)]);

return $this->_waitForVerdict($request->guid);
}

/**
* Sets the timeout in seconds the websocket client can take for one receive
*
Expand Down Expand Up @@ -386,4 +453,21 @@ public function setUploadTimeout(int $UploadTimeoutInSeconds): self
$this->_uploadTimeoutInSeconds = $UploadTimeoutInSeconds;
return $this;
}

/**
* @throws GuzzleException
* @throws UploadFailedException
*/
private function UploadStream(Stream $stream, string $url, string $uploadToken)
{
$response = $this->_httpClient->put($url, [
'body' => $stream,
'content-length' => $stream->getSize(),
'timeout' => $this->_uploadTimeoutInSeconds,
'headers' => ["Authorization" => $uploadToken]
]);
if ($response->getStatusCode() > 399) {
throw new UploadFailedException($response->getReasonPhrase(), $response->getStatusCode());
}
}
}
9 changes: 8 additions & 1 deletion php/tests/vaas/ClientCredentialsGrantAuthenticatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@ public function testAuthenticatorWithInvalidCredentials_ThrowsAccessDeniedExcept
$authenticator = new ClientCredentialsGrantAuthenticator("invalid", "invalid", $_ENV["TOKEN_URL"]);
$authenticator->getToken();
}
}

public function testAuthenticatorWithValidCredentials_ReturnsToken(): void
{
$authenticator = new ClientCredentialsGrantAuthenticator($_ENV["CLIENT_ID"], $_ENV["CLIENT_SECRET"], $_ENV["TOKEN_URL"]);
$token = $authenticator->getToken();
$this->assertNotEmpty($token);
}
}
110 changes: 109 additions & 1 deletion php/tests/vaas/VaasTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@

require_once __DIR__ . "/vendor/autoload.php";

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Stream;
use JsonMapper_Exception;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use VaasSdk\ClientCredentialsGrantAuthenticator;
use VaasSdk\Exceptions\TimeoutException;
use VaasSdk\Exceptions\UploadFailedException;
use VaasSdk\Exceptions\VaasAuthenticationException;
use VaasSdk\Exceptions\VaasClientException;
use VaasSdk\Exceptions\VaasServerException;
use VaasSdk\ResourceOwnerPasswordGrantAuthenticator;
use VaasSdk\Vaas;
use Dotenv\Dotenv;
Expand All @@ -21,6 +27,7 @@
use VaasSdk\Exceptions\VaasInvalidStateException;
use VaasSdk\Message\Verdict;
use VaasSdk\Sha256;
use WebSocket\BadOpcodeException;

final class VaasTest extends TestCase
{
Expand Down Expand Up @@ -399,7 +406,7 @@ public function testForUrl_WithInvalidUrl_ThrowsVaasClientException()
public function testForUrl_WithNull_ThrowsVaasClientException()
{
$vaas = new Vaas($_ENV["VAAS_URL"], $this->_getDebugLogger());
$this->expectException(\TypeError::class);
$this->expectException(\InvalidArgumentException::class);
$vaas->Connect($this->getClientCredentialsGrantAuthenticator()->getToken());

$invalidUrl = null;
Expand All @@ -422,4 +429,105 @@ public function testForUrl_WithStatus4xx_ThrowsVaasClientException()
$verdict = $vaas->ForUrl($invalidUrl);
$this->_getDebugLogger()->info("Verdict for URL " . $invalidUrl . " is " . $verdict->Verdict);
}

/**
* @throws JsonMapper_Exception
* @throws VaasClientException
* @throws VaasServerException
* @throws UploadFailedException
* @throws TimeoutException
* @throws BadOpcodeException
* @throws GuzzleException
* @throws VaasAuthenticationException
* @throws VaasInvalidStateException
*/
public function testForStream_WithEicarString_ReturnsMalicious()
{
$vaas = new Vaas($_ENV["VAAS_URL"], $this->_getDebugLogger());
$vaas->Connect($this->getClientCredentialsGrantAuthenticator()->getToken());
$eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}\$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!\$H+H*";
$stream = fopen(sprintf('data://text/plain,%s', $eicar), 'r');
rewind($stream);
$eicarStream = new Stream($stream);

$verdict = $vaas->ForStream($eicarStream);

$this->assertEquals(Verdict::MALICIOUS, $verdict->Verdict);
}

/**
* @throws JsonMapper_Exception
* @throws VaasClientException
* @throws VaasServerException
* @throws TimeoutException
* @throws UploadFailedException
* @throws GuzzleException
* @throws BadOpcodeException
* @throws VaasInvalidStateException
* @throws VaasAuthenticationException
*/
public function testForStream_WithCleanString_ReturnsClean()
{
$vaas = new Vaas($_ENV["VAAS_URL"], $this->_getDebugLogger());
$vaas->Connect($this->getClientCredentialsGrantAuthenticator()->getToken());
$clean = "I am a clean string";
$stream = fopen(sprintf('data://text/plain,%s', $clean), 'r');
rewind($stream);
$eicarStream = new Stream($stream);

$verdict = $vaas->ForStream($eicarStream);

$this->assertEquals(Verdict::CLEAN, $verdict->Verdict);
}

/**
* @throws GuzzleException
* @throws JsonMapper_Exception
* * @throws VaasClientException
* * @throws VaasServerException
* * @throws TimeoutException
* * @throws UploadFailedException
* * @throws GuzzleException
* * @throws BadOpcodeException
* * @throws VaasInvalidStateException
* * @throws VaasAuthenticationException
*/
public function testForStream_WithCleanUrlContentAsStream_ReturnsClean()
{
$vaas = new Vaas($_ENV["VAAS_URL"], $this->_getDebugLogger());
$vaas->Connect($this->getClientCredentialsGrantAuthenticator()->getToken());
$url = "https://raw.githubusercontent.com/GDATASoftwareAG/vaas/main/Readme.md";
$httpClient = new Client();
$response = $httpClient->get($url);
$stream = new Stream($response->getBody()->detach());

$verdict = $vaas->ForStream($stream);

$this->assertEquals(Verdict::CLEAN, $verdict->Verdict);
}

/**
* @throws GuzzleException
* @throws JsonMapper_Exception
* * @throws VaasClientException
* * @throws VaasServerException
* * @throws TimeoutException
* * @throws UploadFailedException
* * @throws GuzzleException
* * @throws BadOpcodeException
* * @throws VaasInvalidStateException
* * @throws VaasAuthenticationException
*/
public function testForStream_WithEicarUrlContentAsStream_ReturnsMalicious()
{
$vaas = new Vaas($_ENV["VAAS_URL"], $this->_getDebugLogger());
$vaas->Connect($this->getClientCredentialsGrantAuthenticator()->getToken());
$httpClient = new Client();
$response = $httpClient->get(self::MALICIOUS_URL);
$stream = new Stream($response->getBody()->detach());

$verdict = $vaas->ForStream($stream);

$this->assertEquals(Verdict::MALICIOUS, $verdict->Verdict);
}
}
2 changes: 1 addition & 1 deletion php/tests/vaas/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"league/oauth2-client": "^2.4.0"
},
"require-dev": {
"phpunit/phpunit": "^9",
"phpunit/phpunit": "^10",
"phpspec/prophecy-phpunit": "^2"
}
}

0 comments on commit 9832c78

Please sign in to comment.