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
2 changes: 1 addition & 1 deletion application/defs/osib_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def paths_to_osib(
attributes=Node_attributes(
sources_i18n={
Lang("en"): _Source(
name="Open Worldwide Application Security Project",
name="Open Web Application Security Project",
source="https://owasp.org",
)
}
Expand Down
11 changes: 11 additions & 0 deletions application/frontend/src/pages/MyOpenCRE/MyOpenCRE.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.myopencre-section {
margin-top: 2rem;
}

.myopencre-upload {
margin-top: 1.5rem;
}

.myopencre-disabled {
opacity: 0.7;
}
139 changes: 123 additions & 16 deletions application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import React from 'react';
import { Button, Container, Header } from 'semantic-ui-react';
import './MyOpenCRE.scss';

import React, { useState } from 'react';
import { Button, Container, Form, Header, Message } from 'semantic-ui-react';

import { useEnvironment } from '../../hooks';

export const MyOpenCRE = () => {
const { apiUrl } = useEnvironment();
// console.log('API URL:', apiUrl);

const downloadCreCsv = async () => {
try {
const baseUrl = apiUrl || window.location.origin;
// Upload enabled only for local/dev
const isUploadEnabled = apiUrl !== '/rest/v1';

const backendUrl = baseUrl.includes('localhost') ? 'http://127.0.0.1:5000' : baseUrl;
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<any | null>(null);

const response = await fetch(`${backendUrl}/cre_csv`, {
/* ------------------ CSV DOWNLOAD ------------------ */

const downloadCreCsv = async () => {
try {
const response = await fetch(`${apiUrl}/cre_csv`, {
method: 'GET',
headers: {
Accept: 'text/csv',
},
headers: { Accept: 'text/csv' },
});

if (!response.ok) {
Expand All @@ -41,6 +46,67 @@ export const MyOpenCRE = () => {
}
};

/* ------------------ FILE SELECTION ------------------ */

const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setError(null);
setSuccess(null);

if (!e.target.files || e.target.files.length === 0) return;

const file = e.target.files[0];

if (!file.name.toLowerCase().endsWith('.csv')) {
setError('Please upload a valid CSV file.');
e.target.value = '';
setSelectedFile(null);
return;
}

setSelectedFile(file);
};

/* ------------------ CSV UPLOAD ------------------ */

const uploadCsv = async () => {
if (!selectedFile) return;

setLoading(true);
setError(null);
setSuccess(null);

const formData = new FormData();
formData.append('cre_csv', selectedFile);

try {
const response = await fetch(`${apiUrl}/cre_csv_import`, {
method: 'POST',
body: formData,
});

if (response.status === 403) {
throw new Error(
'CSV import is disabled on hosted environments. Run OpenCRE locally with CRE_ALLOW_IMPORT=true.'
);
}

if (!response.ok) {
const text = await response.text();
throw new Error(text || 'CSV import failed');
}

const result = await response.json();
setSuccess(result);
setSelectedFile(null);
} catch (err: any) {
setError(err.message || 'Unexpected error during import');
} finally {
setLoading(false);
}
};

/* ------------------ UI ------------------ */

return (
<Container style={{ marginTop: '3rem' }}>
<Header as="h1">MyOpenCRE</Header>
Expand All @@ -51,15 +117,56 @@ export const MyOpenCRE = () => {
</p>

<p>
Start by downloading the mapping template below, fill it with your standard’s controls, and map them
to CRE IDs.
Start by downloading the CRE catalogue below, then map your standard’s controls or sections to CRE IDs
in the spreadsheet.
</p>

<div className="myopencre-section">
<Button primary onClick={downloadCreCsv}>
Download CRE Catalogue (CSV)
</Button>
</div>

<div className="myopencre-section myopencre-upload">
<Header as="h3">Upload Mapping CSV</Header>

<p>Upload your completed mapping spreadsheet to import your standard into OpenCRE.</p>

{!isUploadEnabled && (
<Message info className="myopencre-disabled">
CSV upload is disabled on hosted environments due to resource constraints.
<br />
Please run OpenCRE locally to enable standard imports.
</Message>
)}

{error && <Message negative>{error}</Message>}

{success && (
<Message positive>
<strong>Import successful</strong>
<ul>
<li>New CREs added: {success.new_cres?.length ?? 0}</li>
<li>Standards imported: {success.new_standards}</li>
</ul>
</Message>
)}

<Button primary onClick={downloadCreCsv}>
Download CRE Catalogue (CSV)
<Form>
<Form.Field>
<input type="file" accept=".csv" disabled={!isUploadEnabled || loading} onChange={onFileChange} />
</Form.Field>

</Button>
<Button
primary
loading={loading}
disabled={!isUploadEnabled || !selectedFile || loading}
onClick={uploadCsv}
>
Upload CSV
</Button>
</Form>
</div>
</Container>
);
};
18 changes: 6 additions & 12 deletions application/frontend/src/scaffolding/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export const Header = () => {

<NavLink to="/explorer" className="nav-link" activeClassName="nav-link--active">
Explorer
</NavLink>

<NavLink to="/myopencre" className="nav-link" activeClassName="nav-link--active">
</a>
<Link to="/myopencre" className="nav-link">
MyOpenCRE
</NavLink>

</div>

<div>
Expand Down Expand Up @@ -190,16 +190,10 @@ export const Header = () => {
onClick={closeMobileMenu}
>
Explorer
</NavLink>

<NavLink
to="/myopencre"
className="nav-link"
activeClassName="nav-link--active"
onClick={closeMobileMenu}
>
</a>
<a href="/myopencre" className="nav-link" onClick={MyOpenCRE}>
MyOpenCRE
</NavLink>
</a>
</div>

<div className="mobile-auth">
Expand Down
11 changes: 0 additions & 11 deletions application/frontend/src/scaffolding/Header/header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -322,14 +322,3 @@
display: block;
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
4 changes: 2 additions & 2 deletions application/tests/data/osib_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ schema: v0.90
date: 2021/11/04
children:
"OWASP":
aliases: ["Open Worldwide Application Security Project", "owasp"]
aliases: ["Open Web Application Security Project", "owasp"]
attributes:
sources_i18n:
en:
name: "Open Worldwide Application Security Project&reg; (OWASP)"
name: "Open Web Application Security Project&reg; (OWASP)"
children:
"ZAP":
attributes:
Expand Down
2 changes: 1 addition & 1 deletion application/tests/data/osib_example.yml.bak
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ children:
attributes:
sources_i18n:
en:
name: "Open Worldwide Application Security Project&reg; (OWASP)"
name: "Open Web Application Security Project&reg; (OWASP)"
children:
1:
alias: "asvs"
Expand Down
4 changes: 2 additions & 2 deletions application/tests/osib_defs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
# attributes=Node_attributes(
# sources_i18n={
# Lang("en"): _Source(
# name="Open Worldwide Application Security Project",
# name="Open Web Application Security Project",
# source="https://owasp.org",
# )
# }
Expand Down Expand Up @@ -326,7 +326,7 @@
# attributes=defs.Node_attributes(
# sources_i18n={
# defs.Lang("en"): defs._Source(
# name="Open Worldwide Application Security Project",
# name="Open Web Application Security Project",
# source="https://owasp.org",
# )
# }
Expand Down