Skip to content
Merged
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
Expand Up @@ -12,6 +12,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
Expand Down Expand Up @@ -118,12 +119,26 @@ public static TemplateType determineTemplateType(
}

public static byte[] getTemplate(TemplateType type) {
return getTemplate(type, null);
}

public static byte[] getTemplate(TemplateType type, String securityResponseId) {
byte[] template;
if (type == TemplateType.JSON) {
return TEMPLATE_JSON;
template = TEMPLATE_JSON;
} else if (type == TemplateType.HTML) {
return TEMPLATE_HTML;
template = TEMPLATE_HTML;
} else {
return null;
}
return null;

// Use empty string when securityResponseId is not present
String replacementValue =
(securityResponseId == null || securityResponseId.isEmpty()) ? "" : securityResponseId;

String templateString = new String(template, StandardCharsets.UTF_8);
String replacedTemplate = templateString.replace("[security_response_id]", replacementValue);
return replacedTemplate.getBytes(StandardCharsets.UTF_8);
}

public static String getContentType(TemplateType type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}.security-response-id{font-size:14px;color:#999;margin-top:20px;font-family:monospace}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p><p class="security-response-id">Security Response ID: [security_response_id]</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}
{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}],"security_response_id":"[security_response_id]"}
Original file line number Diff line number Diff line change
Expand Up @@ -67,104 +67,150 @@ class BlockingActionHelperSpecification extends DDSpecification {
null | null
}

