Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP SDK: Use VaaS HTTP API #678

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
60ae424
WIP Use VaaS HTTP API
lennartdohmann Dec 12, 2024
873de70
WIP Use VaaS HTTP API
lennartdohmann Dec 12, 2024
e1311eb
WIP Tests for VaaS HTTP API
lennartdohmann Dec 12, 2024
35458b0
WIP Basic tests done
lennartdohmann Dec 12, 2024
5b01030
WIP Fix missing phpspec/prophecy-phpunit
lennartdohmann Dec 12, 2024
eb59801
WIP Fix tests
lennartdohmann Dec 12, 2024
005395b
WIP Use hash lookup for tests
lennartdohmann Dec 12, 2024
7b6be35
WIP Fix examples
lennartdohmann Dec 12, 2024
8145185
WIP Fix examples for real
lennartdohmann Dec 12, 2024
61485a0
WIP Fix examples for real for real
lennartdohmann Dec 12, 2024
8b7cf98
WIP Fix streaming by adding content-length and add transfer timeout f…
lennartdohmann Dec 13, 2024
550caf5
WIP Fix tests
lennartdohmann Dec 13, 2024
947a077
Apply review requests
lennartdohmann Dec 16, 2024
5d0c290
Add Sha256Test.php to test suite
lennartdohmann Dec 16, 2024
fcd4ea6
Add logging
lennartdohmann Dec 16, 2024
f1d373b
Fix missing dependency for monolog
lennartdohmann Dec 16, 2024
ce86dd0
Fix monolog version
lennartdohmann Dec 16, 2024
91c88a1
Fix warnings
lennartdohmann Dec 16, 2024
ac30ae0
Add Readme.md content
lennartdohmann Dec 16, 2024
8b9cdbd
Some little more adjustments
lennartdohmann Dec 16, 2024
07ab026
Use a VaaS builder
lennartdohmann Dec 16, 2024
ff49a0e
Update Readme.md
lennartdohmann Dec 16, 2024
52503dd
Update php/examples/Readme.md
lennartdohmann Dec 17, 2024
a2826f7
Refators the Authenntication stuff
ata-no-one Dec 17, 2024
8ad14c8
refactors the forXXX-options
ata-no-one Dec 18, 2024
32c0829
Fix location of phpunit for vscode
lennartdohmann Dec 18, 2024
6d58eb3
Add missing cancellation token
lennartdohmann Dec 18, 2024
dab1c4a
Merge pull request #681 from GDATASoftwareAG/php/http_api_authenticat…
lennartdohmann Dec 18, 2024
926e18e
refactors the options
ata-no-one Dec 18, 2024
7d58aa8
Merge pull request #682 from GDATASoftwareAG/php/http_api_options_pro…
lennartdohmann Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci-php.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ on:
env:
CLIENT_ID: ${{ secrets.CLIENT_ID }}
CLIENT_SECRET: ${{secrets.CLIENT_SECRET}}
VAAS_URL: "wss://gateway.production.vaas.gdatasecurity.de"
VAAS_URL: "https://gateway.production.vaas.gdatasecurity.de"
TOKEN_URL: "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"
VAAS_CLIENT_ID: ${{ secrets.VAAS_CLIENT_ID }}
VAAS_USER_NAME: ${{ secrets.VAAS_USER_NAME }}
Expand Down Expand Up @@ -57,7 +57,7 @@ jobs:
run: |
echo "CLIENT_ID=${{ secrets.STAGING_CLIENT_ID }}" >> $GITHUB_ENV
echo "CLIENT_SECRET=${{ secrets.STAGING_CLIENT_SECRET }}" >> $GITHUB_ENV
echo "VAAS_URL=wss://gateway.staging.vaas.gdatasecurity.de" >> $GITHUB_ENV
echo "VAAS_URL=https://gateway.staging.vaas.gdatasecurity.de" >> $GITHUB_ENV
echo "TOKEN_URL=https://account-staging.gdata.de/realms/vaas-staging/protocol/openid-connect/token" >> $GITHUB_ENV
echo "VAAS_CLIENT_ID=${{ secrets.STAGING_VAAS_CLIENT_ID }}" >> $GITHUB_ENV
echo "VAAS_USER_NAME=${{ secrets.STAGING_VAAS_USER_NAME }}" >> $GITHUB_ENV
Expand All @@ -68,7 +68,7 @@ jobs:
run: |
echo "CLIENT_ID=${{ secrets.DEVELOP_CLIENT_ID }}" >> $GITHUB_ENV
echo "CLIENT_SECRET=${{ secrets.DEVELOP_CLIENT_SECRET }}" >> $GITHUB_ENV
echo "VAAS_URL=wss://gateway.develop.vaas.gdatasecurity.de" >> $GITHUB_ENV
echo "VAAS_URL=https://gateway.develop.vaas.gdatasecurity.de" >> $GITHUB_ENV
echo "TOKEN_URL=https://account-staging.gdata.de/realms/vaas-develop/protocol/openid-connect/token" >> $GITHUB_ENV
echo "VAAS_CLIENT_ID=${{ secrets.DEVELOP_VAAS_CLIENT_ID }}" >> $GITHUB_ENV
echo "VAAS_USER_NAME=${{ secrets.DEVELOP_VAAS_USER_NAME }}" >> $GITHUB_ENV
Expand All @@ -86,7 +86,7 @@ jobs:
php_version: ${{ matrix.version }}

