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
16 changes: 16 additions & 0 deletions app/components/support/crate-report-form.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@
}
}

.vulnerability-report {
padding: var(--space-s) var(--space-s);
background-color: light-dark(white, #141413);
border: 1px solid var(--gray-border);
border-radius: var(--space-3xs);
width: 100%;

:first-child {
margin-top: 0;
}

:last-child {
margin-bottom: 0;
}
}

.buttons {
position: relative;
margin: var(--space-m) 0;
Expand Down
52 changes: 45 additions & 7 deletions app/components/support/crate-report-form.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Input, Textarea } from '@ember/component';
import { fn, uniqueId } from '@ember/helper';
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import { LinkTo } from '@ember/routing';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
Expand All @@ -24,8 +25,12 @@ const REASONS = [
description: 'it is abusive or otherwise harmful',
},
{
reason: 'security',
description: 'it contains a vulnerability (please try to contact the crate author first)',
reason: 'malicious-code',
description: 'it contains malicious code',
},
{
reason: 'vulnerability',
description: 'it contains a vulnerability',
},
{
reason: 'other',
Expand Down Expand Up @@ -76,6 +81,14 @@ export default class CrateReportForm extends Component {
this.reasonsInvalid = false;
}

get isMaliciousCodeReport() {
return this.selectedReasons.includes('malicious-code');
}

get isVulnerabilityReport() {
return this.selectedReasons.includes('vulnerability');
}

@action
submit() {
if (!this.validate()) {
Expand All @@ -87,7 +100,7 @@ export default class CrateReportForm extends Component {
}

composeMail() {
let crate = this.crate;
let { crate, isMaliciousCodeReport } = this;
let reasons = this.reasons
.map(({ reason, description }) => {
let selected = this.isReasonSelected(reason);
Expand All @@ -103,9 +116,16 @@ Additional details:
${this.detail}
`;
let subject = `The "${crate}" crate`;
let address = '[email protected]';
let mailto = `mailto:${address}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
return mailto;
if (isMaliciousCodeReport) {
subject = `[SECURITY] ${subject}`;
}

let addresses = '[email protected]';
if (isMaliciousCodeReport) {
addresses += ',[email protected]';
}

return `mailto:${addresses}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
}

<template>
Expand Down Expand Up @@ -167,6 +187,20 @@ ${this.detail}
{{/if}}
</fieldset>

{{#if this.isVulnerabilityReport}}
<div class='vulnerability-report form-group' data-test-id='vulnerability-report'>
<h3>🔍 Vulnerability Report</h3>
<p>For crate vulnerabilities, please consider:</p>
<ul>
<li>Contacting the crate author first when possible</li>
<li>Reporting to the
<a href='https://rustsec.org/contributing.html' target='_blank' rel='noopener noreferrer'>RustSec Advisory
Database</a></li>
<li>Reviewing our <LinkTo @route='policies.security' target='_blank'>security policy</LinkTo></li>
</ul>
</div>
{{/if}}

<fieldset class='form-group' data-test-id='fieldset-detail'>
{{#let (uniqueId) as |id|}}
<label for={{id}} class='form-group-name'>Detail</label>
Expand All @@ -191,7 +225,11 @@ ${this.detail}
<div class='buttons'>
<button type='submit' class='report-button button button--small' data-test-id='report-button'>
Report to
<strong>[email protected]</strong>
{{#if this.isMaliciousCodeReport}}
<strong>[email protected] & [email protected]</strong>
{{else}}
<strong>[email protected]</strong>
{{/if}}
</button>
</div>
</form>
Expand Down
108 changes: 82 additions & 26 deletions e2e/acceptance/support.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { test, expect } from '@/e2e/helper';

test.describe('Acceptance | support page', { tag: '@acceptance' }, () => {
test.beforeEach(async ({ page, msw }) => {
let crate = msw.db.crate.create({ name: 'nanomsg' });
msw.db.version.create({ crate, num: '0.6.0' });

// mock `window.open()`
await page.addInitScript(() => {
globalThis.open = (url, target, features) => {
globalThis.openKwargs = { url, target, features };
return { document: { write() {}, close() {} }, close() {} } as ReturnType<(typeof globalThis)['open']>;
};
});
});

test('shows an inquire list', async ({ page, percy, a11y }) => {
await page.goto('/support');
await expect(page).toHaveURL('/support');
Expand Down Expand Up @@ -32,17 +45,6 @@ test.describe('Acceptance | support page', { tag: '@acceptance' }, () => {

test.describe('reporting a crate from support page', () => {
test.beforeEach(async ({ page, msw }) => {
let crate = msw.db.crate.create({ name: 'nanomsg' });
msw.db.version.create({ crate, num: '0.6.0' });

// mock `window.open()`
await page.addInitScript(() => {
globalThis.open = (url, target, features) => {
globalThis.openKwargs = { url, target, features };
return { document: { write() {}, close() {} }, close() {} } as ReturnType<(typeof globalThis)['open']>;
};
});

await page.goto('/support');
await page.getByTestId('link-crate-violation').click();
await expect(page).toHaveURL('/support?inquire=crate-violation');
Expand Down Expand Up @@ -130,7 +132,8 @@ test.describe('Acceptance | support page', { tag: '@acceptance' }, () => {
- [x] it contains spam
- [ ] it is name-squatting (reserving a crate name without content)
- [ ] it is abusive or otherwise harmful
- [ ] it contains a vulnerability (please try to contact the crate author first)
- [ ] it contains malicious code
- [ ] it contains a vulnerability
- [ ] it is violating the usage policy in some other way (please specify below)

Additional details:
Expand Down Expand Up @@ -174,7 +177,8 @@ Additional details:
- [x] it contains spam
- [ ] it is name-squatting (reserving a crate name without content)
- [ ] it is abusive or otherwise harmful
- [ ] it contains a vulnerability (please try to contact the crate author first)
- [ ] it contains malicious code
- [ ] it contains a vulnerability
- [x] it is violating the usage policy in some other way (please specify below)

Additional details:
Expand All @@ -193,17 +197,6 @@ test detail

test.describe('reporting a crate from crate page', () => {
test.beforeEach(async ({ page, msw }) => {
let crate = msw.db.crate.create({ name: 'nanomsg' });
msw.db.version.create({ crate, num: '0.6.0' });

// mock `window.open()`
await page.addInitScript(() => {
globalThis.open = (url, target, features) => {
globalThis.openKwargs = { url, target, features };
return { document: { write() {}, close() {} }, close() {} } as ReturnType<(typeof globalThis)['open']>;
};
});

await page.goto('/crates/nanomsg');
await page.getByTestId('link-crate-report').click();
await expect(page).toHaveURL('/support?crate=nanomsg&inquire=crate-violation');
Expand Down Expand Up @@ -263,7 +256,8 @@ test detail
- [x] it contains spam
- [ ] it is name-squatting (reserving a crate name without content)
- [ ] it is abusive or otherwise harmful
- [ ] it contains a vulnerability (please try to contact the crate author first)
- [ ] it contains malicious code
- [ ] it contains a vulnerability
- [ ] it is violating the usage policy in some other way (please specify below)

Additional details:
Expand Down Expand Up @@ -303,7 +297,8 @@ Additional details:
- [x] it contains spam
- [ ] it is name-squatting (reserving a crate name without content)
- [ ] it is abusive or otherwise harmful
- [ ] it contains a vulnerability (please try to contact the crate author first)
- [ ] it contains malicious code
- [ ] it contains a vulnerability
- [x] it is violating the usage policy in some other way (please specify below)

Additional details:
Expand All @@ -319,4 +314,65 @@ test detail
await page.waitForFunction(expect => globalThis.openKwargs.target === expect, '_self');
});
});

test('valid form with required detail', async ({ page }) => {
await page.goto('/support');
await page.getByTestId('link-crate-violation').click();
await expect(page).toHaveURL('/support?inquire=crate-violation');

const crateInput = page.getByTestId('crate-input');
await crateInput.fill('nanomsg');
await expect(crateInput).toHaveValue('nanomsg');
const checkbox = page.getByTestId('malicious-code-checkbox');
await checkbox.check();
await expect(checkbox).toBeChecked();
const detailInput = page.getByTestId('detail-input');
await detailInput.fill('test detail');
await expect(detailInput).toHaveValue('test detail');

await page.waitForFunction(() => globalThis.openKwargs === undefined);
const reportButton = page.getByTestId('report-button');
await reportButton.click();

await expect(page.getByTestId('crate-invalid')).not.toBeVisible();
await expect(page.getByTestId('reasons-invalid')).not.toBeVisible();
await expect(page.getByTestId('detail-invalid')).not.toBeVisible();

let body = `I'm reporting the https://crates.io/crates/nanomsg crate because:

- [ ] it contains spam
- [ ] it is name-squatting (reserving a crate name without content)
- [ ] it is abusive or otherwise harmful
- [x] it contains malicious code
- [ ] it contains a vulnerability
- [ ] it is violating the usage policy in some other way (please specify below)

Additional details:

test detail
`;
let subject = `[SECURITY] The "nanomsg" crate`;
let addresses = '[email protected],[email protected]';
let mailto = `mailto:${addresses}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
// wait for `window.open()` to be called
await page.waitForFunction(() => !!globalThis.openKwargs);
await page.waitForFunction(expect => globalThis.openKwargs.url === expect, mailto);
await page.waitForFunction(expect => globalThis.openKwargs.target === expect, '_self');
});

test('shows help text for vulnerability reports', async ({ page }) => {
await page.goto('/support');
await page.getByTestId('link-crate-violation').click();
await expect(page).toHaveURL('/support?inquire=crate-violation');

const crateInput = page.getByTestId('crate-input');
await crateInput.fill('nanomsg');
await expect(crateInput).toHaveValue('nanomsg');
await expect(page.getByTestId('vulnerability-report')).not.toBeVisible();

const checkbox = page.getByTestId('vulnerability-checkbox');
await checkbox.check();
await expect(checkbox).toBeChecked();
await expect(page.getByTestId('vulnerability-report')).toBeVisible();
});
});
Loading
Loading