From 158c69adf695abdcfc0450ae80ad401f9fbf21cd Mon Sep 17 00:00:00 2001 From: Spyros Date: Mon, 2 Oct 2023 15:08:01 +0100 Subject: [PATCH 1/3] fix smartlink functionality in case we don't know about a standard (#409) * fix smartlink functionality in case we don't know about a standard * lint and tests fix * lint2 --- .../src/pages/Search/components/BodyText.tsx | 26 +++++++++++-------- .../frontend/src/pages/chatbot/chatbot.tsx | 24 +++++++---------- application/tests/web_main_test.py | 8 +++--- application/web/web_main.py | 10 +++---- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/application/frontend/src/pages/Search/components/BodyText.tsx b/application/frontend/src/pages/Search/components/BodyText.tsx index 124e73e50..7e089c105 100644 --- a/application/frontend/src/pages/Search/components/BodyText.tsx +++ b/application/frontend/src/pages/Search/components/BodyText.tsx @@ -25,10 +25,10 @@ export const SearchBody = () => {

- Use OpenCRE Chat to ask any security question (Google account required to maximize queries per minute). In collaboration - with Google, we injected all the standards in OpenCRE into an AI model to create the world's first - security-specialized chatbot. This ensures you get a more reliable answer, and also a reference to a - reputable source. + Use OpenCRE Chat to ask any security question (Google account required to + maximize queries per minute). In collaboration with Google, we injected all the standards in OpenCRE + into an AI model to create the world's first security-specialized chatbot. This ensures you get a more + reliable answer, and also a reference to a reputable source.

HOW?

@@ -48,10 +48,10 @@ export const SearchBody = () => {

WHO?

- OpenCRE is the brainchild of software security professionals Spyros Gasteratos and Rob van - der Veer, who joined forces to tackle the complexities and segmentation in current security standards - and guidelines. They collaborated closely with many initiatives, including SKF, OpenSSF and the Owasp - Top 10 project. OpenCRE is an open-source platform overseen by the OWASP foundation through the + OpenCRE is the brainchild of software security professionals Spyros Gasteratos and Rob van der Veer, + who joined forces to tackle the complexities and segmentation in current security standards and + guidelines. They collaborated closely with many initiatives, including SKF, OpenSSF and the Owasp Top + 10 project. OpenCRE is an open-source platform overseen by the OWASP foundation through the OWASP Integration standard project . The goal is to foster better coordination among security initiatives.

@@ -61,8 +61,8 @@ export const SearchBody = () => { Cloud Control Matrix, ISO27001, ISO27002, and NIST SSDF).

- Contact us via (rob.vanderveer [at] owasp.org) for any questions, remarks or to join the movement. Currently, a stakeholder group is - being formed. + Contact us via (rob.vanderveer [at] owasp.org) for any questions, remarks or to join the movement. + Currently, a stakeholder group is being formed.

For more details, see this @@ -72,7 +72,11 @@ export const SearchBody = () => { OpenCRE explanation document{' '} , follow our - LinkedIn page , click the diagram below, or browse our catalogue textually or graphically. + LinkedIn page , click the diagram below, or{' '} + + browse our catalogue textually or graphically + + .

diff --git a/application/frontend/src/pages/chatbot/chatbot.tsx b/application/frontend/src/pages/chatbot/chatbot.tsx index 228258eb5..47b66dca2 100644 --- a/application/frontend/src/pages/chatbot/chatbot.tsx +++ b/application/frontend/src/pages/chatbot/chatbot.tsx @@ -1,7 +1,7 @@ import './chatbot.scss'; -import DOMPurify,{sanitize} from 'dompurify'; -import {marked} from 'marked'; +import DOMPurify, { sanitize } from 'dompurify'; +import { marked } from 'marked'; import React, { createElement, useEffect, useState } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; @@ -13,7 +13,6 @@ import { useEnvironment } from '../../hooks'; import { Document } from '../../types'; export const Chatbot = () => { - type chatMessage = { timestamp: string; role: string; @@ -64,12 +63,12 @@ export const Chatbot = () => { for (const txt of responses) { if (i % 2 == 0) { res.push( -

- ) + ); } else { res.push({txt}); } @@ -171,9 +170,7 @@ export const Chatbot = () => { {m.role} - - {m.timestamp} - + {m.timestamp} {processResponse(m.message)} {m.data @@ -187,9 +184,8 @@ export const Chatbot = () => { Note: The content of OpenCRE could not be used to answer your question, as no matching standard was found. The answer therefore has no reference and - needs to be regarded as less reliable. Try rephrasing your question, - use similar topics, or{' '} - OpenCRE search. + needs to be regarded as less reliable. Try rephrasing your question, use + similar topics, or OpenCRE search. )} diff --git a/application/tests/web_main_test.py b/application/tests/web_main_test.py index 30f870c6f..f35492168 100644 --- a/application/tests/web_main_test.py +++ b/application/tests/web_main_test.py @@ -557,7 +557,7 @@ def test_smartlink(self) -> None: self.assertEqual(location, "/node/standard/ASVS/section/v0.1.2") self.assertEqual(302, response.status_code) - # negative test, this cwe does not exist, therefore there is nowhere to redirect to + # negative test, this cwe does not exist, therefore we redirect to Mitre! response = client.get( "/smartlink/standard/CWE/999", headers={"Content-Type": "application/json"}, @@ -566,5 +566,7 @@ def test_smartlink(self) -> None: for head in response.headers: if head[0] == "Location": location = head[1] - self.assertEqual(location, "") - self.assertEqual(404, response.status_code) + self.assertEqual( + location, "https://cwe.mitre.org/data/definitions/999.html" + ) + self.assertEqual(302, response.status_code) diff --git a/application/web/web_main.py b/application/web/web_main.py index 0cfccf9bf..587f88e43 100644 --- a/application/web/web_main.py +++ b/application/web/web_main.py @@ -290,13 +290,13 @@ def smartlink( name: str, ntype: str = defs.Credoctypes.Standard.value, section: str = "" ) -> Any: """if node is found, show node, else redirect""" + # ATTENTION: DO NOT MESS WITH THIS FUNCTIONALITY WITHOUT A TICKET AND CORE CONTRIBUTORS APPROVAL! + # CRITICAL FUNCTIONALITY DEPENDS ON THIS! database = db.Node_collection() opt_version = request.args.get("version") # match ntype to the credoctypes case-insensitive - typ = [t for t in defs.Credoctypes if t.value.lower() == ntype.lower()] - doctype = None - if typ: - doctype = typ[0] + typ = [t.value for t in defs.Credoctypes if t.value.lower() == ntype.lower()] + doctype = None if not typ else typ[0] page = 1 items_per_page = 1 @@ -327,7 +327,7 @@ def smartlink( if found_section_id: return redirect(f"/node/{ntype}/{name}/sectionid/{section}") return redirect(f"/node/{ntype}/{name}/section/{section}") - elif ntype == defs.Credoctypes.Standard.value and redirectors.redirect( + elif doctype == defs.Credoctypes.Standard.value and redirectors.redirect( name, section ): logger.info( From 257ea3f36b764b20a79c356c8e4ac7cfe5240015 Mon Sep 17 00:00:00 2001 From: Spyros Date: Mon, 2 Oct 2023 17:27:46 +0100 Subject: [PATCH 2/3] Smartlink fix (#410) * fix smartlink functionality in case we don't know about a standard * lint and tests fix * lint2 * add e2e --- Makefile | 1 + application/frontend/src/test/basic-e2etest.ts | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 0d819b766..1717d2132 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ e2e: yarn test:e2e killall yarn killall flask + test: [ -d "./venv" ] && . ./venv/bin/activate export FLASK_APP=$(CURDIR)/cre.py diff --git a/application/frontend/src/test/basic-e2etest.ts b/application/frontend/src/test/basic-e2etest.ts index 3d4c6a521..99e324cf1 100644 --- a/application/frontend/src/test/basic-e2etest.ts +++ b/application/frontend/src/test/basic-e2etest.ts @@ -166,5 +166,13 @@ describe('App.js', () => { ); expect(clearFilters).toContain('Clear Filters'); }); + + it('can smartlink', async () => { + const response = await page.goto('http://127.0.0.1:5000/smartlink/standard/CWE/1002'); + expect(response.url()).toBe('http://127.0.0.1:5000/node/standard/CWE/sectionid/1002'); + const redirectResponse = await page.goto('http://127.0.0.1:5000/smartlink/standard/CWE/404'); + expect(redirectResponse.url()).toBe('https://cwe.mitre.org/data/definitions/404.html'); + }); + afterAll(async () => await browser.close()); }); From b4d4882a98e5a7ffac2930303d30d42d0a2b0103 Mon Sep 17 00:00:00 2001 From: Spyros Date: Fri, 6 Oct 2023 12:54:44 +0100 Subject: [PATCH 3/3] migrate to axios and fix error messages (#421) * migrate to axios and fix error messages * Update application/frontend/src/pages/Deeplink/Deeplink.tsx Co-authored-by: John Harvey <10814889+john681611@users.noreply.github.com> Signed-off-by: Spyros --------- Signed-off-by: Spyros Co-authored-by: John Harvey <10814889+john681611@users.noreply.github.com> --- .../LoadingAndErrorIndicator.tsx | 2 + .../pages/BrowseRootCres/browseRootCres.tsx | 42 +++++++++---------- .../CommonRequirementEnumeration.tsx | 41 ++++++++++-------- .../frontend/src/pages/Deeplink/Deeplink.tsx | 35 +++++++++------- .../frontend/src/pages/Graph/Graph.tsx | 37 +++++++++------- .../frontend/src/pages/Search/SearchName.tsx | 10 +++-- .../src/pages/Search/components/SearchBar.tsx | 2 +- .../frontend/src/pages/Standard/Standard.tsx | 40 +++++++++++------- .../src/pages/Standard/StandardSection.tsx | 42 +++++++++---------- application/frontend/src/types.ts | 4 ++ application/web/web_main.py | 14 ++++--- 11 files changed, 152 insertions(+), 117 deletions(-) diff --git a/application/frontend/src/components/LoadingAndErrorIndicator/LoadingAndErrorIndicator.tsx b/application/frontend/src/components/LoadingAndErrorIndicator/LoadingAndErrorIndicator.tsx index 0e2303770..c59c1ac1a 100644 --- a/application/frontend/src/components/LoadingAndErrorIndicator/LoadingAndErrorIndicator.tsx +++ b/application/frontend/src/components/LoadingAndErrorIndicator/LoadingAndErrorIndicator.tsx @@ -10,6 +10,8 @@ export const LoadingAndErrorIndicator: FunctionComponent { + console.log(loading); + console.log(error); return ( <> {loading && } diff --git a/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx b/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx index c559e07c4..1466282f6 100644 --- a/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx +++ b/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx @@ -1,8 +1,7 @@ import './browseRootCres.scss'; +import axios from 'axios'; import React, { useContext, useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; -import { useParams } from 'react-router-dom'; import { DocumentNode } from '../../components/DocumentNode'; import { ClearFilterButton, FilterButton } from '../../components/FilterButton/FilterButton'; @@ -17,30 +16,29 @@ export const BrowseRootCres = () => { const { apiUrl } = useEnvironment(); const [loading, setLoading] = useState(false); const [display, setDisplay] = useState(); - const { error, data, refetch } = useQuery<{ data: Document }, string>( - 'cre', - () => - fetch(`${apiUrl}/root_cres`) - .then((res) => res.json()) - .then((resjson) => { - setDisplay(resjson.data); - return resjson; - }), - { - retry: false, - enabled: false, - onSettled: () => { - setLoading(false); - }, - } - ); + const [error, setError] = useState(null); useEffect(() => { - window.scrollTo(0, 0); setLoading(true); - refetch(); - }, []); + window.scrollTo(0, 0); + axios + .get(`${apiUrl}/root_cres`) + .then(function (response) { + setError(null); + setDisplay(response?.data?.data); + }) + .catch(function (axiosError) { + if (axiosError.response.status === 404) { + setError('Standard does not exist in the DB, please check your search parameters'); + } else { + setError(axiosError.response); + } + }) + .finally(() => { + setLoading(false); + }); + }, []); return (

Root CREs:

diff --git a/application/frontend/src/pages/CommonRequirementEnumeration/CommonRequirementEnumeration.tsx b/application/frontend/src/pages/CommonRequirementEnumeration/CommonRequirementEnumeration.tsx index 77cde05a2..4bd962daa 100644 --- a/application/frontend/src/pages/CommonRequirementEnumeration/CommonRequirementEnumeration.tsx +++ b/application/frontend/src/pages/CommonRequirementEnumeration/CommonRequirementEnumeration.tsx @@ -1,7 +1,7 @@ import './commonRequirementEnumeration.scss'; -import React, { useContext, useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; +import axios from 'axios'; +import React, { useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import { DocumentNode } from '../../components/DocumentNode'; @@ -17,27 +17,32 @@ export const CommonRequirementEnumeration = () => { const { id } = useParams(); const { apiUrl } = useEnvironment(); const [loading, setLoading] = useState(false); - const globalState = useContext(filterContext); - - const { error, data, refetch } = useQuery<{ data: Document }, string>( - 'cre', - () => fetch(`${apiUrl}/id/${id}`).then((res) => res.json()), - { - retry: false, - enabled: false, - onSettled: () => { - setLoading(false); - }, - } - ); + const [error, setError] = useState(null); + const [data, setData] = useState(); useEffect(() => { - window.scrollTo(0, 0); setLoading(true); - refetch(); + window.scrollTo(0, 0); + + axios + .get(`${apiUrl}/id/${id}`) + .then(function (response) { + setError(null); + setData(response?.data?.data); + }) + .catch(function (axiosError) { + if (axiosError.response.status === 404) { + setError('CRE does not exist in the DB, please check your search parameters'); + } else { + setError(axiosError.response); + } + }) + .finally(() => { + setLoading(false); + }); }, [id]); - const cre = data?.data; + const cre = data; let filteredCRE; if (cre != undefined) { filteredCRE = applyFilters(JSON.parse(JSON.stringify(cre))); // dirty deepcopy diff --git a/application/frontend/src/pages/Deeplink/Deeplink.tsx b/application/frontend/src/pages/Deeplink/Deeplink.tsx index 1f8a298df..b0d65da7f 100644 --- a/application/frontend/src/pages/Deeplink/Deeplink.tsx +++ b/application/frontend/src/pages/Deeplink/Deeplink.tsx @@ -1,5 +1,5 @@ +import axios from 'axios'; import React, { useEffect, useState } from 'react'; -import { useQuery } from 'react-query'; import { useLocation, useParams } from 'react-router-dom'; import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator'; @@ -10,6 +10,8 @@ export const Deeplink = () => { let { type, nodeName, section, subsection, tooltype, sectionID } = useParams(); const { apiUrl } = useEnvironment(); const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [data, setData] = useState(); const search = useLocation().search; section = section ? section : new URLSearchParams(search).get('section'); subsection = subsection ? subsection : new URLSearchParams(search).get('subsection'); @@ -27,25 +29,28 @@ export const Deeplink = () => { (tooltype != null ? `tooltype=${tooltype}&` : '') + (sectionID != null ? `sectionID=${sectionID}&` : ''); - const { error, data, refetch } = useQuery<{ standards: Document[] }, string>( - 'deeplink', - () => fetch(url).then((res) => res.json()), - { - retry: false, - enabled: false, - onSettled: () => { - setLoading(false); - }, - } - ); useEffect(() => { window.scrollTo(0, 0); setLoading(true); - refetch(); + axios + .get(url) + .then(function (response) { + setError(null); + setData(response.data?.standard); + }) + .catch(function (axiosError) { + if (axiosError.response.status === 404) { + setError('Standard does not exist, please check your search parameters'); + } else { + setError(axiosError.response); + } + }) + .finally(() => { + setLoading(false); + }); }, [type, nodeName]); - // const { error, data, } = useQuery<{ standards: Document[]; }, string>('deeplink', () => fetch(url).then((res) => res.json()), {}); - const documents = data?.standards || []; + const documents = data || []; return ( <>
diff --git a/application/frontend/src/pages/Graph/Graph.tsx b/application/frontend/src/pages/Graph/Graph.tsx index 1db5ceff4..7824fbd05 100644 --- a/application/frontend/src/pages/Graph/Graph.tsx +++ b/application/frontend/src/pages/Graph/Graph.tsx @@ -1,3 +1,4 @@ +import axios from 'axios'; import Elk, { ElkEdge, ElkNode, ElkPort, ElkPrimitiveEdge } from 'elkjs'; import React, { useEffect, useState } from 'react'; import ReactFlow, { @@ -14,7 +15,6 @@ import ReactFlow, { isNode, removeElements, } from 'react-flow-renderer'; -import { useQuery } from 'react-query'; import { useParams } from 'react-router-dom'; import { FlowNode } from 'typescript'; @@ -94,22 +94,29 @@ export const Graph = () => { const { id } = useParams(); const { apiUrl } = useEnvironment(); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [data, setData] = useState(); - const { error, data, refetch } = useQuery<{ data: Document }, string>( - 'cre', - () => fetch(`${apiUrl}/id/${id}`).then((res) => res.json()), - { - retry: false, - enabled: false, - onSettled: () => { - setLoading(false); - }, - } - ); useEffect(() => { - window.scrollTo(0, 0); setLoading(true); - refetch(); + window.scrollTo(0, 0); + + axios + .get(`${apiUrl}/id/${id}`) + .then(function (response) { + setError(null); + setData(response?.data?.data); + }) + .catch(function (axiosError) { + if (axiosError.response.status === 404) { + setError('CRE does not exist in the DB, please check your search parameters'); + } else { + setError(axiosError.response); + } + }) + .finally(() => { + setLoading(false); + }); }, [id]); const [layout, setLayout] = useState<(Node | Edge)[]>(); @@ -119,7 +126,7 @@ export const Graph = () => { if (data) { console.log('flow running:', id); - let cre = data.data; + let cre = data; let graph = documentToReactFlowNode(cre); const els = await createGraphLayoutElk(graph.nodes, graph.edges); setLayout(els); diff --git a/application/frontend/src/pages/Search/SearchName.tsx b/application/frontend/src/pages/Search/SearchName.tsx index c3f5ef078..5699888b6 100644 --- a/application/frontend/src/pages/Search/SearchName.tsx +++ b/application/frontend/src/pages/Search/SearchName.tsx @@ -16,7 +16,7 @@ export const SearchName = () => { const { apiUrl } = useEnvironment(); const [loading, setLoading] = useState(false); const [documents, setDocuments] = useState([]); - const [error, setError] = useState(null); + const [error, setError] = useState(null); useEffect(() => { setLoading(true); @@ -27,9 +27,13 @@ export const SearchName = () => { setDocuments(response.data); }) .catch(function (axiosError) { - // TODO: backend errors if no matches, shoudl return + // TODO: backend errors if no matches, should return // proper error instead. - setError(axiosError); + if (axiosError.response.status === 404) { + setError('No results match your search term'); + } else { + setError(axiosError.response); + } }) .finally(() => { setLoading(false); diff --git a/application/frontend/src/pages/Search/components/SearchBar.tsx b/application/frontend/src/pages/Search/components/SearchBar.tsx index 42020f7c0..2989ed3be 100644 --- a/application/frontend/src/pages/Search/components/SearchBar.tsx +++ b/application/frontend/src/pages/Search/components/SearchBar.tsx @@ -45,7 +45,7 @@ export const SearchBar = () => { }); }} label={ - diff --git a/application/frontend/src/pages/Standard/Standard.tsx b/application/frontend/src/pages/Standard/Standard.tsx index 53bb7b316..7ccc1e008 100644 --- a/application/frontend/src/pages/Standard/Standard.tsx +++ b/application/frontend/src/pages/Standard/Standard.tsx @@ -1,14 +1,14 @@ import './standard.scss'; +import axios from 'axios'; import React, { useEffect, useState } from 'react'; -import { useQuery } from 'react-query'; import { useLocation, useParams } from 'react-router-dom'; import { Pagination } from 'semantic-ui-react'; import { DocumentNode } from '../../components/DocumentNode'; import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator'; import { useEnvironment } from '../../hooks'; -import { Document } from '../../types'; +import { Document, PaginatedResponse } from '../../types'; import { getDocumentDisplayName } from '../../utils/document'; export const Standard = () => { @@ -16,25 +16,33 @@ export const Standard = () => { const { apiUrl } = useEnvironment(); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); + const [err, setErr] = useState(null); + const [data, setData] = useState(); + if (!type) { type = 'standard'; } - const { error, data, refetch } = useQuery< - { standards: Document[]; total_pages: number; page: number }, - string - >('standard', () => fetch(`${apiUrl}/${type}/${id}?page=${page}`).then((res) => res.json()), { - retry: false, - enabled: false, - onSettled: () => { - setLoading(false); - }, - }); useEffect(() => { window.scrollTo(0, 0); setLoading(true); - refetch(); - }, [page, id]); + axios + .get(`${apiUrl}/${type}/${id}?page=${page}`) + .then(function (response) { + setErr(null); + setData(response.data); + }) + .catch(function (axiosError) { + if (axiosError.response.status === 404) { + setErr('Standard does not exist, please check your search parameters'); + } else { + setErr(axiosError.response); + } + }) + .finally(() => { + setLoading(false); + }); + }, [id, type, page]); const documents = data?.standards || []; @@ -42,9 +50,9 @@ export const Standard = () => { <>

{id}

- + {!loading && - !error && + !err && documents .sort((a, b) => getDocumentDisplayName(a).localeCompare(getDocumentDisplayName(b))) .map((standard, i) => ( diff --git a/application/frontend/src/pages/Standard/StandardSection.tsx b/application/frontend/src/pages/Standard/StandardSection.tsx index 303766a72..76c839458 100644 --- a/application/frontend/src/pages/Standard/StandardSection.tsx +++ b/application/frontend/src/pages/Standard/StandardSection.tsx @@ -1,7 +1,7 @@ import './standard.scss'; +import axios from 'axios'; import React, { useEffect, useMemo, useState } from 'react'; -import { useQuery } from 'react-query'; import { useParams } from 'react-router-dom'; import { Pagination } from 'semantic-ui-react'; @@ -9,7 +9,7 @@ import { DocumentNode } from '../../components/DocumentNode'; import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator'; import { DOCUMENT_TYPES, DOCUMENT_TYPE_NAMES, TOOL } from '../../const'; import { useEnvironment } from '../../hooks'; -import { Document } from '../../types'; +import { Document, PaginatedResponse } from '../../types'; import { getDocumentDisplayName, groupLinksByType } from '../../utils'; import { getDocumentTypeText } from '../../utils/document'; @@ -18,6 +18,8 @@ export const StandardSection = () => { const { apiUrl } = useEnvironment(); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [data, setData] = useState(); const getSectionParameter = (): string => { return section ? `§ion=${encodeURIComponent(section)}` : ''; @@ -25,29 +27,27 @@ export const StandardSection = () => { const getSectionIDParameter = (): string => { return sectionID ? `§ionID=${encodeURIComponent(sectionID)}` : ''; }; - const { error, data, refetch } = useQuery< - { standards: Document[]; total_pages: number; page: number }, - string - >( - 'standard section', - () => - fetch(`${apiUrl}/standard/${id}?page=${page}${getSectionParameter()}${getSectionIDParameter()}`).then( - (res) => res.json() - ), - { - retry: false, - enabled: false, - onSettled: () => { - setLoading(false); - }, - } - ); useEffect(() => { window.scrollTo(0, 0); setLoading(true); - refetch(); - }, [page, id]); + axios + .get(`${apiUrl}/standard/${id}?page=${page}${getSectionParameter()}${getSectionIDParameter()}`) + .then(function (response) { + setError(null); + setData(response.data); + }) + .catch(function (axiosError) { + if (axiosError.response.status === 404) { + setError('Standard does not exist in the DB, please check your search parameters'); + } else { + setError(axiosError.response); + } + }) + .finally(() => { + setLoading(false); + }); + }, [id, section, sectionID, page]); const documents = data?.standards || []; const document = documents[0]; diff --git a/application/frontend/src/types.ts b/application/frontend/src/types.ts index c8b7cec72..5622cc405 100644 --- a/application/frontend/src/types.ts +++ b/application/frontend/src/types.ts @@ -19,3 +19,7 @@ export interface LinkedDocument { document: Document; ltype: string; } +export interface PaginatedResponse { + standards: Document[]; + total_pages: number; +} diff --git a/application/web/web_main.py b/application/web/web_main.py index 587f88e43..3ccd1f051 100644 --- a/application/web/web_main.py +++ b/application/web/web_main.py @@ -94,7 +94,7 @@ def find_cre(creid: str = None, crename: str = None) -> Any: # refer result = {"data": json.loads(oscal_utils.document_to_oscal(cre))} return jsonify(result) - abort(404) + abort(404, "CRE does not exist") @app.route("/rest/v1//", methods=["GET"]) @@ -171,7 +171,7 @@ def find_node_by_name(name: str, ntype: str = defs.Credoctypes.Standard.value) - return jsonify(result) else: - abort(404) + abort(404, "Node does not exist") # TODO: (spyros) paginate @@ -197,7 +197,7 @@ def find_document_by_tag() -> Any: return jsonify(result) logger.info("tags aborting 404") - abort(404) + abort(404, "Tag does not exist") @app.route("/rest/v1/gap_analysis", methods=["GET"]) @@ -239,7 +239,7 @@ def text_search() -> Any: res = [doc.todict() for doc in documents] return jsonify(res) else: - abort(404) + abort(404, "No object matches the given search terms") @app.route("/rest/v1/root_cres", methods=["GET"]) @@ -266,11 +266,13 @@ def find_root_cres() -> Any: return jsonify(json.loads(oscal_utils.list_to_oscal(documents))) return jsonify(result) - abort(404) + abort(404, "No root CREs") @app.errorhandler(404) def page_not_found(e) -> Any: + from pprint import pprint + return "Resource Not found", 404 @@ -336,7 +338,7 @@ def smartlink( return redirect(redirectors.redirect(name, section)) else: logger.info(f"not sure what happened, 404") - return abort(404) + return abort(404, "Document does not exist") @app.before_request