- name: run tests
run: ./vendor/bin/phpunit --colors --testdox
run: ./vendor/bin/phpunit --colors --testdox --exclude-group exclude
working-directory: php/tests/vaas

- name: install example requirements
Expand Down
67 changes: 23 additions & 44 deletions php/examples/VaasExample/AuthenticationExamples.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,34 @@

namespace VaasExamples;

use VaasSdk\Authentication\ClientCredentialsGrantAuthenticator;
use VaasSdk\Exceptions\InvalidSha256Exception;
use VaasSdk\Exceptions\TimeoutException;
use VaasSdk\Exceptions\VaasAuthenticationException;
use VaasSdk\ResourceOwnerPasswordGrantAuthenticator;
use VaasSdk\Authentication\Authenticator;
use VaasSdk\Authentication\GrantType;
use VaasSdk\Options\AuthenticationOptions;
use VaasSdk\Vaas;

$USE_RESOURCE_OWNER_PASSWORD_GRANT_AUTHENTICATOR = false;

// If you got a username and password from us, you can use the ResourceOwnerPasswordAuthenticator like this
if ($USE_RESOURCE_OWNER_PASSWORD_GRANT_AUTHENTICATOR){
$authenticator = new ResourceOwnerPasswordGrantAuthenticator(
"vaas-customer",
getenv("VAAS_USER_NAME"),
getenv("VAAS_PASSWORD"),
getenv("TOKEN_URL")
);
}

// $credentials = new AuthenticationOptions(
// grantType: GrantType::PASSWORD,
// clientId: getenv("VAAS_CLIENT_ID"),
// username: getenv("VAAS_USER_NAME"),
// password: getenv("VAAS_PASSWORD")
// );
// You may use self registration and create a new username and password for the
// ResourceOwnerPasswordAuthenticator by yourself like the example above on https://vaas.gdata.de/login

// If you got a client id and client secret from us, you can use the ClientCredentialsGrantAuthenticator like this
else{
$authenticator = new ClientCredentialsGrantAuthenticator(
getenv("CLIENT_ID"),
getenv("CLIENT_SECRET"),
getenv("TOKEN_URL")
);
}

