diff --git a/swh/web/misc/badges.py b/swh/web/misc/badges.py index 13248063..16d6da66 100644 --- a/swh/web/misc/badges.py +++ b/swh/web/misc/badges.py @@ -1,166 +1,166 @@ # Copyright (C) 2019-2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from base64 import b64encode from typing import Optional, cast from pybadges import badge from django.conf.urls import url from django.contrib.staticfiles import finders from django.http import HttpRequest, HttpResponse from swh.model.exceptions import ValidationError -from swh.model.identifiers import ( - CONTENT, - DIRECTORY, - ORIGIN, - RELEASE, - REVISION, - SNAPSHOT, - parse_swhid, - swhid, -) +from swh.model.hashutil import hash_to_bytes, hash_to_hex +from swh.model.identifiers import RELEASE, CoreSWHID, ObjectType, QualifiedSWHID from swh.web.common import archive from swh.web.common.exc import BadInputExc, NotFoundExc from swh.web.common.identifiers import resolve_swhid from swh.web.common.utils import reverse _orange = "#f36a24" _blue = "#0172b2" _red = "#cd5741" _swh_logo_data = None _badge_config = { - CONTENT: {"color": _blue, "title": "Archived source file",}, - DIRECTORY: {"color": _blue, "title": "Archived source tree",}, - ORIGIN: {"color": _orange, "title": "Archived software repository",}, - RELEASE: {"color": _blue, "title": "Archived software release",}, - REVISION: {"color": _blue, "title": "Archived commit",}, - SNAPSHOT: {"color": _blue, "title": "Archived software repository snapshot",}, + "content": {"color": _blue, "title": "Archived source file",}, + "directory": {"color": _blue, "title": "Archived source tree",}, + "origin": {"color": _orange, "title": "Archived software repository",}, + "release": {"color": _blue, "title": "Archived software release",}, + "revision": {"color": _blue, "title": "Archived commit",}, + "snapshot": {"color": _blue, "title": "Archived software repository snapshot",}, "error": {"color": _red, "title": "An error occurred when generating the badge"}, } def _get_logo_data() -> str: """ Get data-URI for Software Heritage SVG logo to embed it in the generated badges. """ global _swh_logo_data if _swh_logo_data is None: swh_logo_path = cast(str, finders.find("img/swh-logo-white.svg")) with open(swh_logo_path, "rb") as swh_logo_file: _swh_logo_data = "data:image/svg+xml;base64,%s" % b64encode( swh_logo_file.read() ).decode("ascii") return _swh_logo_data def _swh_badge( request: HttpRequest, object_type: str, object_id: str, object_swhid: Optional[str] = "", ) -> HttpResponse: """ Generate a Software Heritage badge for a given object type and id. Args: request: input http request object_type: The type of swh object to generate a badge for, either *content*, *directory*, *revision*, *release*, *origin* or *snapshot* object_id: The id of the swh object, either an url for origin type or a *sha1* for other object types object_swhid: If provided, the object SWHID will not be recomputed Returns: HTTP response with content type *image/svg+xml* containing the SVG badge data. If the provided parameters are invalid, HTTP 400 status code will be returned. If the object can not be found in the archive, HTTP 404 status code will be returned. """ left_text = "error" whole_link = None try: - if object_type == ORIGIN: + if object_type == "origin": archive.lookup_origin({"url": object_id}) right_text = "repository" whole_link = reverse( "browse-origin", query_params={"origin_url": object_id} ) else: # when SWHID is provided, object type and id will be parsed # from it if object_swhid: - parsed_swhid = parse_swhid(object_swhid) - object_type = parsed_swhid.object_type - object_id = parsed_swhid.object_id - swh_object = archive.lookup_object(object_type, object_id) - if object_swhid: - right_text = object_swhid + parsed_swhid = QualifiedSWHID.from_string(object_swhid) + object_type = parsed_swhid.object_type.name.lower() + object_id = hash_to_hex(parsed_swhid.object_id) + swh_object = archive.lookup_object(object_type, object_id) + # remove SWHID qualified if any for badge text + right_text = str( + CoreSWHID( + object_type=parsed_swhid.object_type, + object_id=parsed_swhid.object_id, + ) + ) else: - right_text = swhid(object_type, object_id) - - whole_link = resolve_swhid(right_text)["browse_url"] - # remove SWHID metadata if any for badge text - if object_swhid: - right_text = right_text.split(";")[0] + right_text = str( + CoreSWHID( + object_type=ObjectType[object_type.upper()], + object_id=hash_to_bytes(object_id), + ) + ) + swh_object = archive.lookup_object(object_type, object_id) + + whole_link = resolve_swhid(str(right_text))["browse_url"] # use release name for badge text if object_type == RELEASE: right_text = "release %s" % swh_object["name"] left_text = "archived" except (BadInputExc, ValidationError): right_text = f'invalid {object_type if object_type else "object"} id' object_type = "error" except NotFoundExc: right_text = f'{object_type if object_type else "object"} not found' object_type = "error" badge_data = badge( left_text=left_text, right_text=right_text, right_color=_badge_config[object_type]["color"], whole_link=request.build_absolute_uri(whole_link), whole_title=_badge_config[object_type]["title"], logo=_get_logo_data(), embed_logo=True, ) return HttpResponse(badge_data, content_type="image/svg+xml") def _swh_badge_swhid(request: HttpRequest, object_swhid: str) -> HttpResponse: """ Generate a Software Heritage badge for a given object SWHID. Args: request (django.http.HttpRequest): input http request object_swhid (str): a SWHID of an archived object Returns: django.http.HttpResponse: An http response with content type *image/svg+xml* containing the SVG badge data. If any error occurs, a status code of 400 will be returned. """ return _swh_badge(request, "", "", object_swhid) urlpatterns = [ url( r"^badge/(?P[a-z]+)/(?P.+)/$", _swh_badge, name="swh-badge", ), url( r"^badge/(?Pswh:[0-9]+:[a-z]+:[0-9a-f]+.*)/$", _swh_badge_swhid, name="swh-badge-swhid", ), ] diff --git a/swh/web/tests/misc/test_badges.py b/swh/web/tests/misc/test_badges.py index d079a49f..41c04fd1 100644 --- a/swh/web/tests/misc/test_badges.py +++ b/swh/web/tests/misc/test_badges.py @@ -1,210 +1,208 @@ # Copyright (C) 2019-2020 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information from corsheaders.middleware import ACCESS_CONTROL_ALLOW_ORIGIN from hypothesis import given -from swh.model.identifiers import ( - CONTENT, - DIRECTORY, - ORIGIN, - RELEASE, - REVISION, - SNAPSHOT, - swhid, -) +from swh.model.hashutil import hash_to_bytes +from swh.model.identifiers import ObjectType, QualifiedSWHID from swh.web.common import archive from swh.web.common.identifiers import resolve_swhid from swh.web.common.utils import reverse from swh.web.misc.badges import _badge_config, _get_logo_data from swh.web.tests.django_asserts import assert_contains from swh.web.tests.strategies import ( content, directory, invalid_sha1, new_origin, origin, release, revision, snapshot, unknown_content, unknown_directory, unknown_release, unknown_revision, unknown_snapshot, ) from swh.web.tests.utils import check_http_get_response @given(content()) def test_content_badge(client, content): - _test_badge_endpoints(client, CONTENT, content["sha1_git"]) + _test_badge_endpoints(client, "content", content["sha1_git"]) @given(directory()) def test_directory_badge(client, directory): - _test_badge_endpoints(client, DIRECTORY, directory) + _test_badge_endpoints(client, "directory", directory) @given(origin()) def test_origin_badge(client, origin): - _test_badge_endpoints(client, ORIGIN, origin["url"]) + _test_badge_endpoints(client, "origin", origin["url"]) @given(release()) def test_release_badge(client, release): - _test_badge_endpoints(client, RELEASE, release) + _test_badge_endpoints(client, "release", release) @given(revision()) def test_revision_badge(client, revision): - _test_badge_endpoints(client, REVISION, revision) + _test_badge_endpoints(client, "revision", revision) @given(snapshot()) def test_snapshot_badge(client, snapshot): - _test_badge_endpoints(client, SNAPSHOT, snapshot) + _test_badge_endpoints(client, "snapshot", snapshot) @given( unknown_content(), unknown_directory(), new_origin(), unknown_release(), unknown_revision(), unknown_snapshot(), invalid_sha1(), ) def test_badge_errors( client, unknown_content, unknown_directory, new_origin, unknown_release, unknown_revision, unknown_snapshot, invalid_sha1, ): for object_type, object_id in ( - (CONTENT, unknown_content["sha1_git"]), - (DIRECTORY, unknown_directory), - (ORIGIN, new_origin.url), - (RELEASE, unknown_release), - (REVISION, unknown_revision), - (SNAPSHOT, unknown_snapshot), + ("content", unknown_content["sha1_git"]), + ("directory", unknown_directory), + ("origin", new_origin), + ("release", unknown_release), + ("revision", unknown_revision), + ("snapshot", unknown_snapshot), ): url_args = {"object_type": object_type, "object_id": object_id} url = reverse("swh-badge", url_args=url_args) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml" ) _check_generated_badge(resp, **url_args, error="not found") - if object_type != ORIGIN: - object_swhid = swhid(object_type, object_id) - url = reverse("swh-badge-swhid", url_args={"object_swhid": object_swhid}) - resp = check_http_get_response( - client, url, status_code=200, content_type="image/svg+xml" - ) - _check_generated_badge(resp, **url_args, error="not found") - for object_type, object_id in ( - (CONTENT, invalid_sha1), - (DIRECTORY, invalid_sha1), - (RELEASE, invalid_sha1), - (REVISION, invalid_sha1), - (SNAPSHOT, invalid_sha1), + (ObjectType.CONTENT, invalid_sha1), + (ObjectType.DIRECTORY, invalid_sha1), + (ObjectType.RELEASE, invalid_sha1), + (ObjectType.REVISION, invalid_sha1), + (ObjectType.SNAPSHOT, invalid_sha1), ): - url_args = {"object_type": object_type, "object_id": object_id} + url_args = {"object_type": object_type.name.lower(), "object_id": object_id} url = reverse("swh-badge", url_args=url_args) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml" ) _check_generated_badge(resp, **url_args, error="invalid id") - object_swhid = f"swh:1:{object_type[:3]}:{object_id}" + object_swhid = f"swh:1:{object_type.value}:{object_id}" url = reverse("swh-badge-swhid", url_args={"object_swhid": object_swhid}) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml" ) _check_generated_badge(resp, "", "", error="invalid id") @given(origin(), release()) def test_badge_endpoints_have_cors_header(client, origin, release): url = reverse( - "swh-badge", url_args={"object_type": ORIGIN, "object_id": origin["url"]} + "swh-badge", url_args={"object_type": "origin", "object_id": origin["url"]} ) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml", http_origin="https://example.org", ) assert ACCESS_CONTROL_ALLOW_ORIGIN in resp - release_swhid = swhid(RELEASE, release) + release_swhid = str( + QualifiedSWHID(object_type=ObjectType.RELEASE, object_id=hash_to_bytes(release)) + ) url = reverse("swh-badge-swhid", url_args={"object_swhid": release_swhid}) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml", http_origin="https://example.org", ) assert ACCESS_CONTROL_ALLOW_ORIGIN in resp -def _test_badge_endpoints(client, object_type, object_id): +def _test_badge_endpoints(client, object_type: str, object_id: str): url_args = {"object_type": object_type, "object_id": object_id} url = reverse("swh-badge", url_args=url_args) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml" ) _check_generated_badge(resp, **url_args) - if object_type != ORIGIN: - obj_swhid = swhid(object_type, object_id) + + if object_type != "origin": + obj_swhid = str( + QualifiedSWHID( + object_type=ObjectType[object_type.upper()], + object_id=hash_to_bytes(object_id), + ) + ) url = reverse("swh-badge-swhid", url_args={"object_swhid": obj_swhid}) resp = check_http_get_response( client, url, status_code=200, content_type="image/svg+xml" ) _check_generated_badge(resp, **url_args) def _check_generated_badge(response, object_type, object_id, error=None): assert response.status_code == 200, response.content assert response["Content-Type"] == "image/svg+xml" if not object_type: object_type = "object" - if object_type == ORIGIN and error is None: + if object_type == "origin" and error is None: link = reverse("browse-origin", query_params={"origin_url": object_id}) text = "repository" elif error is None: - text = swhid(object_type, object_id) + text = str( + QualifiedSWHID( + object_type=ObjectType[object_type.upper()], + object_id=hash_to_bytes(object_id), + ) + ) link = resolve_swhid(text)["browse_url"] - if object_type == RELEASE: + if object_type == "release": release = archive.lookup_release(object_id) text = release["name"] elif error == "invalid id": text = "error" link = f"invalid {object_type} id" object_type = "error" elif error == "not found": text = "error" link = f"{object_type} not found" object_type = "error" assert_contains(response, "") assert_contains(response, _get_logo_data()) assert_contains(response, _badge_config[object_type]["color"]) assert_contains(response, _badge_config[object_type]["title"]) assert_contains(response, text) assert_contains(response, link)