diff --git a/website/web/api/v1/__init__.py b/website/web/api/v1/__init__.py
index 893bc208..771208c5 100644
--- a/website/web/api/v1/__init__.py
+++ b/website/web/api/v1/__init__.py
@@ -11,7 +11,7 @@
apiv1_blueprint = Blueprint(
- "apiv1", __name__, url_prefix=""
+ "apiv1", __name__, url_prefix="/api"
) # should we put the version in the URL ?
csrf.exempt(apiv1_blueprint)
@@ -29,14 +29,15 @@ def setup_api(application: Any) -> Api:
apiv1_blueprint,
title="Vulnerability-Lookup API",
version=version("vulnerabilitylookup"),
- description=""
+ description=""
"
"
- "API to query Vulnerability Lookup"
+ "API to query Vulnerability-Lookup. "
+ "A project from CIRCL."
"
Back to the main page"
- "
Official documentation",
+ "
Official documentation of the project.",
license="GNU Affero General Public License version 3",
license_url="https://www.gnu.org/licenses/agpl-3.0.html",
- doc="/doc",
+ doc="/",
security="apikey",
authorizations=authorizations,
contact_email=application.config.get("ADMIN_EMAIL", "info@circl.lu"),
@@ -49,13 +50,14 @@ def custom_ui() -> str:
return render_template(
"swagger-ui.html",
title=api.title,
- specs_url="{}://{}/swagger.json".format(
+ specs_url="{}://{}/api/swagger.json".format(
http_schema, get_config("generic", "public_domain")
),
)
from website.web.api.v1 import system
from website.web.api.v1 import vulnerability
+ from website.web.api.v1 import browse
from website.web.api.v1 import comment
from website.web.api.v1 import user
from website.web.api.v1 import bundle
@@ -63,16 +65,17 @@ def custom_ui() -> str:
from website.web.api.v1 import sighting
from website.web.api.v1 import stats
- api.add_namespace(system.system_ns, path="/api")
- api.add_namespace(vulnerability.vulnerability_ns, path="/api")
+ api.add_namespace(system.system_ns, path="/system")
+ api.add_namespace(vulnerability.vulnerability_ns, path="/vulnerability")
api.add_namespace(vulnerability.legacy_ns, path="/")
- api.add_namespace(stats.stats_ns, path="/api")
+ api.add_namespace(browse.browse_ns, path="/browse")
+ api.add_namespace(stats.stats_ns, path="/stats")
if get_config("generic", "user_accounts"):
- api.add_namespace(comment.comment_ns, path="/api")
- api.add_namespace(user.user_ns, path="/api")
- api.add_namespace(bundle.bundle_ns, path="/api")
- api.add_namespace(sighting.sighting_ns, path="/api")
- api.add_namespace(epss.epss_ns, path="/api")
+ api.add_namespace(comment.comment_ns, path="/comment")
+ api.add_namespace(user.user_ns, path="/user")
+ api.add_namespace(bundle.bundle_ns, path="/bundle")
+ api.add_namespace(sighting.sighting_ns, path="/sighting")
+ api.add_namespace(epss.epss_ns, path="/epss")
return api
diff --git a/website/web/api/v1/bundle.py b/website/web/api/v1/bundle.py
index ceeac285..4ff97cdc 100644
--- a/website/web/api/v1/bundle.py
+++ b/website/web/api/v1/bundle.py
@@ -100,7 +100,7 @@
)
-@bundle_ns.route("/bundle/")
+@bundle_ns.route("/")
class BundleItem(Resource): # type: ignore[misc]
@bundle_ns.doc(description="Get a bundle with its UUID.") # type: ignore[misc]
@bundle_ns.doc(
@@ -138,7 +138,7 @@ def delete(self, bundle_uuid: str) -> Tuple[dict[Any, Any], int]:
return {"message": "Bundle not found."}, 404
-@bundle_ns.route("/bundle/")
+@bundle_ns.route("/")
class BundlesList(Resource): # type: ignore[misc]
@bundle_ns.doc("list_bundles") # type: ignore[misc]
@bundle_ns.expect(parser) # type: ignore[misc]
diff --git a/website/web/api/v1/comment.py b/website/web/api/v1/comment.py
index b6a2cc82..b59cbb7c 100644
--- a/website/web/api/v1/comment.py
+++ b/website/web/api/v1/comment.py
@@ -105,7 +105,7 @@
)
-@comment_ns.route("/comment/")
+@comment_ns.route("/")
class CommentItem(Resource): # type: ignore[misc]
@comment_ns.doc(description="Get a comment with its UUID.") # type: ignore[misc]
@comment_ns.doc(
@@ -145,7 +145,7 @@ def delete(self, comment_uuid: str) -> Tuple[dict[Any, Any], int]:
return {"message": "Comment not found."}, 404
-@comment_ns.route("/comment/")
+@comment_ns.route("/")
class CommentsList(Resource): # type: ignore[misc]
@comment_ns.doc("list_comments") # type: ignore[misc]
@comment_ns.expect(parser) # type: ignore[misc]
diff --git a/website/web/api/v1/epss.py b/website/web/api/v1/epss.py
index ed04e5a3..db506ca6 100644
--- a/website/web/api/v1/epss.py
+++ b/website/web/api/v1/epss.py
@@ -11,7 +11,7 @@
epss_ns = Namespace("epss", description="EPSS related operations.")
-@epss_ns.route("/epss/")
+@epss_ns.route("/")
class EPSSItem(Resource): # type: ignore[misc]
@epss_ns.doc(description="Experimental - Get the EPSS score of a vulnerability.") # type: ignore[misc]
@epss_ns.doc(
diff --git a/website/web/api/v1/sighting.py b/website/web/api/v1/sighting.py
index f25ac21e..3faca0dd 100644
--- a/website/web/api/v1/sighting.py
+++ b/website/web/api/v1/sighting.py
@@ -119,7 +119,7 @@
)
-@sighting_ns.route("/sighting/")
+@sighting_ns.route("/")
class SightingItem(Resource): # type: ignore[misc]
@sighting_ns.doc(description="Get a sighting with its UUID.") # type: ignore[misc]
@sighting_ns.doc(
@@ -136,8 +136,8 @@ def get(self, sighting_uuid: str) -> Tuple[dict[Any, Any], int]:
return result, 200
-@sighting_ns.route("/sighting")
-@sighting_ns.route("/sighting/")
+@sighting_ns.route("")
+@sighting_ns.route("/")
class SightingsList(Resource): # type: ignore[misc]
@sighting_ns.doc("list_sightings") # type: ignore[misc]
@sighting_ns.expect(parser) # type: ignore[misc]
diff --git a/website/web/api/v1/stats.py b/website/web/api/v1/stats.py
index caa93bf1..3b37d586 100644
--- a/website/web/api/v1/stats.py
+++ b/website/web/api/v1/stats.py
@@ -15,7 +15,7 @@
logger = logging.getLogger(__name__)
-stats_ns = Namespace("stats", description="Endpoint to get various stats.")
+stats_ns = Namespace("stats", description="Endpoint for retrieving various statistics.")
# Argument Parsing
@@ -112,7 +112,7 @@ def generate_markdown_table(data: list[dict[str, Any]]) -> str:
return markdown_table
-@stats_ns.route("/stats/vulnerability/most_sighted")
+@stats_ns.route("/vulnerability/most_sighted")
class VulnerabilityMostSighted(Resource): # type: ignore[misc]
@stats_ns.doc(description="Returns the most sighted vulnerabilities.") # type: ignore[misc]
@stats_ns.expect(parser_sighting) # type: ignore[misc]
@@ -163,7 +163,7 @@ def get(self) -> Union[list[dict[str, Any]], str]:
return result
-@stats_ns.route("/stats/vulnerability/most_commented")
+@stats_ns.route("/vulnerability/most_commented")
class VulnerabilityMostCommented(Resource): # type: ignore[misc]
@stats_ns.doc(description="Returns the most commented vulnerabilities.") # type: ignore[misc]
@stats_ns.expect(parser) # type: ignore[misc]
diff --git a/website/web/api/v1/system.py b/website/web/api/v1/system.py
index d2592198..c6491ebd 100644
--- a/website/web/api/v1/system.py
+++ b/website/web/api/v1/system.py
@@ -15,10 +15,10 @@
local_instance_name = get_config("generic", "local_instance_name").lower()
local_instance_vulnid_pattern = get_config("generic", "local_instance_vulnid_pattern")
-system_ns = Namespace("system", description="Get the status of the system.")
+system_ns = Namespace("system", description="Endpoints for retrieving various information about the system's status.")
-@system_ns.route("/system/redis_up")
+@system_ns.route("/redis_up")
@system_ns.route(
"/redis_up",
doc={
@@ -32,7 +32,7 @@ def get(self) -> bool:
return vulnerabilitylookup.check_redis_up()
-@system_ns.route("/system/dbInfo")
+@system_ns.route("/dbInfo")
@system_ns.route(
"/dbInfo",
doc={
@@ -55,7 +55,7 @@ def get(self) -> dict[str, Any]:
return vulnerabilitylookup.get_info()
-@system_ns.route("/system/configInfo")
+@system_ns.route("/configInfo")
@system_ns.doc(
description="Get non-sensitive information about the configuration of the system."
)
diff --git a/website/web/api/v1/user.py b/website/web/api/v1/user.py
index 37ef2e5e..fdfa6f1d 100644
--- a/website/web/api/v1/user.py
+++ b/website/web/api/v1/user.py
@@ -76,7 +76,7 @@
)
-@user_ns.route("/user/me")
+@user_ns.route("/me")
class UserSelf(Resource): # type: ignore[misc]
@user_ns.doc(description="Get information about the currently authenticated user.") # type: ignore[misc]
@user_ns.doc(
@@ -99,7 +99,7 @@ def get(self) -> Tuple[dict[Any, Any], int]:
return me, 200
-@user_ns.route("/user/api_key")
+@user_ns.route("/api_key")
class UserNewAPIKey(Resource): # type: ignore[misc]
@user_ns.doc(description="Regenerating the API key of the authenticated user with the current API key.") # type: ignore[misc]
@user_ns.doc(
@@ -140,7 +140,7 @@ def post(self) -> Tuple[dict[Any, Any], int]:
return user_obj, 200
-@user_ns.route("/user/")
+@user_ns.route("/")
class UserItem(Resource): # type: ignore[misc]
@user_ns.doc(description="Delete a user.") # type: ignore[misc]
@user_ns.doc(
@@ -177,7 +177,7 @@ def delete(self, user_id: int) -> Tuple[dict[Any, Any], int]:
return {}, 204
-@user_ns.route("/user/")
+@user_ns.route("/")
class UsersList(Resource): # type: ignore[misc]
@user_ns.doc("list_users") # type: ignore[misc]
@user_ns.doc(
diff --git a/website/web/api/v1/vulnerability.py b/website/web/api/v1/vulnerability.py
index 8195f01f..a7edcfbf 100644
--- a/website/web/api/v1/vulnerability.py
+++ b/website/web/api/v1/vulnerability.py
@@ -29,7 +29,7 @@
vulnerability_ns = Namespace(
"vulnerability", description="Vulnerability related operations."
)
-legacy_ns = Namespace("legacy", description="legacy endpoints")
+legacy_ns = Namespace("legacy", description="Legacy endpoints for vulnerabilities.")
storage = Redis(
host=get_config("generic", "storage_db_hostname"),
@@ -55,16 +55,21 @@
)
-@vulnerability_ns.route("/vulnerability/")
-@vulnerability_ns.route(
- "/cve/",
+@vulnerability_ns.route("/")
+@legacy_ns.route(
+ "cve/",
+ doc={
+ "description": "Alias for /api/vulnerability/",
doc={
"description": "Alias for /api/vulnerability/")
class Vulnerability(Resource): # type: ignore[misc]
@vulnerability_ns.doc(description="Get a vulnerability.") # type: ignore[misc]
@vulnerability_ns.expect(parser) # type: ignore[misc]
@@ -102,7 +107,7 @@ def delete(self, vulnerability_id: str) -> Tuple[dict[Any, Any], int]:
return {}, 204
-@vulnerability_ns.route("/vulnerability/")
+@vulnerability_ns.route("/")
class VulnerabilitiesList(Resource): # type: ignore[misc]
@vulnerability_ns.doc(description="Create a vulnerability with the CVE version 5 format.") # type: ignore[misc]
@vulnerability_ns.doc(
@@ -205,10 +210,10 @@ def post(self) -> Tuple[Dict[Any, Any], int]:
return vuln, 200
-@vulnerability_ns.route("/vulnerability/last")
-@vulnerability_ns.route("/vulnerability/last/")
-@vulnerability_ns.route("/vulnerability/last/")
-@vulnerability_ns.route("/vulnerability/last//")
+@vulnerability_ns.route("/last")
+@vulnerability_ns.route("/last/")
+@vulnerability_ns.route("/last/")
+@vulnerability_ns.route("/last//")
@vulnerability_ns.doc(description="Get the last vulnerabilities")
@vulnerability_ns.route(
"/last",
@@ -226,22 +231,6 @@ def post(self) -> Tuple[Dict[Any, Any], int]:
"doc": False,
},
)
-@vulnerability_ns.route(
- "/last/",
- doc={
- "description": "Alias for /api/vulnerability/last/",
- "deprecated": True,
- "doc": False,
- },
-)
-@vulnerability_ns.route(
- "/last//",
- doc={
- "description": "Alias for /api/vulnerability/last//",
- "deprecated": True,
- "doc": False,
- },
-)
class Last(Resource): # type: ignore[misc]
def get(
self, source: str | None = None, number: int | None = 30
@@ -249,43 +238,10 @@ def get(
return [entry for v_id, entry in vulnerabilitylookup.get_last(source, number)]
-@vulnerability_ns.route("/vulnerability/browse")
-@vulnerability_ns.route(
- "/browse",
- doc={
- "description": "Alias for /api/vulnerability/browse",
- "deprecated": True,
- "doc": False,
- },
-)
-@vulnerability_ns.doc(description="Get the known vendors")
-class Vendors(Resource): # type: ignore[misc]
- def get(self) -> list[str]:
- vendor = request.args.get("vendor", "").lower()
- vendors = list(vulnerabilitylookup.get_vendors())
- if vendor and len(vendor) >= 3:
- vendors = [elem for elem in vendors if vendor in elem]
- return vendors
-
-@vulnerability_ns.route("/vulnerability/browse/")
-@vulnerability_ns.route(
- "/browse/",
- doc={
- "description": "Alias for /api/vulnerability/browse/",
- "deprecated": True,
- "doc": False,
- },
-)
-@vulnerability_ns.doc(description="Get the known products for a vendor")
-class VendorProducts(Resource): # type: ignore[misc]
- def get(self, vendor: str) -> list[str]:
- return list(vulnerabilitylookup.get_vendor_products(vendor))
-
-
-@vulnerability_ns.route("/vulnerability/search//")
-@vulnerability_ns.route(
- "/search//",
+@vulnerability_ns.route("/search//")
+@legacy_ns.route(
+ "search//",
doc={
"description": "Alias for /api/vulnerability/search//",
"deprecated": True,
@@ -293,10 +249,23 @@ def get(self, vendor: str) -> list[str]:
},
)
@vulnerability_ns.doc(
- description="Get the the vulnerabilities per vendor and a specific product"
+ description="Returns a list of vulnerabilities related to the product."
)
class VendorProductVulnerabilities(Resource): # type: ignore[misc]
def get(
self, vendor: str, product: str
) -> dict[str, list[tuple[str, dict[str, Any]]]]:
+ """Returns a list of vulnerabilities related to the product."""
return vulnerabilitylookup.get_vendor_product_vulnerabilities(vendor, product)
+
+
+@vulnerability_ns.route("/browse/")
+@vulnerability_ns.doc(description="Get the known vendors.")
+class Vendors(Resource): # type: ignore[misc]
+ def get(self) -> list[str]:
+ """Get the known vendors."""
+ vendor = request.args.get("vendor", "").lower()
+ vendors = list(vulnerabilitylookup.get_vendors())
+ if vendor and len(vendor) >= 3:
+ vendors = [elem for elem in vendors if vendor in elem]
+ return vendors
diff --git a/website/web/templates/search.html b/website/web/templates/search.html
index 798c3329..59d2ff1e 100644
--- a/website/web/templates/search.html
+++ b/website/web/templates/search.html
@@ -263,7 +263,7 @@ All the vulnerabilites related to {{vendor}} - {{product}}
document.getElementById("freetext_search").oninput = function(event) {
var text = document.getElementById("freetext_search").value;
if (text.length >= 3) {
- fetch("{{ url_for('apiv1.vulnerability_vendors') }}?vendor="+text)
+ fetch("{{ url_for('apiv1.browse_vendors') }}?vendor="+text)
.then(response => response.json())
.then(vendors => {
var options = '';