$vaas = new Vaas(
getenv("VAAS_URL")
// `Password` authentication method by yourself like the example above on https://vaas.gdata.de/login

// If you got a client id and client secret from us, you can use the `Client Credentials` authentication method like this

$credentials = new AuthenticationOptions(
grantType: GrantType::CLIENT_CREDENTIALS,
clientId: getenv("CLIENT_ID"),
clientSecret: getenv("CLIENT_SECRET")
);

try {
$vaas->Connect($authenticator->getToken());
} catch (VaasAuthenticationException $e) {
fwrite(STDERR, "Authentication failed: " . $e->getMessage() . "\n");
exit(1);
}
$authenticator = new Authenticator($credentials);
$vaas = new Vaas($authenticator);

// Get verdict for an eicar hash
try {
$vaasVerdict = $vaas->ForSha256("000005c43196142f01d615a67b7da8a53cb0172f8e9317a2ec9a0a39a1da6fe8");
} catch (InvalidSha256Exception $e) {
fwrite(STDERR, "Invalid sha256: " . $e->getMessage() . "\n");
exit(1);
} catch (TimeoutException $e) {
fwrite(STDERR, "Timeout: " . $e->getMessage() . "\n");
exit(1);
}
fwrite(STDOUT, "Verdict for $vaasVerdict->Sha256 is $vaasVerdict->Verdict \n");
$vaasVerdict = $vaas->forSha256Async("000005c43196142f01d615a67b7da8a53cb0172f8e9317a2ec9a0a39a1da6fe8")->await();
fwrite(STDOUT, "Verdict for $vaasVerdict->sha256 is $vaasVerdict->verdict->value \n");
23 changes: 12 additions & 11 deletions php/examples/VaasExample/GetVerdictByFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

namespace VaasExamples;

use VaasSdk\Authentication\ClientCredentialsGrantAuthenticator;
use VaasSdk\Authentication\Authenticator;
use VaasSdk\Authentication\GrantType;
use VaasSdk\Options\AuthenticationOptions;
use VaasSdk\Vaas;

include_once("./vendor/autoload.php");

$authenticator = new ClientCredentialsGrantAuthenticator(
getenv("CLIENT_ID"),
getenv("CLIENT_SECRET"),
getenv("TOKEN_URL") ?: "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"
$credentials = new AuthenticationOptions(
grantType: GrantType::CLIENT_CREDENTIALS,
clientId: getenv("CLIENT_ID"),
clientSecret: getenv("CLIENT_SECRET")
);

$vaas = (new Vaas())
->withAuthenticator($authenticator)
->withUrl(getenv("VAAS_URL") ?? "wss://gateway.production.vaas.gdatasecurity.de")
lennartdohmann marked this conversation as resolved.
Show resolved Hide resolved
->build();
$authenticator = new Authenticator($credentials);

$vaas = new Vaas($authenticator);
lennartdohmann marked this conversation as resolved.
Show resolved Hide resolved

$scanPath = getenv("SCAN_PATH");
$vaasVerdict = $vaas->ForFile($scanPath);
$vaasVerdict = $vaas->forFileAsync($scanPath)->await();

fwrite(STDOUT, "Verdict for $vaasVerdict->Sha256 is " . $vaasVerdict->Verdict->value . " \n");
fwrite(STDOUT, "Verdict for $vaasVerdict->sha256 is " . $vaasVerdict->verdict->value . " \n");
28 changes: 15 additions & 13 deletions php/examples/VaasExample/GetVerdictByHash.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@

namespace VaasExamples;

use VaasSdk\Authentication\ClientCredentialsGrantAuthenticator;
use VaasSdk\Authentication\Authenticator;
use VaasSdk\Authentication\GrantType;
use VaasSdk\Options\AuthenticationOptions;
use VaasSdk\Vaas;

include_once("./vendor/autoload.php");

$authenticator = new ClientCredentialsGrantAuthenticator(
getenv("CLIENT_ID"),
getenv("CLIENT_SECRET"),
getenv("TOKEN_URL") ?: "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"
$credentials = new AuthenticationOptions(
grantType: GrantType::CLIENT_CREDENTIALS,
clientId: getenv("CLIENT_ID"),
clientSecret: getenv("CLIENT_SECRET")
);
$vaas = (new Vaas())
->withAuthenticator($authenticator)
->withUrl(getenv("VAAS_URL") ?? "wss://gateway.production.vaas.gdatasecurity.de")
lennartdohmann marked this conversation as resolved.
Show resolved Hide resolved
->build();

$authenticator = new Authenticator($credentials);

$vaas = new Vaas($authenticator);

// EICAR
$vaasVerdict = $vaas->ForSha256("000005c43196142f01d615a67b7da8a53cb0172f8e9317a2ec9a0a39a1da6fe8");
fwrite(STDOUT, "Verdict for $vaasVerdict->Sha256 is " . $vaasVerdict->Verdict->value . " \n");
$vaasVerdict = $vaas->forSha256Async("000005c43196142f01d615a67b7da8a53cb0172f8e9317a2ec9a0a39a1da6fe8")->await();
fwrite(STDOUT, "Verdict for $vaasVerdict->sha256 is " . $vaasVerdict->verdict->value . " \n");
// SOMEFILE
$vaasVerdict = $vaas->ForSha256("70caea443deb0d0a890468f9ac0a9b1187676ba3e66eb60a722b187107eb1ea8");
fwrite(STDOUT, "Verdict for $vaasVerdict->Sha256 is " . $vaasVerdict->Verdict->value . " \n");
$vaasVerdict = $vaas->forSha256Async("70caea443deb0d0a890468f9ac0a9b1187676ba3e66eb60a722b187107eb1ea8")->await();
fwrite(STDOUT, "Verdict for $vaasVerdict->sha256 is " . $vaasVerdict->verdict->value . " \n");
28 changes: 15 additions & 13 deletions php/examples/VaasExample/GetVerdictByUrl.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@

namespace VaasExamples;

use VaasSdk\Authentication\ClientCredentialsGrantAuthenticator;
use VaasSdk\Authentication\Authenticator;
use VaasSdk\Authentication\GrantType;
use VaasSdk\Options\AuthenticationOptions;
use VaasSdk\Vaas;

include_once("./vendor/autoload.php");

$authenticator = new ClientCredentialsGrantAuthenticator(
getenv("CLIENT_ID"),
getenv("CLIENT_SECRET"),
getenv("TOKEN_URL") ?: "https://account.gdata.de/realms/vaas-production/protocol/openid-connect/token"
$credentials = new AuthenticationOptions(
grantType: GrantType::CLIENT_CREDENTIALS,
clientId: getenv("CLIENT_ID"),
clientSecret: getenv("CLIENT_SECRET")
);
$vaas = (new Vaas())
->withAuthenticator($authenticator)
->withUrl(getenv("VAAS_URL") ?? "wss://gateway.production.vaas.gdatasecurity.de")
lennartdohmann marked this conversation as resolved.
Show resolved Hide resolved
->build();

$authenticator = new Authenticator($credentials);

$vaas = new Vaas($authenticator);

// EICAR
$vaasVerdict = $vaas->ForUrl("https://secure.eicar.org/eicar.com");
fwrite(STDOUT, "Verdict for $vaasVerdict->Sha256 is " . $vaasVerdict->Verdict->value . " \n");
$vaasVerdict = $vaas->forUrlAsync("https://secure.eicar.org/eicar.com")->await();
fwrite(STDOUT, "Verdict for $vaasVerdict->sha256 is " . $vaasVerdict->verdict->value . " \n");
// SOMEFILE
$vaasVerdict = $vaas->ForUrl("https://www.gdatasoftware.com/oem/verdict-as-a-service");
fwrite(STDOUT, "Verdict for $vaasVerdict->Sha256 is " . $vaasVerdict->Verdict->value . " \n");
$vaasVerdict = $vaas->forUrlAsync("https://www.gdatasoftware.com/oem/verdict-as-a-service")->await();
fwrite(STDOUT, "Verdict for $vaasVerdict->sha256 is " . $vaasVerdict->verdict->value . " \n");
2 changes: 1 addition & 1 deletion php/examples/wordpress/src/gd-scan/Vaas/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace WpGdScan\Vaas;

use VaasSdk\Vaas;
use VaasSdk\Authentication\ClientCredentialsGrantAuthenticator;
use VaasSdk\Vaas;

class Client
{
Expand Down
5 changes: 1 addition & 4 deletions php/phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
<phpunit>
<testsuites>
<testsuite name="VaasTesting">
<file>tests/vaas/StreamsInLoopTest.php</file>
<file>tests/vaas/Sha256Test.php</file>
<file>tests/vaas/VaasTest.php</file>
<file>tests/vaas/ProtocolTest.php</file>
<file>tests/vaas/ClientCredentialsGrantAuthenticatorTest.php</file>
<file>tests/vaas/AuthenticatorTest.php</file>
</testsuite>
</testsuites>
</phpunit>
139 changes: 139 additions & 0 deletions php/src/vaas/Authentication/Authenticator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace VaasSdk\Authentication;

use Amp\Cancellation;
use Amp\Future;
use Amp\Http\Client\HttpClient;
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use Amp\Sync\LocalMutex;
use Amp\Sync\Mutex;
use Exception;
use InvalidArgumentException;
use VaasSdk\Exceptions\VaasAuthenticationException;
use VaasSdk\Options\AuthenticationOptions;
use VaasSdk\Options\VaasOptions;
use function Amp\async;
use function Amp\delay;

class Authenticator
{
private HttpClient $httpClient;
private VaasOptions $options;
private AuthenticationOptions $credentials;
private Mutex $mutex;
private ?TokenResponse $lastTokenResponse = null;
private int $validTo = 0;
private ?int $lastRequestTime = null;

public function __construct(AuthenticationOptions $credentials, ?VaasOptions $options = null, ?HttpClient $httpClient = null)
lennartdohmann marked this conversation as resolved.
Show resolved Hide resolved
{
$this->credentials = $credentials;
$this->options = $options ?? new VaasOptions();
$this->httpClient = $httpClient ?? HttpClientBuilder::buildDefault();
$this->mutex = new LocalMutex();
}

/**
* Gets the access token asynchronously.
* If the token is still valid, it will be returned immediately.
* If the token is expired, a new token will be requested.
* @param Cancellation|null $cancellation Cancellation token
* @return Future Future that resolves to the access token string
*/
public function getTokenAsync(?Cancellation $cancellation = null): Future
{
return async(function () use ($cancellation) {
$lock = $this->mutex->acquire();
try {
$now = time();
if ($this->lastTokenResponse !== null && $this->validTo > $now) {
return $this->lastTokenResponse->accessToken;
}

if ($this->lastRequestTime !== null) {
$timeToWait = $this->lastRequestTime + 1 - $now;
if ($timeToWait > 0) {
delay($timeToWait);
}
}

$this->lastRequestTime = time();
$this->lastTokenResponse = $this->requestTokenAsync($cancellation)->await();
$expiresInSeconds = $this->lastTokenResponse->expiresInSeconds ?? throw new VaasAuthenticationException("Identity provider did not return expires_in");

$this->validTo = time() + $expiresInSeconds;
return $this->lastTokenResponse->accessToken;
}
catch (Exception $ex) {
throw new VaasAuthenticationException("Failed to get token", $ex->getCode(), $ex);
}
finally {
$lock->release();
}
});
}

/**
* Requests a new token asynchronously.
* @param Cancellation|null $cancellation Cancellation token
* @return Future Future that resolves to a TokenResponse
*/
private function requestTokenAsync(?Cancellation $cancellation = null): Future
{
return async(function () use ($cancellation) {
$form = $this->tokenRequestToForm();
$request = new Request($this->options->tokenUrl, 'POST');
$request->setBody($form);
$request->setHeader('Content-Type', 'application/x-www-form-urlencoded');

try {
$response = $this->httpClient->request($request, $cancellation);
} catch (Exception $ex) {
throw new VaasAuthenticationException("Failed to request token", $ex->getCode(), $ex);
}

$stringResponse = $response->getBody()->buffer($cancellation);

if (!$response->isSuccessful()) {
$statusCode = $response->getStatus();
$errorResponse = json_decode($stringResponse, true);
if ($errorResponse === null) {
throw new VaasAuthenticationException("Identity provider returned status code $statusCode: Empty body");
}

throw new VaasAuthenticationException("Identity provider returned status code $statusCode: " . ($errorResponse['error_description'] ?? $errorResponse['error']));
}

$tokenResponse = json_decode($stringResponse, true);
if ($tokenResponse === null) { throw new VaasAuthenticationException("Identity provider returned invalid JSON"); }
if ($tokenResponse['access_token'] === null) { throw new VaasAuthenticationException("Access token is null"); }
if ($tokenResponse['expires_in'] === null) { throw new VaasAuthenticationException("expires_in is null"); }

return new TokenResponse($tokenResponse['access_token'], $tokenResponse['expires_in']);
});
}

/**
* Converts the token request to form data.
* @return string Form data for the token request
*/
private function tokenRequestToForm(): string
{
if ($this->credentials->grantType === GrantType::CLIENT_CREDENTIALS) {
return http_build_query([
'client_id' => $this->credentials->clientId,
'client_secret' => $this->credentials->clientSecret ?? throw new InvalidArgumentException(),
'grant_type' => 'client_credentials',
]);
}

return http_build_query([
'client_id' => $this->credentials->clientId,
'username' => $this->credentials->userName ?? throw new InvalidArgumentException(),
'password' => $this->credentials->password ?? throw new InvalidArgumentException(),
'grant_type' => 'password',
]);
}
}
Loading