void 'getTemplate returning default html template'() {
void 'getTemplate returning default #templateType template'() {
expect:
new String(BlockingActionHelper.getTemplate(HTML), StandardCharsets.UTF_8)
.contains("<title>You've been blocked</title>")
}
new String(BlockingActionHelper.getTemplate(templateType), StandardCharsets.UTF_8)
.contains(expectedContent)

void 'getTemplate returning default JSON template'() {
expect:
new String(BlockingActionHelper.getTemplate(JSON), StandardCharsets.UTF_8)
.contains('"You\'ve been blocked"')
where:
templateType | expectedContent
HTML | "<title>You've been blocked</title>"
JSON | '"You\'ve been blocked"'
}

void 'getTemplate returning custom html template'() {
void 'getTemplate returning custom #templateType template'() {
setup:
File tempDir = File.createTempDir('testTempDir-', '')
Config config = Mock(Config)
File tempFile = new File(tempDir, 'template.html')
tempFile << '<body>My template</body>'
File tempFile = new File(tempDir, fileName)
tempFile << templateContent

when:
BlockingActionHelper.reset(config)

then:
1 * config.getAppSecHttpBlockedTemplateHtml() >> tempFile.toString()
1 * config.getAppSecHttpBlockedTemplateJson() >> null
new String(BlockingActionHelper.getTemplate(HTML), StandardCharsets.UTF_8)
.contains('<body>My template</body>')
1 * config.getAppSecHttpBlockedTemplateHtml() >> (templateType == HTML ? tempFile.toString() : null)
1 * config.getAppSecHttpBlockedTemplateJson() >> (templateType == JSON ? tempFile.toString() : null)
new String(BlockingActionHelper.getTemplate(templateType), StandardCharsets.UTF_8)
.contains(templateContent)

cleanup:
BlockingActionHelper.reset(Config.get())
tempDir.deleteDir()

where:
templateType | fileName | templateContent
HTML | 'template.html' | '<body>My template</body>'
JSON | 'template.json' | '{"foo":"bar"}'
}

void 'getTemplate with null argument'() {
expect:
BlockingActionHelper.getTemplate(null) == null
}

void 'getTemplate returning custom json template'() {
void 'will use default #templateType template if #reason'() {
setup:
File tempDir = File.createTempDir('testTempDir-', '')
Config config = Mock(Config)
File tempFile = new File(tempDir, 'template.json')
tempFile << '{"foo":"bar"}'
File tempDir = tempDirSetup ? File.createTempDir('testTempDir-', '') : null
File template = tempFile ? new File(tempDir, 'template') : null
if (template) {
template << 'a' * (500 * 1024 + 1)
}

when:
BlockingActionHelper.reset(config)

then:
1 * config.getAppSecHttpBlockedTemplateHtml() >> null
1 * config.getAppSecHttpBlockedTemplateJson() >> tempFile.toString()
new String(BlockingActionHelper.getTemplate(JSON), StandardCharsets.UTF_8)
.contains('{"foo":"bar"}')
1 * config.getAppSecHttpBlockedTemplateHtml() >> htmlConfigValue?.call(template)
1 * config.getAppSecHttpBlockedTemplateJson() >> jsonConfigValue?.call(template)
new String(BlockingActionHelper.getTemplate(templateType), StandardCharsets.UTF_8)
.contains(expectedContent)

cleanup:
BlockingActionHelper.reset(Config.get())
tempDir.deleteDir()
}
if (tempDir) {
tempDir.deleteDir()
}

void 'getTemplate with null argument'() {
expect:
BlockingActionHelper.getTemplate(null) == null
where:
templateType | reason | tempDirSetup | tempFile | htmlConfigValue | jsonConfigValue | expectedContent
HTML | 'custom file does not exist' | false | false | { _ -> '/bad/file.html' } | { _ -> '/bad/file.json' } | "<title>You've been blocked</title>"
JSON | 'custom file does not exist' | false | false | { _ -> '/bad/file.html' } | { _ -> '/bad/file.json' } | '"You\'ve been blocked'
HTML | 'custom file is too big' | true | true | { it -> it.toString() } | { it -> it.toString() } | "<title>You've been blocked</title>"
JSON | 'custom file is too big' | true | true | { it -> it.toString() } | { it -> it.toString() } | '"You\'ve been blocked'
}

void 'will use default templates if custom files do not exist'() {
setup:
Config config = Mock(Config)

void 'getTemplate with security_response_id replaces placeholder in #templateType template'() {
given:
def securityResponseId = '12345678-1234-1234-1234-123456789abc'

when:
BlockingActionHelper.reset(config)
def template = BlockingActionHelper.getTemplate(templateType, securityResponseId)
def templateStr = new String(template, StandardCharsets.UTF_8)

then:
1 * config.getAppSecHttpBlockedTemplateHtml() >> '/bad/file.html'
1 * config.getAppSecHttpBlockedTemplateJson() >> '/bad/file.json'
new String(BlockingActionHelper.getTemplate(HTML), StandardCharsets.UTF_8)
.contains("<title>You've been blocked</title>")
new String(BlockingActionHelper.getTemplate(JSON), StandardCharsets.UTF_8)
.contains('"You\'ve been blocked')
!templateStr.contains('[security_response_id]')
templateStr.contains(expectedContent.replace('[id]', securityResponseId))

cleanup:
BlockingActionHelper.reset(Config.get())
where:
templateType | expectedContent
HTML | 'Security Response ID: [id]'
JSON | '"security_response_id":"[id]"'
}

void 'getTemplate without security_response_id uses empty string in #templateType template'() {
when:
def template = BlockingActionHelper.getTemplate(templateType, null)
def templateStr = new String(template, StandardCharsets.UTF_8)

then:
!templateStr.contains('[security_response_id]')
expectedContents.every { content -> templateStr.contains(content) }

where:
templateType | expectedContents
HTML | ['Security Response ID:']
JSON | ['"security_response_id"', '""']
}

void 'getTemplate with empty security_response_id uses empty string'() {
when:
def htmlTemplate = BlockingActionHelper.getTemplate(HTML, '')
def jsonTemplate = BlockingActionHelper.getTemplate(JSON, '')

then:
!new String(htmlTemplate, StandardCharsets.UTF_8).contains('[security_response_id]')
!new String(jsonTemplate, StandardCharsets.UTF_8).contains('[security_response_id]')
}

void 'will use default templates if custom files are too big'() {
void 'getTemplate with security_response_id works with custom #templateType template'() {
setup:
Config config = Mock(Config)
File tempDir = File.createTempDir('testTempDir-', '')
File template = new File(tempDir, 'template')
template << 'a' * (500 * 1024 + 1)
Config config = Mock(Config)
File tempFile = new File(tempDir, fileName)
tempFile << templateContent
def securityResponseId = 'test-block-id-123'

when:
BlockingActionHelper.reset(config)
def template = BlockingActionHelper.getTemplate(templateType, securityResponseId)
def templateStr = new String(template, StandardCharsets.UTF_8)

then:
1 * config.getAppSecHttpBlockedTemplateHtml() >> template.toString()
1 * config.getAppSecHttpBlockedTemplateJson() >> template.toString()
new String(BlockingActionHelper.getTemplate(HTML), StandardCharsets.UTF_8)
.contains("<title>You've been blocked</title>")
new String(BlockingActionHelper.getTemplate(JSON), StandardCharsets.UTF_8)
.contains('"You\'ve been blocked')
1 * config.getAppSecHttpBlockedTemplateHtml() >> (templateType == HTML ? tempFile.toString() : null)
1 * config.getAppSecHttpBlockedTemplateJson() >> (templateType == JSON ? tempFile.toString() : null)
templateStr.contains(expectedContent.replace('[id]', securityResponseId))
!templateStr.contains('[security_response_id]')

cleanup:
BlockingActionHelper.reset(Config.get())
tempDir.deleteDir()

where:
templateType | fileName | templateContent | expectedContent
HTML | 'template.html' | '<body>Custom template with security_response_id: [security_response_id]</body>' | 'Custom template with security_response_id: [id]'
JSON | 'template.json' | '{"error":"blocked","id":"[security_response_id]"}' | '"error":"blocked","id":"[id]"'
}
}
2 changes: 1 addition & 1 deletion dd-java-agent/appsec/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies {
implementation project(':internal-api')
implementation project(':communication')
implementation project(':telemetry')
implementation group: 'io.sqreen', name: 'libsqreen', version: '17.2.0'
implementation group: 'io.sqreen', name: 'libsqreen', version: '17.3.0'
implementation libs.moshi

testImplementation libs.bytebuddy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public boolean tryCommitBlockingResponse(
log.debug("About to call block response function: {}", blockResponseFunction);
boolean res =
blockResponseFunction.tryCommitBlockingResponse(
reqCtx.getTraceSegment(), statusCode, templateType, extraHeaders);
reqCtx.getTraceSegment(), statusCode, templateType, extraHeaders, null);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we expose the block id in the external public API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, is not specified in the RFC, I'm asking other team for their implementations and aiming to specify it in the RFC.
Right now the answer IMHO is no

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, we don't want to expose that

if (res) {
TraceSegment traceSegment = reqCtx.getTraceSegment();
if (traceSegment != null) {
Expand Down
Loading