diff --git a/application/defs/osib_defs.py b/application/defs/osib_defs.py index 89c204704..71e6c219e 100644 --- a/application/defs/osib_defs.py +++ b/application/defs/osib_defs.py @@ -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", ) } diff --git a/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.scss b/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.scss index e69de29bb..8e29bb2ce 100644 --- a/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.scss +++ b/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.scss @@ -0,0 +1,11 @@ +.myopencre-section { + margin-top: 2rem; +} + +.myopencre-upload { + margin-top: 1.5rem; +} + +.myopencre-disabled { + opacity: 0.7; +} diff --git a/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx b/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx index 68c1fb056..d433b617d 100644 --- a/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx +++ b/application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx @@ -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(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(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) { @@ -41,6 +46,67 @@ export const MyOpenCRE = () => { } }; + /* ------------------ FILE SELECTION ------------------ */ + + const onFileChange = (e: React.ChangeEvent) => { + 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 (
MyOpenCRE
@@ -51,15 +117,56 @@ export const MyOpenCRE = () => {

- 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.

+
+ +
+ +
+
Upload Mapping CSV
+ +

Upload your completed mapping spreadsheet to import your standard into OpenCRE.

+ + {!isUploadEnabled && ( + + CSV upload is disabled on hosted environments due to resource constraints. +
+ Please run OpenCRE locally to enable standard imports. +
+ )} + + {error && {error}} + + {success && ( + + Import successful +
    +
  • New CREs added: {success.new_cres?.length ?? 0}
  • +
  • Standards imported: {success.new_standards}
  • +
+
+ )} - + + +
); }; diff --git a/application/frontend/src/scaffolding/Header/Header.tsx b/application/frontend/src/scaffolding/Header/Header.tsx index 1b36dd721..f08b6d7dc 100644 --- a/application/frontend/src/scaffolding/Header/Header.tsx +++ b/application/frontend/src/scaffolding/Header/Header.tsx @@ -68,11 +68,11 @@ export const Header = () => { Explorer - - - + + MyOpenCRE +
@@ -190,16 +190,10 @@ export const Header = () => { onClick={closeMobileMenu} > Explorer - - - + + MyOpenCRE - +
diff --git a/application/frontend/src/scaffolding/Header/header.scss b/application/frontend/src/scaffolding/Header/header.scss index 9d43a50fe..50cf72531 100644 --- a/application/frontend/src/scaffolding/Header/header.scss +++ b/application/frontend/src/scaffolding/Header/header.scss @@ -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; -} diff --git a/application/tests/data/osib_example.yml b/application/tests/data/osib_example.yml index 89a6d18eb..d91a739e3 100644 --- a/application/tests/data/osib_example.yml +++ b/application/tests/data/osib_example.yml @@ -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® (OWASP)" + name: "Open Web Application Security Project® (OWASP)" children: "ZAP": attributes: diff --git a/application/tests/data/osib_example.yml.bak b/application/tests/data/osib_example.yml.bak index c6cf165fe..1dfb985c0 100644 --- a/application/tests/data/osib_example.yml.bak +++ b/application/tests/data/osib_example.yml.bak @@ -9,7 +9,7 @@ children: attributes: sources_i18n: en: - name: "Open Worldwide Application Security Project® (OWASP)" + name: "Open Web Application Security Project® (OWASP)" children: 1: alias: "asvs" diff --git a/application/tests/osib_defs_test.py b/application/tests/osib_defs_test.py index d74fd0c00..cae8f50a3 100644 --- a/application/tests/osib_defs_test.py +++ b/application/tests/osib_defs_test.py @@ -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", # ) # } @@ -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", # ) # }