Skip to content

feat: Annotate empty request bodies #13606

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

Merged
merged 4 commits into from
Jun 12, 2019
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
60 changes: 60 additions & 0 deletions src/sentry/data/samples/python-formdata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"_meta": {
"request": {
"data": {
"bam": {
"": {
"len": 0,
"rem": [
[
"!raw",
"x",
0,
0
]
]
}
}
}
}
},
"request": {
"url": "http://127.0.0.1:5000/",
"headers": [
[
"Accept",
"*/*"
],
[
"Content-Length",
"75378"
],
[
"Content-Type",
"multipart/form-data; boundary=------------------------f1861cf25dfa767f"
],
[
"Expect",
"100-continue"
],
[
"Host",
"127.0.0.1:5000"
],
[
"User-Agent",
"curl/7.54.0"
]
],
"env": {
"SERVER_PORT": "5000",
"SERVER_NAME": "127.0.0.1"
},
"data": {
"bam": "",
"foo": "bar"
},
"method": "POST",
"inferred_content_type": "multipart/form-data"
}
}
62 changes: 62 additions & 0 deletions src/sentry/data/samples/python-omittedbody.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"_meta": {
"request": {
"data": {
"": {
"len": 5,
"rem": [
[
"!config",
"x",
0,
5
]
]
}
}
}
},
"request": {
"url": "http://127.0.0.1:5000/",
"headers": [
[
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
],
[
"Accept-Encoding",
"gzip, deflate"
],
[
"Accept-Language",
"en-US,en;q=0.5"
],
[
"Cache-Control",
"max-age=0"
],
[
"Connection",
"keep-alive"
],
[
"Host",
"127.0.0.1:5000"
],
[
"Upgrade-Insecure-Requests",
"1"
],
[
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0"
]
],
"data": "",
"method": "POST",
"env": {
"SERVER_PORT": "5000",
"SERVER_NAME": "127.0.0.1"
}
}
}
62 changes: 62 additions & 0 deletions src/sentry/data/samples/python-rawbody.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"_meta": {
"request": {
"data": {
"": {
"len": 5,
"rem": [
[
"!raw",
"x",
0,
5
]
]
}
}
}
},
"request": {
"url": "http://127.0.0.1:5000/",
"headers": [
[
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
],
[
"Accept-Encoding",
"gzip, deflate"
],
[
"Accept-Language",
"en-US,en;q=0.5"
],
[
"Cache-Control",
"max-age=0"
],
[
"Connection",
"keep-alive"
],
[
"Host",
"127.0.0.1:5000"
],
[
"Upgrade-Insecure-Requests",
"1"
],
[
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0"
]
],
"data": "",
"method": "POST",
"env": {
"SERVER_PORT": "5000",
"SERVER_NAME": "127.0.0.1"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,43 @@ import ClippedBox from 'app/components/clippedBox';
import ContextData from 'app/components/contextData';
import ErrorBoundary from 'app/components/errorBoundary';
import KeyValueList from 'app/components/events/interfaces/keyValueList';
import AnnotatedText from 'app/components/events/meta/annotatedText';
import MetaData from 'app/components/events/meta/metaData';

class RichHttpContent extends React.Component {
static propTypes = {
data: PropTypes.object.isRequired,
};

getBodySection = data => {
getBodySection = (data, value, meta) => {
// The http interface provides an inferred content type for the data body.
switch (data.inferredContentType) {
case 'application/json':
return <ContextData data={data.data} />;
case 'application/x-www-form-urlencoded':
return (
<KeyValueList data={objectToSortedTupleArray(data.data)} isContextData={true} />
);
default:
return <pre>{JSON.stringify(data.data, null, 2)}</pre>;
if (meta && (!value || value instanceof String)) {
// TODO(markus): Currently annotated nested objects are shown without
// annotations.
return (
<pre>
<AnnotatedText
value={value}
chunks={meta.chunks}
remarks={meta.rem}
errors={meta.err}
/>
</pre>
);
} else if (value) {
switch (data.inferredContentType) {
case 'application/json':
return <ContextData data={value} />;
case 'application/x-www-form-urlencoded':
case 'multipart/form-data':
return (
<KeyValueList data={objectToSortedTupleArray(value)} isContextData={true} />
);
default:
return <pre>{JSON.stringify(value, null, 2)}</pre>;
}
} else {
return null;
}
};

Expand Down Expand Up @@ -55,11 +75,19 @@ class RichHttpContent extends React.Component {
</ClippedBox>
)}

{data.data && (
<ClippedBox title={t('Body')}>
<ErrorBoundary mini>{this.getBodySection(data)}</ErrorBoundary>
</ClippedBox>
)}
<MetaData object={data} prop="data">
{(value, meta) => {
if (value || meta) {
return (
<ClippedBox title={t('Body')}>
{this.getBodySection(data, value, meta)}
</ClippedBox>
);
}

return null;
}}
</MetaData>

{data.cookies && !objectIsEmpty(data.cookies) && (
<ClippedBox title={t('Cookies')} defaultCollapsed>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,31 @@ const ErrorIcon = styled(InlineSvg)`
const REMARKS = {
a: 'Annotated',
x: 'Removed',
s: 'Substituted',
s: 'Replaced',
m: 'Masked',
p: 'Pseudonymized',
e: 'Encrypted',
};

const KNOWN_RULES = {
'!limit': 'size limits',
'!raw': 'raw payload',
'!config': 'SDK configuration',
};

function getTooltipText(remark, rule) {
const remark_title = REMARKS[remark];
const rule_title = KNOWN_RULES[rule] || t('PII rule "%s"', rule);
if (remark_title) {
return t('%s because of %s', remark_title, rule_title);
} else {
return rule_title;
}
}

function renderChunk(chunk) {
if (chunk.type === 'redaction') {
const title = t('%s due to PII rule "%s"', REMARKS[chunk.remark], chunk.rule_id);
const title = getTooltipText(chunk.remark, chunk.rule_id);
return (
<Tooltip title={title}>
<Redaction>{chunk.text}</Redaction>
Expand All @@ -56,22 +72,18 @@ function renderChunk(chunk) {
}

function renderChunks(chunks) {
if (chunks.length === 1) {
return chunks[0].text;
}

const spans = chunks.map((chunk, key) => React.cloneElement(renderChunk(chunk), {key}));

return <Chunks>{spans}</Chunks>;
}

function renderValue(value, chunks, errors, remarks) {
if (chunks.length) {
if (chunks.length > 1) {
return renderChunks(chunks);
}

let element = null;
if (!_.isNull(value)) {
if (value) {
element = <Redaction>{value}</Redaction>;
} else if (errors && errors.length) {
element = <Placeholder>invalid</Placeholder>;
Expand All @@ -80,7 +92,7 @@ function renderValue(value, chunks, errors, remarks) {
}

if (remarks && remarks.length) {
const title = t('%s due to PII rule "%s"', REMARKS[remarks[0][1]], remarks[0][0]);
const title = getTooltipText(remarks[0][1], remarks[0][0]);
element = <Tooltip title={title}>{element}</Tooltip>;
}

Expand Down
15 changes: 15 additions & 0 deletions tests/acceptance/test_issue_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ def test_python_event(self):
self.visit_issue(event.group.id)
self.browser.snapshot('issue details python')

def test_python_rawbody_event(self):
event = self.create_sample_event(
platform='python-rawbody',
)
self.visit_issue(event.group.id)
self.browser.move_to('.request pre span')
self.browser.snapshot('issue details python raw body')

def test_python_formdata_event(self):
event = self.create_sample_event(
platform='python-formdata',
)
self.visit_issue(event.group.id)
self.browser.snapshot('issue details python formdata')
Copy link
Member

Choose a reason for hiding this comment

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

Did you want the tooltip to display as well? You should be able to trigger a hover with selenium actionchain.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did this for the one snapshot where I expect a tooltip. This one doesn't yet have a tooltip because passing annotations through is too complicated for now.


def test_cocoa_event(self):
event = self.create_sample_event(
platform='cocoa',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ exports[`CrashContent renders with meta data 1`] = `
<Tooltip
containerDisplayMode="inline-block"
position="top"
title="Pseudonymized due to PII rule \\"device_id\\""
title="Pseudonymized because of PII rule \\"device_id\\""
>
<Manager>
<Reference>
Expand Down
Loading