Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.datadog.appsec.php.integration

import com.datadog.appsec.php.docker.AppSecContainer
import com.datadog.appsec.php.docker.FailOnUnmatchedTraces
import com.datadog.appsec.php.docker.InspectContainerHelper
import com.datadog.appsec.php.model.Span
import com.datadog.appsec.php.model.Trace
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestMethodOrder
import org.junit.jupiter.api.condition.EnabledIf
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

import java.io.InputStream
import java.net.http.HttpRequest
import java.net.http.HttpResponse

import static com.datadog.appsec.php.integration.TestParams.getPhpVersion
import static com.datadog.appsec.php.integration.TestParams.getVariant
import static java.net.http.HttpResponse.BodyHandlers.ofString

@Testcontainers
@EnabledIf('isExpectedVersion')
@TestMethodOrder(MethodOrderer.OrderAnnotation)
class Laminas33Tests {

/**
* Laminas MVC 3.3.x supports PHP 7.3–8.1 per composer constraints in www/laminas33.
*/
static boolean expectedVersion =
['7.3', '7.4', '8.0', '8.1'].contains(getPhpVersion()) && !getVariant().contains('zts')

AppSecContainer getContainer() {
getClass().CONTAINER
}

@Container
@FailOnUnmatchedTraces
public static final AppSecContainer CONTAINER =
new AppSecContainer(
workVolume: this.name,
baseTag: 'apache2-mod-php',
phpVersion: getPhpVersion(),
phpVariant: getVariant(),
www: 'laminas33',
)

static void main(String[] args) {
InspectContainerHelper.run(CONTAINER)
}

@Test
@Order(1)
void 'home request sets http route to literal slash'() {
Trace trace = container.traceFromRequest('/') { HttpResponse<InputStream> resp ->
assert resp.statusCode() == 200
}
Span span = trace.first()
assert span.meta.'http.route' == '/'
}

@Test
@Order(2)
void 'Login failure automated event'() {
Trace trace = container.traceFromRequest('/authenticate?email=nonExisiting@email.com') {
HttpResponse<InputStream> resp ->
assert resp.statusCode() == 403
}

Span span = trace.first()
assert span.meta.'appsec.events.users.login.failure.track' == 'true'
assert span.meta.'_dd.appsec.events.users.login.failure.auto.mode' == 'identification'
assert span.meta.'appsec.events.users.login.failure.usr.exists' == 'false'
assert span.meta.'http.route' == '/authenticate'
assert span.metrics._sampling_priority_v1 == 2.0d
}

@Test
@Order(3)
void 'Login success automated event'() {
def trace = container.traceFromRequest('/authenticate?email=ciuser@example.com') {
HttpResponse<InputStream> resp ->
assert resp.statusCode() == 200
}

Span span = trace.first()
assert span.meta.'usr.id' == '1'
assert span.meta.'_dd.appsec.events.users.login.success.auto.mode' == 'identification'
assert span.meta.'appsec.events.users.login.success.track' == 'true'
assert span.meta.'http.route' == '/authenticate'
assert span.metrics._sampling_priority_v1 == 2.0d
}

@Test
@Order(4)
void 'path params trigger WAF block and laminas http route template'() {
HttpRequest req = container.buildReq('/dynamic-path/someValue').GET().build()
def trace = container.traceFromRequest(req, ofString()) { HttpResponse<String> re ->
assert re.statusCode() == 403
assert re.body().toLowerCase().contains('blocked')
}

Span span = trace.first()
assert span.metrics.'_dd.appsec.enabled' == 1.0d
assert span.metrics.'_dd.appsec.waf.duration' > 0.0d
assert span.meta.'_dd.appsec.event_rules.version' != ''
assert span.meta.'appsec.blocked' == 'true'
assert span.meta.'http.route' == '/dynamic-path[/:param01]'
}
}
2 changes: 2 additions & 0 deletions appsec/tests/integration/src/test/www/laminas33/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor/
/data/cache/
28 changes: 28 additions & 0 deletions appsec/tests/integration/src/test/www/laminas33/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "datadog/laminas-appsec-integration",
"description": "Laminas MVC 3.3 app for AppSec integration tests",
"type": "project",
"license": "BSD-3-Clause",
"require": {
"php": "^7.3 || ~8.0.0 || ~8.1.0",
"laminas/laminas-component-installer": "^2.4",
"laminas/laminas-development-mode": "^3.2",
"laminas/laminas-mvc": "3.3.*",
"laminas/laminas-authentication": "^2.9",
"laminas/laminas-db": "^2.13",
"laminas/laminas-session": "^2.10"
},
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/"
}
},
"config": {
"allow-plugins": {
"laminas/laminas-component-installer": true
},
"platform": {
"php": "7.3.33"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

return [
'modules' => require __DIR__ . '/modules.config.php',
'module_listener_options' => [
'use_laminas_loader' => false,
'config_glob_paths' => [
realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php',
],
'config_cache_enabled' => false,
'config_cache_key' => 'application.config.cache',
'module_map_cache_enabled' => false,
'module_map_cache_key' => 'application.module.cache',
'cache_dir' => 'data/cache/',
],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

use Laminas\Authentication\AuthenticationService;
use Laminas\Authentication\Storage\Session as SessionStorage;
use Laminas\Db\Adapter\Adapter;

return [
'db' => [
'driver' => 'Pdo',
'dsn' => 'sqlite:/tmp/laminas_appsec.sqlite',
],
'service_manager' => [
'factories' => [
Adapter::class => function ($container) {
return new Adapter($container->get('config')['db']);
},
AuthenticationService::class => function ($container) {
$storage = new SessionStorage();
return new AuthenticationService($storage);
},
],
],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

return [];
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

use Laminas\Mvc\Application;
use Laminas\Stdlib\ArrayUtils;

$appConfig = require __DIR__ . '/application.config.php';
if (file_exists(__DIR__ . '/development.config.php')) {
$appConfig = ArrayUtils::merge($appConfig, require __DIR__ . '/development.config.php');
}

return Application::init($appConfig)
->getServiceManager();
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

return [
'Laminas\Session',
'Laminas\Db',
'Laminas\Router',
'Laminas\Validator',
'Laminas\ZendFrameworkBridge',
'Application',
];
26 changes: 26 additions & 0 deletions appsec/tests/integration/src/test/www/laminas33/initialize.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash -e

cd /var/www

export DD_TRACE_CLI_ENABLED=false

composer install --no-interaction --no-dev
chown -R www-data:www-data vendor

mkdir -p data/cache /tmp/logs/laminas
rm -f /tmp/laminas_appsec.sqlite
touch /tmp/laminas_appsec.sqlite

php -r '
$pdo = new PDO("sqlite:/tmp/laminas_appsec.sqlite");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT UNIQUE NOT NULL, password TEXT NOT NULL)");
$hash = md5("password");
$stmt = $pdo->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?)");
$stmt->execute(["Ci User", "ciuser@example.com", $hash]);
'

chown www-data:www-data /tmp/laminas_appsec.sqlite
chown -R www-data:www-data /var/www/data
mkdir -p /tmp/logs/laminas
chown www-data:www-data /tmp/logs/laminas
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Application;

use Application\Controller\DynamicPathController;
use Application\Controller\LoginControllerFactory;
use Laminas\Router\Http\Literal;
use Laminas\Router\Http\Segment;
use Laminas\ServiceManager\Factory\InvokableFactory;

return [
'router' => [
'routes' => [
'home' => [
'type' => Literal::class,
'options' => [
'route' => '/',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'index',
],
],
],
'authenticate' => [
'type' => Literal::class,
'options' => [
'route' => '/authenticate',
'defaults' => [
'controller' => Controller\LoginController::class,
'action' => 'auth',
],
],
],
'register' => [
'type' => Literal::class,
'options' => [
'route' => '/register',
'defaults' => [
'controller' => Controller\LoginController::class,
'action' => 'signup',
],
],
],
'dynamic_path' => [
'type' => Segment::class,
'options' => [
'route' => '/dynamic-path[/:param01]',
'constraints' => [
'param01' => '[a-zA-Z0-9_-]+',
],
'defaults' => [
'controller' => DynamicPathController::class,
'action' => 'index',
],
],
],
],
],
'controllers' => [
'factories' => [
Controller\LoginController::class => LoginControllerFactory::class,
Controller\IndexController::class => InvokableFactory::class,
DynamicPathController::class => InvokableFactory::class,
],
],
'view_manager' => [
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'not_found_template' => 'error/404',
'exception_template' => 'error/index',
'template_map' => [
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
],
'template_path_stack' => [
__DIR__ . '/../view',
],
],
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Application\Controller;

use Laminas\Mvc\Controller\AbstractActionController;

class DynamicPathController extends AbstractActionController
{
public function indexAction()
{
$response = $this->getResponse();
$response->setContent('ok');
return $response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Application\Controller;

use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;

class IndexController extends AbstractActionController
{
public function indexAction()
{
return new ViewModel();
}
}
Loading
Loading