From ec37e43c96c54a84b084b467b50d44f7dfee792b Mon Sep 17 00:00:00 2001 From: Rob Hudson Date: Thu, 12 Oct 2023 11:24:11 -0700 Subject: [PATCH] Add breach data model and sync command Fixes #13611, #13612 --- .../products/management/commands/__init__.py | 3 + .../management/commands/sync_breaches.py | 27 +++ bedrock/products/migrations/0001_initial.py | 40 ++++ bedrock/products/migrations/__init__.py | 3 + bedrock/products/models.py | 172 ++++++++++++++++++ bedrock/products/tests/__init__.py | 0 bedrock/products/tests/conftest.py | 26 +++ bedrock/products/tests/test_models.py | 98 ++++++++++ bedrock/settings/base.py | 4 + requirements/dev.txt | 154 ++++++++++++++++ requirements/prod.in | 1 + requirements/prod.txt | 136 ++++++++++++++ 12 files changed, 664 insertions(+) create mode 100644 bedrock/products/management/commands/__init__.py create mode 100644 bedrock/products/management/commands/sync_breaches.py create mode 100644 bedrock/products/migrations/0001_initial.py create mode 100644 bedrock/products/migrations/__init__.py create mode 100644 bedrock/products/tests/__init__.py create mode 100644 bedrock/products/tests/conftest.py create mode 100644 bedrock/products/tests/test_models.py diff --git a/bedrock/products/management/commands/__init__.py b/bedrock/products/management/commands/__init__.py new file mode 100644 index 00000000000..448bb8652d6 --- /dev/null +++ b/bedrock/products/management/commands/__init__.py @@ -0,0 +1,3 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. diff --git a/bedrock/products/management/commands/sync_breaches.py b/bedrock/products/management/commands/sync_breaches.py new file mode 100644 index 00000000000..bc795acfc1b --- /dev/null +++ b/bedrock/products/management/commands/sync_breaches.py @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +from django.core.management.base import BaseCommand + +from bedrock.products.models import Breach +from bedrock.utils.management.decorators import alert_sentry_on_exception + + +@alert_sentry_on_exception +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."), + + def output(self, msg): + if not self.quiet: + print(msg) + + def handle(self, *args, **options): + self.quiet = options["quiet"] + + added, updated = Breach.objects.sync_db() + self.output(f"Breaches added: {added}") + self.output(f"Breaches updated: {updated}") + + Breach.objects.sync_logos(verbose=not self.quiet) diff --git a/bedrock/products/migrations/0001_initial.py b/bedrock/products/migrations/0001_initial.py new file mode 100644 index 00000000000..40a8d71b53a --- /dev/null +++ b/bedrock/products/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# Generated by Django 3.2.21 on 2023-09-26 17:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Breach", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255, unique=True)), + ("title", models.CharField(max_length=255)), + ("domain", models.CharField(max_length=255)), + ("breach_date", models.DateField(null=True)), + ("added_date", models.DateTimeField(null=True)), + ("modified_date", models.DateTimeField(null=True)), + ("pwn_count", models.PositiveIntegerField(default=0)), + ("logo_path", models.CharField(max_length=255)), + ("data_classes", models.JSONField(default=list)), + ("is_verified", models.BooleanField(default=False)), + ("is_fabricated", models.BooleanField(default=False)), + ("is_sensitive", models.BooleanField(default=False)), + ("is_retired", models.BooleanField(default=False)), + ("is_spam_list", models.BooleanField(default=False)), + ("is_malware", models.BooleanField(default=False)), + ], + options={ + "verbose_name_plural": "Breaches", + }, + ), + ] diff --git a/bedrock/products/migrations/__init__.py b/bedrock/products/migrations/__init__.py new file mode 100644 index 00000000000..448bb8652d6 --- /dev/null +++ b/bedrock/products/migrations/__init__.py @@ -0,0 +1,3 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. diff --git a/bedrock/products/models.py b/bedrock/products/models.py index 448bb8652d6..06b9d92bd20 100644 --- a/bedrock/products/models.py +++ b/bedrock/products/models.py @@ -1,3 +1,175 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. +from tempfile import TemporaryFile + +from django.conf import settings +from django.db import models +from django.utils.dateparse import parse_date, parse_datetime + +import requests +from google.cloud import storage + + +class BreachManager(models.Manager): + def sync_db(self): + # Fetch new breach data and update the database. + BREACH_URL = "https://haveibeenpwned.com/api/v3/breaches" + + response = requests.get(BREACH_URL, headers={"User-Agent": "mozilla-org"}) + response.raise_for_status() + + breaches_added = 0 + breaches_updated = 0 + + for data in response.json(): + breach, created = self.get_or_create(name=data["Name"]) + obj_data = { + "title": data["Title"], + "domain": data["Domain"], + "breach_date": data["BreachDate"], + "added_date": data["AddedDate"], + "modified_date": data["ModifiedDate"], + "pwn_count": data["PwnCount"], + "logo_path": "", # We aren't using the hibp logos because they are too large and inconsistent. + "data_classes": data["DataClasses"], + "is_verified": data["IsVerified"], + "is_fabricated": data["IsFabricated"], + "is_sensitive": data["IsSensitive"], + "is_retired": data["IsRetired"], + "is_spam_list": data["IsSpamList"], + "is_malware": data["IsMalware"], + } + + changed = False + for key, value in obj_data.items(): + # Convert date strings to date objects. + if key == "breach_date": + value = parse_date(value) + elif key in ("added_date", "modified_date"): + value = parse_datetime(value) + + if getattr(breach, key, None) != value: + changed = True + setattr(breach, key, value) + + if changed: + if created: + breaches_added += 1 + else: + breaches_updated += 1 + breach.save() + + return breaches_added, breaches_updated + + def sync_logos(self, verbose=True): + # Iterate over db breaches and download logos. + verbose and print("Syncing breach logos...") + + GCS_DIR = "media/" + GCS_PATH = "img/products/monitor/breach_logos/" + + def _urlize(path): + # Convert a GCS path to the full static URL to the logo. + return path.replace(GCS_DIR, settings.STATIC_URL, 1) + + # Get list of all breach logos from GCS. + try: + client = storage.Client() + bucket = client.get_bucket(settings.GCS_MEDIA_BUCKET_NAME) + blob_list = bucket.list_blobs(prefix=GCS_DIR + GCS_PATH) + gcs_logos = [_urlize(blob.name) for blob in blob_list] + except Exception as e: + verbose and print(f"Failed to get list of GCS logos: {e}. Aborting.") + return + + for breach in self.all(): + if not breach.domain: + verbose and print(f"Skipping {breach.name} because it has no domain.") + continue + + # Check if the breach has a logo_path value and if it exists in GCS. + if breach.logo_path and breach.logo_path in gcs_logos: + verbose and print(f"Skipping {breach.name} because it already has an existing logo.") + continue + + # NOTE: We are storing the full logo URL in the logo_path field since the db is per deployment environment. + # This allows us to reference the logo images locally without needing to download them. + logo_path = f"{GCS_DIR}{GCS_PATH}{breach.domain.lower()}.ico" + logo_url = _urlize(logo_path) + + # Check if the logo exists in GCS. If so, no reason to re-fetch fron DDG. + if logo_url in gcs_logos: + breach.logo_path = logo_url + breach.save() + print(f"Found existing logo for {breach.name} in GCS. Updating db.") + continue + + # Fetch the logo from the ddg api. + resp = requests.get(f"https://icons.duckduckgo.com/ip3/{breach.domain}.ico", headers={"User-Agent": "mozilla-org"}) + if resp.status_code != 200: + verbose and print(f"Failed to fetch logo for {breach.name} from ddg api. Status code: {resp.status_code}.") + continue + + # Save the logo to a temp file then upload to GCS. + with TemporaryFile() as tf: + tf.write(resp.content) + tf.seek(0) + try: + blob = bucket.blob(logo_path) + blob.upload_from_file(tf) + print(f"Uploaded logo for {breach.name} to GCS: {logo_path}") + except Exception as e: + verbose and print(f"Failed to upload logo for {breach.name} to GCS: {e}") + continue + + # Update the logo_path value in the db. + breach.logo_path = logo_url + breach.save() + verbose and print(f"Saved logo for {breach.name} to {breach.logo_path}") + + # Add the logo_path to the list of gcs logos. + gcs_logos.append(logo_path) + + +class Breach(models.Model): + name = models.CharField(max_length=255, unique=True) + title = models.CharField(max_length=255) + domain = models.CharField(max_length=255) + breach_date = models.DateField(null=True) + added_date = models.DateTimeField(null=True) + modified_date = models.DateTimeField(null=True) + pwn_count = models.PositiveIntegerField(default=0) + # Note: The description is unused on the site and not included to reduce the size of the database. + # description = models.TextField() + logo_path = models.CharField(max_length=255) + data_classes = models.JSONField(default=list) + is_verified = models.BooleanField(default=False) + is_fabricated = models.BooleanField(default=False) + is_sensitive = models.BooleanField(default=False) + is_retired = models.BooleanField(default=False) + is_spam_list = models.BooleanField(default=False) + is_malware = models.BooleanField(default=False) + + objects = BreachManager() + + class Meta: + verbose_name_plural = "Breaches" + + def __str__(self): + return self.name + + @property + def category(self): + if self.name in ("Exactis", "Apollo", "YouveBeenScraped", "ElasticsearchSalesLeads", "Estonia", "MasterDeeds", "PDL"): + return "data-aggregator-breach" + if self.is_sensitive: + return "sensitive-breach" + if self.domain != "": + return "website-breach" + return "data-aggregator-breach" + + @property + def is_delayed(self): + # Boolean whether the difference between the `breach_date` and `added_date` is greater than 90 days. + return abs((self.added_date.date() - self.breach_date).days) > 90 diff --git a/bedrock/products/tests/__init__.py b/bedrock/products/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/bedrock/products/tests/conftest.py b/bedrock/products/tests/conftest.py new file mode 100644 index 00000000000..a1d3014e8cf --- /dev/null +++ b/bedrock/products/tests/conftest.py @@ -0,0 +1,26 @@ +from django.utils.dateparse import parse_date, parse_datetime + +import pytest + +from bedrock.products.models import Breach + + +@pytest.fixture +def breach(db): + return Breach.objects.create( + name="Twitter", + title="Twitter", + domain="twitter.com", + breach_date=parse_date("2022-01-01"), + added_date=parse_datetime("2022-08-01T01:23:45Z"), + modified_date=parse_datetime("2022-08-01T01:23:45Z"), + pwn_count=6682453, + logo_path="/path/to/twitter.com.ico", + data_classes=["Bios", "Email addresses", "Geographic locations", "Names", "Phone numbers", "Profile photos", "Usernames"], + is_verified=True, + is_fabricated=False, + is_sensitive=False, + is_retired=False, + is_spam_list=False, + is_malware=False, + ) diff --git a/bedrock/products/tests/test_models.py b/bedrock/products/tests/test_models.py new file mode 100644 index 00000000000..bdef0e482b1 --- /dev/null +++ b/bedrock/products/tests/test_models.py @@ -0,0 +1,98 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + + +from unittest import mock + +from django.utils.dateparse import parse_date, parse_datetime + +import pytest + +from bedrock.products.models import Breach + + +def _update(**kwargs): + Breach.objects.filter(name="Twitter").update(**kwargs) + + +def test_is_delayed(breach): + assert breach.is_delayed is True + + +def test_is_not_delayed(breach): + _update(breach_date=parse_date("2022-07-01")) + breach = Breach.objects.get(name="Twitter") + assert breach.is_delayed is False + + +@pytest.mark.parametrize( + "kwargs, expected", + [ + (None, "website-breach"), + ({"name": "Apollo"}, "data-aggregator-breach"), + ({"is_sensitive": True}, "sensitive-breach"), + ({"domain": ""}, "data-aggregator-breach"), + ], +) +def test_category(breach, kwargs, expected): + if kwargs: + _update(**kwargs) + breach = Breach.objects.get() + assert breach.category == expected + + +BREACH_JSON = { + "Name": "Twitter", + "Title": "Twitter", + "Domain": "twitter.com", + "BreachDate": "2022-01-01", + "AddedDate": "2022-08-01T01:23:45Z", + "ModifiedDate": "2022-08-01T01:23:45Z", + "PwnCount": 6682453, + "Description": "Example description. We don't use this field.", + "LogoPath": "/path/to/twitter.com.ico", + "DataClasses": ["Bios", "Email addresses", "Geographic locations", "Names", "Phone numbers", "Profile photos", "Usernames"], + "IsVerified": True, + "IsFabricated": False, + "IsSensitive": False, + "IsRetired": False, + "IsSpamList": False, + "IsMalware": False, + "IsSubscriptionFree": False, # We don't use this field. +} + + +@mock.patch("bedrock.products.models.requests.get") +def test_sync_db(mock_requests, db): + mock_requests.return_value.json.return_value = [BREACH_JSON] + added, updated = Breach.objects.sync_db() + assert added == 1 + assert updated == 0 + assert Breach.objects.count() == 1 + breach = Breach.objects.get() + assert breach.name == "Twitter" + assert breach.title == "Twitter" + assert breach.domain == "twitter.com" + assert breach.breach_date == parse_date("2022-01-01") + assert breach.added_date == parse_datetime("2022-08-01T01:23:45Z") + assert breach.modified_date == parse_datetime("2022-08-01T01:23:45Z") + assert breach.pwn_count == 6682453 + assert breach.logo_path == "" + assert breach.data_classes == ["Bios", "Email addresses", "Geographic locations", "Names", "Phone numbers", "Profile photos", "Usernames"] + assert breach.is_verified is True + assert breach.is_fabricated is False + assert breach.is_sensitive is False + assert breach.is_retired is False + assert breach.is_spam_list is False + assert breach.is_malware is False + + +@mock.patch("bedrock.products.models.requests.get") +def test_sync_db__update(mock_requests, breach, db): + BREACH_JSON["PwnCount"] = 9999999 + mock_requests.return_value.json.return_value = [BREACH_JSON] + added, updated = Breach.objects.sync_db() + assert added == 0 + assert updated == 1 + assert Breach.objects.count() == 1 diff --git a/bedrock/settings/base.py b/bedrock/settings/base.py index efbd737fb9a..49f93f352f1 100644 --- a/bedrock/settings/base.py +++ b/bedrock/settings/base.py @@ -507,6 +507,10 @@ def lazy_langs(): if DEBUG: STATICFILES_DIRS += (path("media"),) +# GCS bucket name for media. Configured on a per-deployment basis in envvars. Defaults to dev. +# NOTE: This shouldn't be needed locally unless you're testing GCS uploads. +GCS_MEDIA_BUCKET_NAME = config("GCS_MEDIA_BUCKET_NAME", default="bedrock-nonprod-dev-media") + def set_whitenoise_headers(headers, path, url): if "/fonts/" in url: diff --git a/requirements/dev.txt b/requirements/dev.txt index b7fdf4efdd2..dec71b0e6c5 100755 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -104,6 +104,12 @@ braceexpand==0.1.7 \ --hash=sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014 \ --hash=sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705 # via -r requirements/dev.in +cachetools==5.3.1 \ + --hash=sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590 \ + --hash=sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b + # via + # -r requirements/prod.txt + # google-auth certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 @@ -441,6 +447,115 @@ glean-parser==8.1.1 \ --hash=sha256:5fdd123d9711032a4e2acfa7983cbbfa6a53a29f7270dbe52be9e5ee1790a0f8 \ --hash=sha256:6d49e0c0aac34a1e9eda7fc63b12f1513efed1c827e0ac94078493114049021e # via -r requirements/prod.txt +google-api-core==2.12.0 \ + --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ + --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 + # via + # -r requirements/prod.txt + # google-cloud-core + # google-cloud-storage +google-auth==2.23.2 \ + --hash=sha256:5a9af4be520ba33651471a0264eead312521566f44631cbb621164bc30c8fd40 \ + --hash=sha256:c2e253347579d483004f17c3bd0bf92e611ef6c7ba24d41c5c59f2e7aeeaf088 + # via + # -r requirements/prod.txt + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.3.3 \ + --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ + --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 + # via + # -r requirements/prod.txt + # google-cloud-storage +google-cloud-storage==2.11.0 \ + --hash=sha256:6fbf62659b83c8f3a0a743af0d661d2046c97c3a5bfb587c4662c4bc68de3e31 \ + --hash=sha256:88cbd7fb3d701c780c4272bc26952db99f25eb283fb4c2208423249f00b5fe53 + # via -r requirements/prod.txt +google-crc32c==1.5.0 \ + --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ + --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ + --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ + --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ + --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ + --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ + --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ + --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ + --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ + --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ + --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ + --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ + --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ + --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ + --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ + --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ + --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ + --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ + --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ + --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ + --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ + --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ + --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ + --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ + --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ + --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ + --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ + --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ + --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ + --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ + --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ + --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ + --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ + --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ + --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ + --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ + --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ + --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ + --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ + --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ + --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ + --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ + --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ + --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ + --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ + --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ + --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ + --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ + --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ + --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ + --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ + --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ + --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ + --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ + --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ + --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ + --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ + --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ + --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ + --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ + --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ + --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ + --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ + --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ + --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ + --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ + --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ + --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 + # via + # -r requirements/prod.txt + # google-resumable-media +google-resumable-media==2.6.0 \ + --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ + --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b + # via + # -r requirements/prod.txt + # google-cloud-storage +googleapis-common-protos==1.60.0 \ + --hash=sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918 \ + --hash=sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708 + # via + # -r requirements/prod.txt + # google-api-core greenlet==0.4.17 \ --hash=sha256:1023d7b43ca11264ab7052cb09f5635d4afdb43df55e0854498fc63070a0b206 \ --hash=sha256:124a3ae41215f71dc91d1a3d45cbf2f84e46b543e5d60b99ecc20e24b4c8f272 \ @@ -895,10 +1010,41 @@ pluggy==1.0.0 \ # via # pypom # pytest +protobuf==4.24.4 \ + --hash=sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe \ + --hash=sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085 \ + --hash=sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b \ + --hash=sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667 \ + --hash=sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37 \ + --hash=sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92 \ + --hash=sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4 \ + --hash=sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b \ + --hash=sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9 \ + --hash=sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd \ + --hash=sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e \ + --hash=sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46 \ + --hash=sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb + # via + # -r requirements/prod.txt + # google-api-core + # googleapis-common-protos py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 # via -r requirements/dev.in +pyasn1==0.5.0 \ + --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ + --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde + # via + # -r requirements/prod.txt + # pyasn1-modules + # rsa +pyasn1-modules==0.3.0 \ + --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ + --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d + # via + # -r requirements/prod.txt + # google-auth pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 @@ -1135,6 +1281,8 @@ requests==2.31.0 \ # contentful # datadog # django-mozilla-product-details + # google-api-core + # google-cloud-storage # pygithub # pytest-base-url # pytest-selenium @@ -1147,6 +1295,12 @@ rich-text-renderer==0.2.7 \ --hash=sha256:d34f1bfc1a2903e9b087d7550a01089b06867f0be21fa494fdee73b656c80bc7 \ --hash=sha256:e6fab8d1243dddd29bb65717ade63ca57f10561a3944fc49dac770c4d95ab71c # via -r requirements/prod.txt +rsa==4.9 \ + --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ + --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 + # via + # -r requirements/prod.txt + # google-auth ruff==0.0.290 \ --hash=sha256:0e2b09ac4213b11a3520221083866a5816616f3ae9da123037b8ab275066fbac \ --hash=sha256:150bf8050214cea5b990945b66433bf9a5e0cef395c9bc0f50569e7de7540c86 \ diff --git a/requirements/prod.in b/requirements/prod.in index 48e382b026a..786aee26c63 100644 --- a/requirements/prod.in +++ b/requirements/prod.in @@ -29,6 +29,7 @@ everett==3.2.0 fluent.runtime==0.4.0 fluent.syntax==0.19.0 glean-parser==8.1.1 # Must match the required version in the Glean NPM package. +google-cloud-storage==2.11.0 greenlet==0.4.17 # Pinned for stability but subdep of Meinheld gunicorn==19.7.1 honcho==1.1.0 diff --git a/requirements/prod.txt b/requirements/prod.txt index 290e3278974..57d37c1c442 100755 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -52,6 +52,10 @@ botocore==1.31.44 \ # via # boto3 # s3transfer +cachetools==5.3.1 \ + --hash=sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590 \ + --hash=sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b + # via google-auth certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 @@ -265,6 +269,105 @@ glean-parser==8.1.1 \ --hash=sha256:5fdd123d9711032a4e2acfa7983cbbfa6a53a29f7270dbe52be9e5ee1790a0f8 \ --hash=sha256:6d49e0c0aac34a1e9eda7fc63b12f1513efed1c827e0ac94078493114049021e # via -r requirements/prod.in +google-api-core==2.12.0 \ + --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ + --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 + # via + # google-cloud-core + # google-cloud-storage +google-auth==2.23.2 \ + --hash=sha256:5a9af4be520ba33651471a0264eead312521566f44631cbb621164bc30c8fd40 \ + --hash=sha256:c2e253347579d483004f17c3bd0bf92e611ef6c7ba24d41c5c59f2e7aeeaf088 + # via + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.3.3 \ + --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ + --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 + # via google-cloud-storage +google-cloud-storage==2.11.0 \ + --hash=sha256:6fbf62659b83c8f3a0a743af0d661d2046c97c3a5bfb587c4662c4bc68de3e31 \ + --hash=sha256:88cbd7fb3d701c780c4272bc26952db99f25eb283fb4c2208423249f00b5fe53 + # via -r requirements/prod.in +google-crc32c==1.5.0 \ + --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ + --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ + --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ + --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ + --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ + --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ + --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ + --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ + --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ + --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ + --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ + --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ + --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ + --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ + --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ + --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ + --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ + --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ + --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ + --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ + --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ + --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ + --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ + --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ + --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ + --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ + --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ + --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ + --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ + --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ + --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ + --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ + --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ + --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ + --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ + --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ + --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ + --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ + --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ + --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ + --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ + --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ + --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ + --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ + --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ + --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ + --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ + --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ + --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ + --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ + --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ + --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ + --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ + --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ + --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ + --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ + --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ + --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ + --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ + --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ + --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ + --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ + --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ + --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ + --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ + --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ + --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ + --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 + # via google-resumable-media +google-resumable-media==2.6.0 \ + --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ + --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b + # via google-cloud-storage +googleapis-common-protos==1.60.0 \ + --hash=sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918 \ + --hash=sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708 + # via google-api-core greenlet==0.4.17 \ --hash=sha256:1023d7b43ca11264ab7052cb09f5635d4afdb43df55e0854498fc63070a0b206 \ --hash=sha256:124a3ae41215f71dc91d1a3d45cbf2f84e46b543e5d60b99ecc20e24b4c8f272 \ @@ -663,6 +766,33 @@ pillow==10.0.1 \ --hash=sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68 \ --hash=sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1 # via -r requirements/prod.in +protobuf==4.24.4 \ + --hash=sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe \ + --hash=sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085 \ + --hash=sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b \ + --hash=sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667 \ + --hash=sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37 \ + --hash=sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92 \ + --hash=sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4 \ + --hash=sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b \ + --hash=sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9 \ + --hash=sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd \ + --hash=sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e \ + --hash=sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46 \ + --hash=sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb + # via + # google-api-core + # googleapis-common-protos +pyasn1==0.5.0 \ + --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ + --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.3.0 \ + --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ + --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d + # via google-auth pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 @@ -796,11 +926,17 @@ requests==2.31.0 \ # contentful # datadog # django-mozilla-product-details + # google-api-core + # google-cloud-storage # pygithub rich-text-renderer==0.2.7 \ --hash=sha256:d34f1bfc1a2903e9b087d7550a01089b06867f0be21fa494fdee73b656c80bc7 \ --hash=sha256:e6fab8d1243dddd29bb65717ade63ca57f10561a3944fc49dac770c4d95ab71c # via -r requirements/prod.in +rsa==4.9 \ + --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ + --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 + # via google-auth s3transfer==0.6.0 \ --hash=sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd \ --hash=sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947