Skip to content

Commit a493cc2

Browse files
authored
Merge pull request #12043 from Turbo87/contact-form
support/crate-report-form: Split `security` checkbox into `malicious-code` and `vulnerability`
2 parents 451c201 + 4e0b949 commit a493cc2

File tree

4 files changed

+216
-61
lines changed

4 files changed

+216
-61
lines changed

app/components/support/crate-report-form.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@
6262
}
6363
}
6464

65+
.vulnerability-report {
66+
padding: var(--space-s) var(--space-s);
67+
background-color: light-dark(white, #141413);
68+
border: 1px solid var(--gray-border);
69+
border-radius: var(--space-3xs);
70+
width: 100%;
71+
72+
:first-child {
73+
margin-top: 0;
74+
}
75+
76+
:last-child {
77+
margin-bottom: 0;
78+
}
79+
}
80+
6581
.buttons {
6682
position: relative;
6783
margin: var(--space-m) 0;

app/components/support/crate-report-form.gjs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Input, Textarea } from '@ember/component';
22
import { fn, uniqueId } from '@ember/helper';
33
import { on } from '@ember/modifier';
44
import { action } from '@ember/object';
5+
import { LinkTo } from '@ember/routing';
56
import { service } from '@ember/service';
67
import Component from '@glimmer/component';
78
import { tracked } from '@glimmer/tracking';
@@ -24,8 +25,12 @@ const REASONS = [
2425
description: 'it is abusive or otherwise harmful',
2526
},
2627
{
27-
reason: 'security',
28-
description: 'it contains a vulnerability (please try to contact the crate author first)',
28+
reason: 'malicious-code',
29+
description: 'it contains malicious code',
30+
},
31+
{
32+
reason: 'vulnerability',
33+
description: 'it contains a vulnerability',
2934
},
3035
{
3136
reason: 'other',
@@ -76,6 +81,14 @@ export default class CrateReportForm extends Component {
7681
this.reasonsInvalid = false;
7782
}
7883

84+
get isMaliciousCodeReport() {
85+
return this.selectedReasons.includes('malicious-code');
86+
}
87+
88+
get isVulnerabilityReport() {
89+
return this.selectedReasons.includes('vulnerability');
90+
}
91+
7992
@action
8093
submit() {
8194
if (!this.validate()) {
@@ -87,7 +100,7 @@ export default class CrateReportForm extends Component {
87100
}
88101

89102
composeMail() {
90-
let crate = this.crate;
103+
let { crate, isMaliciousCodeReport } = this;
91104
let reasons = this.reasons
92105
.map(({ reason, description }) => {
93106
let selected = this.isReasonSelected(reason);
@@ -103,9 +116,16 @@ Additional details:
103116
${this.detail}
104117
`;
105118
let subject = `The "${crate}" crate`;
106-
let address = '[email protected]';
107-
let mailto = `mailto:${address}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
108-
return mailto;
119+
if (isMaliciousCodeReport) {
120+
subject = `[SECURITY] ${subject}`;
121+
}
122+
123+
let addresses = '[email protected]';
124+
if (isMaliciousCodeReport) {
125+
addresses += ',[email protected]';
126+
}
127+
128+
return `mailto:${addresses}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
109129
}
110130

111131
<template>
@@ -167,6 +187,20 @@ ${this.detail}
167187
{{/if}}
168188
</fieldset>
169189

190+
{{#if this.isVulnerabilityReport}}
191+
<div class='vulnerability-report form-group' data-test-id='vulnerability-report'>
192+
<h3>🔍 Vulnerability Report</h3>
193+
<p>For crate vulnerabilities, please consider:</p>
194+
<ul>
195+
<li>Contacting the crate author first when possible</li>
196+
<li>Reporting to the
197+
<a href='https://rustsec.org/contributing.html' target='_blank' rel='noopener noreferrer'>RustSec Advisory
198+
Database</a></li>
199+
<li>Reviewing our <LinkTo @route='policies.security' target='_blank'>security policy</LinkTo></li>
200+
</ul>
201+
</div>
202+
{{/if}}
203+
170204
<fieldset class='form-group' data-test-id='fieldset-detail'>
171205
{{#let (uniqueId) as |id|}}
172206
<label for={{id}} class='form-group-name'>Detail</label>
@@ -191,7 +225,11 @@ ${this.detail}
191225
<div class='buttons'>
192226
<button type='submit' class='report-button button button--small' data-test-id='report-button'>
193227
Report to
194-
<strong>[email protected]</strong>
228+
{{#if this.isMaliciousCodeReport}}
229+
230+
{{else}}
231+
<strong>[email protected]</strong>
232+
{{/if}}
195233
</button>
196234
</div>
197235
</form>

e2e/acceptance/support.spec.ts

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import { test, expect } from '@/e2e/helper';
22

33
test.describe('Acceptance | support page', { tag: '@acceptance' }, () => {
4+
test.beforeEach(async ({ page, msw }) => {
5+
let crate = msw.db.crate.create({ name: 'nanomsg' });
6+
msw.db.version.create({ crate, num: '0.6.0' });
7+
8+
// mock `window.open()`
9+
await page.addInitScript(() => {
10+
globalThis.open = (url, target, features) => {
11+
globalThis.openKwargs = { url, target, features };
12+
return { document: { write() {}, close() {} }, close() {} } as ReturnType<(typeof globalThis)['open']>;
13+
};
14+
});
15+
});
16+
417
test('shows an inquire list', async ({ page, percy, a11y }) => {
518
await page.goto('/support');
619
await expect(page).toHaveURL('/support');
@@ -32,17 +45,6 @@ test.describe('Acceptance | support page', { tag: '@acceptance' }, () => {
3245

3346
test.describe('reporting a crate from support page', () => {
3447
test.beforeEach(async ({ page, msw }) => {
35-
let crate = msw.db.crate.create({ name: 'nanomsg' });
36-
msw.db.version.create({ crate, num: '0.6.0' });
37-
38-
// mock `window.open()`
39-
await page.addInitScript(() => {
40-
globalThis.open = (url, target, features) => {
41-
globalThis.openKwargs = { url, target, features };
42-
return { document: { write() {}, close() {} }, close() {} } as ReturnType<(typeof globalThis)['open']>;
43-
};
44-
});
45-
4648
await page.goto('/support');
4749
await page.getByTestId('link-crate-violation').click();
4850
await expect(page).toHaveURL('/support?inquire=crate-violation');
@@ -130,7 +132,8 @@ test.describe('Acceptance | support page', { tag: '@acceptance' }, () => {
130132
- [x] it contains spam
131133
- [ ] it is name-squatting (reserving a crate name without content)
132134
- [ ] it is abusive or otherwise harmful
133-
- [ ] it contains a vulnerability (please try to contact the crate author first)
135+
- [ ] it contains malicious code
136+
- [ ] it contains a vulnerability
134137
- [ ] it is violating the usage policy in some other way (please specify below)
135138
136139
Additional details:
@@ -174,7 +177,8 @@ Additional details:
174177
- [x] it contains spam
175178
- [ ] it is name-squatting (reserving a crate name without content)
176179
- [ ] it is abusive or otherwise harmful
177-
- [ ] it contains a vulnerability (please try to contact the crate author first)
180+
- [ ] it contains malicious code
181+
- [ ] it contains a vulnerability
178182
- [x] it is violating the usage policy in some other way (please specify below)
179183
180184
Additional details:
@@ -193,17 +197,6 @@ test detail
193197

194198
test.describe('reporting a crate from crate page', () => {
195199
test.beforeEach(async ({ page, msw }) => {
196-
let crate = msw.db.crate.create({ name: 'nanomsg' });
197-
msw.db.version.create({ crate, num: '0.6.0' });
198-
199-
// mock `window.open()`
200-
await page.addInitScript(() => {
201-
globalThis.open = (url, target, features) => {
202-
globalThis.openKwargs = { url, target, features };
203-
return { document: { write() {}, close() {} }, close() {} } as ReturnType<(typeof globalThis)['open']>;
204-
};
205-
});
206-
207200
await page.goto('/crates/nanomsg');
208201
await page.getByTestId('link-crate-report').click();
209202
await expect(page).toHaveURL('/support?crate=nanomsg&inquire=crate-violation');
@@ -263,7 +256,8 @@ test detail
263256
- [x] it contains spam
264257
- [ ] it is name-squatting (reserving a crate name without content)
265258
- [ ] it is abusive or otherwise harmful
266-
- [ ] it contains a vulnerability (please try to contact the crate author first)
259+
- [ ] it contains malicious code
260+
- [ ] it contains a vulnerability
267261
- [ ] it is violating the usage policy in some other way (please specify below)
268262
269263
Additional details:
@@ -303,7 +297,8 @@ Additional details:
303297
- [x] it contains spam
304298
- [ ] it is name-squatting (reserving a crate name without content)
305299
- [ ] it is abusive or otherwise harmful
306-
- [ ] it contains a vulnerability (please try to contact the crate author first)
300+
- [ ] it contains malicious code
301+
- [ ] it contains a vulnerability
307302
- [x] it is violating the usage policy in some other way (please specify below)
308303
309304
Additional details:
@@ -319,4 +314,65 @@ test detail
319314
await page.waitForFunction(expect => globalThis.openKwargs.target === expect, '_self');
320315
});
321316
});
317+
318+
test('valid form with required detail', async ({ page }) => {
319+
await page.goto('/support');
320+
await page.getByTestId('link-crate-violation').click();
321+
await expect(page).toHaveURL('/support?inquire=crate-violation');
322+
323+
const crateInput = page.getByTestId('crate-input');
324+
await crateInput.fill('nanomsg');
325+
await expect(crateInput).toHaveValue('nanomsg');
326+
const checkbox = page.getByTestId('malicious-code-checkbox');
327+
await checkbox.check();
328+
await expect(checkbox).toBeChecked();
329+
const detailInput = page.getByTestId('detail-input');
330+
await detailInput.fill('test detail');
331+
await expect(detailInput).toHaveValue('test detail');
332+
333+
await page.waitForFunction(() => globalThis.openKwargs === undefined);
334+
const reportButton = page.getByTestId('report-button');
335+
await reportButton.click();
336+
337+
await expect(page.getByTestId('crate-invalid')).not.toBeVisible();
338+
await expect(page.getByTestId('reasons-invalid')).not.toBeVisible();
339+
await expect(page.getByTestId('detail-invalid')).not.toBeVisible();
340+
341+
let body = `I'm reporting the https://crates.io/crates/nanomsg crate because:
342+
343+
- [ ] it contains spam
344+
- [ ] it is name-squatting (reserving a crate name without content)
345+
- [ ] it is abusive or otherwise harmful
346+
- [x] it contains malicious code
347+
- [ ] it contains a vulnerability
348+
- [ ] it is violating the usage policy in some other way (please specify below)
349+
350+
Additional details:
351+
352+
test detail
353+
`;
354+
let subject = `[SECURITY] The "nanomsg" crate`;
355+
356+
let mailto = `mailto:${addresses}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
357+
// wait for `window.open()` to be called
358+
await page.waitForFunction(() => !!globalThis.openKwargs);
359+
await page.waitForFunction(expect => globalThis.openKwargs.url === expect, mailto);
360+
await page.waitForFunction(expect => globalThis.openKwargs.target === expect, '_self');
361+
});
362+
363+
test('shows help text for vulnerability reports', async ({ page }) => {
364+
await page.goto('/support');
365+
await page.getByTestId('link-crate-violation').click();
366+
await expect(page).toHaveURL('/support?inquire=crate-violation');
367+
368+
const crateInput = page.getByTestId('crate-input');
369+
await crateInput.fill('nanomsg');
370+
await expect(crateInput).toHaveValue('nanomsg');
371+
await expect(page.getByTestId('vulnerability-report')).not.toBeVisible();
372+
373+
const checkbox = page.getByTestId('vulnerability-checkbox');
374+
await checkbox.check();
375+
await expect(checkbox).toBeChecked();
376+
await expect(page.getByTestId('vulnerability-report')).toBeVisible();
377+
});
322378
});

0 commit comments

Comments
 (0)