bugsink 2.1.2__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bugsink-2.1.2 → bugsink-2.2.0}/CHANGELOG.md +67 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/PKG-INFO +4 -4
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/service_backends/webhook_security.py +62 -4
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/tests.py +21 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/__init__.py +21 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/prestart.py +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/vacuum.py +22 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/test_vacuum.py +2 -1
- bugsink-2.2.0/bsmain/tests.py +45 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/_version.py +3 -3
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/conf_templates/local.py.template +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/conf_templates/singleserver.py.template +1 -2
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/settings/development.py +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/tests.py +35 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/utils.py +26 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/views.py +5 -24
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/wsgi.py +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink.egg-info/PKG-INFO +4 -4
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink.egg-info/SOURCES.txt +5 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink.egg-info/requires.txt +3 -3
- {bugsink-2.1.2 → bugsink-2.2.0}/events/markdown_stacktrace.py +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/events/models.py +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/events/tests.py +10 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/ua_stuff.py +7 -2
- {bugsink-2.1.2 → bugsink-2.2.0}/events/utils.py +5 -13
- {bugsink-2.1.2 → bugsink-2.2.0}/files/admin.py +3 -3
- bugsink-2.2.0/files/management/commands/delete_legacy_sourcemaps.py +30 -0
- bugsink-2.2.0/files/migrations/0004_alter_filemetadata_unique_together_and_more.py +34 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/minidump.py +8 -7
- {bugsink-2.1.2 → bugsink-2.2.0}/files/models.py +67 -4
- {bugsink-2.1.2 → bugsink-2.2.0}/files/tasks.py +102 -58
- {bugsink-2.1.2 → bugsink-2.2.0}/files/test_vacuum.py +17 -4
- {bugsink-2.1.2 → bugsink-2.2.0}/files/tests.py +168 -10
- {bugsink-2.1.2 → bugsink-2.2.0}/files/views.py +54 -35
- bugsink-2.2.0/ingest/event_counter.py +121 -0
- bugsink-2.2.0/ingest/management/commands/vacuum_ingest_dir.py +110 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/tests.py +138 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/views.py +46 -6
- bugsink-2.2.0/issues/markdown_issue.py +237 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/tests.py +154 -3
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/urls.py +2 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/views.py +32 -8
- bugsink-2.2.0/phonehome/migrations/0006_recalculate_installation_next_quota_check.py +32 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/tasks.py +4 -13
- bugsink-2.2.0/phonehome/utils.py +55 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/tests.py +45 -3
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/views.py +2 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/requirements.txt +3 -3
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/minidump.py +3 -3
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/tests.py +1 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/utils.py +1 -1
- bugsink-2.2.0/teams/tests.py +30 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static/css/dist/styles.css +1 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static_src/package-lock.json +91 -91
- bugsink-2.1.2/bsmain/tests.py +0 -0
- bugsink-2.1.2/ingest/event_counter.py +0 -97
- bugsink-2.1.2/ingest/management/commands/vacuum_ingest_dir.py +0 -97
- bugsink-2.1.2/teams/tests.py +0 -1
- {bugsink-2.1.2 → bugsink-2.2.0}/.bandit +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/.coveragerc +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/.github/dependabot.yml +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/.github/workflows/ci.yml +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/.github/workflows/copilot-setup-steps.yml +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/.gitignore +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/AGENTS.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/CLA.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/CONTRIBUTING.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/Dockerfile +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/Dockerfile.fromwheel +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/LICENSE +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/README.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/SECURITY.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/forms.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/migrations/0002_alter_messagingserviceconfig_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/migrations/0003_messagingserviceconfig_last_failure_error_message_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/migrations/0004_alter_messagingserviceconfig_kind.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/service_backends/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/service_backends/base.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/service_backends/discord.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/service_backends/mattermost.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/service_backends/slack.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/templates/mails/issue_alert.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/templates/mails/issue_alert.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/alerts/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/api/LICENSE +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/api/README.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/api/event.schema.altered.json +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/api/event.schema.json +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/future_python.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/check_migrations.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/cleanup_objectstorage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/convert_mariadb_uuids.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/create_auth_token.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/fetch_event_schema_json.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/makemigrations.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/migrate.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/migrate_to_current_objectstorage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/modelcounts.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/munin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/raise_exception.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/send_bomb.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/send_json.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/showstat.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/commands/stress_test.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/management/utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/migrations/0002_cachedmodelcount.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/migrations/0003_add_description_to_authtoken.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/templates/bsmain/auth_token_list.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bsmain/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/api_fields.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/api_mixins.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/api_pagination.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/api_serializers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/app_settings.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/authentication.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/conf_templates/docker.py.template +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/conf_utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/context_processors.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/debug_views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/decorators.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/email_backends.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/event_schema.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/exceptions.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/gunicorn.docker.conf.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/middleware.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/moreiterutils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/period_utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/permissions.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/pygments_extensions.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/scripts/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/scripts/create_conf.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/scripts/manage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/scripts/runsnappea.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/scripts/show_version.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/scripts/util.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/settings/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/settings/default.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/streams.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/test_api.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/test_utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/timed_sqlite_backend/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/timed_sqlite_backend/base.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/tooling.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/transaction.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/version.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink/volume_based_condition.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink.egg-info/dependency_links.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink.egg-info/entry_points.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/bugsink.egg-info/top_level.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/builddocker.bash +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/buildxdocker.bash +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/compat/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/compat/auth.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/compat/dsn.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/compat/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/compat/timestamp.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/compat/vars.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/docker-compose-sample.yaml +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/LICENSE +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/tenants/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/tenants/base.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/tenants/database_backend/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/tenants/database_backend/base.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/tenants/middleware.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ee/tenants/utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/api_views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/factories.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/cleanup_eventstorage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/delete_by_age_until_under_retention_max.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/delete_old_events.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/fix_project_digest_order.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/make_consistent.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/migrate_to_current_eventstorage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/management/commands/nuke_events.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0002_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0003_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0004_b_squashed.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0004_event_irrelevance_for_retention.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0005_event_events_even_project_abe572_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0006_event_never_evict.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0007_set_never_evict.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0008_remove_event_events_even_project_abe572_idx_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0009_event_events_even_issue_i_90497b_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0010_rename_ingest_order_event_digest_order_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0011_remove_event_events_even_project_adcdee_idx_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0012_event_ingested_at.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0013_harmonize_foogested_at.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0014_event_grouping.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0015_set_event_grouping.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0016_truncate_exception_type_128.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0017_alter_event_calculated_type_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0018_remove_event_has_exception_remove_event_has_logentry.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0019_event_storage_backend.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0020_remove_events_with_null_issue_or_grouping.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0021_alter_do_nothing.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0022_alter_event_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0023_event_remote_addr.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0024_remove_event_debug_info.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0025_fix_never_evict.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0026_event_events_even_project_625413_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0027_event_project_digest_order.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/0028_event_events_even_digeste_bf21dd_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/renderers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/retention.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/serializers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/storage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/storage_registry.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/templates/events/event_stacktrace.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/test_api.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/events/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/management/commands/vacuum_files.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/migrations/0002_chunk_created_at_file_accessed_at_file_created_at_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/migrations/0003_file_storage_backend.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/object_kinds.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/storage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/storage_registry.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/files/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/exceptions.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/filestore.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/header_validators.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/migrations/0001_set_sqlite_wal.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/migrations/0002_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/parsers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/ingest/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/api_views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/factories.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/forms.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0002_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0003_alter_turningpoint_triggering_event.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0004_b_squashed.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0004_rename_event_count_issue_digested_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0005_rename_ingest_order_issue_digest_order_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0006_issue_next_unmute_check.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0007_alter_turningpoint_options.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0008_issue_stored_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0009_fill_stored_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0010_issue_list_indexes.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0011_truncate_exception_type_128.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0012_alter_issue_calculated_type_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0013_fix_issue_stored_event_counts.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0014_grouping_grouping_key_hash.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0015_set_grouping_hash.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0016_alter_grouping_unique_together.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0017_issue_list_indexes_must_start_with_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0018_issue_is_deleted.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0019_alter_grouping_grouping_key_hash.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0020_remove_objects_with_null_issue.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0021_alter_do_nothing.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0022_turningpoint_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0023_turningpoint_set_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0024_turningpoint_project_alter_not_null.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/0025_alter_grouping_project_alter_issue_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/regressions.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/serializers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/_event_nav.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/base.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/breadcrumbs.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/event_404.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/event_details.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/event_list.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/grouping.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/history.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/issue_list.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/stacktrace.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/templates/issues/tags.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/test_api.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/issues/utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/manage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/bursty_data.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/context_managers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/management/commands/pftest_search.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/README.md +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/df-day.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/event_ingest_count-day.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/response_time_api_avg-day.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/response_time_api_max-day.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/snappea_queue_size-day.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/performance/stress-with-eviction/total_requests-day.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/management/commands/print_phonehome.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0001_b_squashed_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0002_create_installation_id.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0002_installation_email_quota_usage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0003_installation_ingest_quotas.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0003_outboundmessage.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0004_installation_created_at.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0004_installation_quota_exceeded_reason.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0005_installation_silence_email_system_warning.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/0005_reset_quota_exceeded_until.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/phonehome/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/pre-commit +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/api_views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/context_processors.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/forms.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0002_b_squashed_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0002_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0003_project_projects_pr_name_11d782_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0003_project_retention_max_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0004_project_quota_exceeded_until.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0005_project_ingested_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0006_initial_ingested_count_value.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0007_rename_ingested_event_count_project_digested_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0008_project_next_quota_check.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0009_alter_project_visibility.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0010_project_stored_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0011_fill_stored_event_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0012_project_is_deleted.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0013_delete_objects_pointing_to_null_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0014_alter_projectmembership_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0015_project_quota_exceeded_reason.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0016_reset_quota_exceeded_until.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/0017_project_issue_count.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/serializers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/mails/project_membership_invite.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/mails/project_membership_invite.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/mails/project_membership_invite_new_user.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/mails/project_membership_invite_new_user.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_alerts_setup.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_edit.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_list.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_member_settings.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_members.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_members_accept.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_members_invite.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_messaging_service_edit.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_messaging_service_new.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_new.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_sdk_setup.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_sdk_setup_javascript.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_sdk_setup_php.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/templates/projects/project_sdk_setup_python.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/test_api.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/projects/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/pyproject.toml +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/api_views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/migrations/0002_release_releases_re_sort_ep_5c07c8_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/migrations/0003_alter_release_project.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/migrations/0004_fix_indexes.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/serializers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/test_api.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/releases/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/requirements.development.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/LICENSE +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/assemble.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/stacktraces/functions.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/stacktraces/platform.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/stacktraces/processing.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/utils/safe.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry/utils/strings.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry_sdk_extensions/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry_sdk_extensions/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/sentry_sdk_extensions/transport.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/setup.cfg +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/datastructures.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/dbrouters.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/decorators.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/example_tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/foreman.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/management/commands/checksnappea.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/management/commands/runsnappea.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/migrations/0002_create_models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/migrations/0003_task_created_at.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/migrations/0004_task_snappea_tas_created_eb0824_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/migrations/0005_stat.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/settings.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/stats.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/utils.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/snappea/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/favicon.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-700.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-700italic.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-italic.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-mono-v19-cyrillic_cyrillic-ext_latin_latin-ext_vietnamese-regular.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-700.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-700italic.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-italic.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/fonts/ibm-plex-sans-v19-cyrillic_cyrillic-ext_greek_latin_latin-ext_vietnamese-regular.woff2 +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/images/bugsink-logo-dark.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/images/bugsink-logo.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/images/lang-javascript.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/images/lang-php.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/images/lang-python.png +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/entity_edit.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/issue_history.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/issue_list.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/issue_stacktrace.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/project_list.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/team_list.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/static/js/user_list.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/management/commands/init_tags.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/management/commands/vacuum_eventless_issuetags.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/management/commands/vacuum_tags.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/migrations/0002_no_cascade.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/migrations/0003_remove_objects_with_null_issue.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/migrations/0004_alter_do_nothing.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/migrations/0005_alter_eventtag_project_alter_issuetag_project_and_more.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/search.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tags/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/api_views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/forms.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/0001_b_squashed_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/0002_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/0002_team_teams_team_name_43e047_idx.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/0003_alter_team_visibility.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/0004_remove_team_slug.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/serializers.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/mails/team_membership_invite.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/mails/team_membership_invite.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/mails/team_membership_invite_new_user.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/mails/team_membership_invite_new_user.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_edit.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_list.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_member_settings.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_members.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_members_accept.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_members_invite.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/templates/teams/team_new.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/test_api.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/teams/views.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/400.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/403.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/403_csrf.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/404.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/4xx_5xx_api.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/500.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/503.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/admin/change_form_object_tools.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/bugsink/counts.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/bugsink/csrf_debug.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/bugsink/login.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/bugsink/settings.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/robots.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/templates/signup.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static_src/.gitignore +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static_src/package.json +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static_src/postcss.config.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static_src/src/styles.css +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/static_src/tailwind.config.js +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templates/bare_base.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templates/barest_base.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templates/base.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templates/tailwind_forms/formfield.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/add_to_qs.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/code.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/issues.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/stricter_templates.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/tailwind_forms.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/user.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/templatetags/version.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/theme/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tools/is_tracked_by_tailwind.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/tools/strip-trailing-whitespace.sh +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/admin.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/apps.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/forms.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/management/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/management/commands/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/management/commands/send_welcome_email.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/migrations/0001_initial.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/migrations/0002_user_theme_preference.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/migrations/0003_user_language.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/migrations/0004_alter_user_language.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/migrations/__init__.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/models.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/tasks.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/mails/confirm_email.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/mails/confirm_email.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/mails/reset_password_email.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/mails/reset_password_email.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/mails/welcome_email.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/mails/welcome_email.txt +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/confirm_email.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/confirm_email_sent.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/logged_out.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/preferences.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/request_reset_password.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/resend_confirmation.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/reset_password.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/reset_password_email_sent.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/user_edit.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/templates/users/user_list.html +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/tests.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/urls.py +0 -0
- {bugsink-2.1.2 → bugsink-2.2.0}/users/views.py +0 -0
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2.2.0 (19 May 2026)
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
|
|
7
|
+
Fix: scope issue actions and event lookups to the authorized project/issue.
|
|
8
|
+
|
|
9
|
+
A project member who knew UUIDs from another project could use some issue-list
|
|
10
|
+
bulk actions and issue event views through a project or issue they were allowed
|
|
11
|
+
to access. These views now require the selected issues/events to belong to the
|
|
12
|
+
authorized parent. See:
|
|
13
|
+
|
|
14
|
+
https://github.com/bugsink/bugsink/security/advisories/GHSA-g5vc-q7qc-v939
|
|
15
|
+
https://github.com/bugsink/bugsink/security/advisories/GHSA-vx2f-6m6h-9frf
|
|
16
|
+
|
|
17
|
+
Fix: scope sourcemap and minidump debug-file metadata to projects.
|
|
18
|
+
|
|
19
|
+
Sourcemap and debug-file IDs are client-provided and were previously resolved
|
|
20
|
+
globally. That could let events in one project use uploaded debug metadata from
|
|
21
|
+
another project. Newly uploaded files now store project information and lookup
|
|
22
|
+
prefers project-scoped metadata. Already-uploaded legacy sourcemaps/debug files
|
|
23
|
+
keep working through a fallback. See:
|
|
24
|
+
|
|
25
|
+
https://github.com/bugsink/bugsink/security/advisories/GHSA-5389-f7vh-wxj8
|
|
26
|
+
|
|
27
|
+
### Smaller fixes
|
|
28
|
+
|
|
29
|
+
* Generate an `event_id` on `/store/` when the SDK does not send one, see #383.
|
|
30
|
+
* Refresh issue title fields on every event digest, see #378.
|
|
31
|
+
* Include ingest-dir cleanup in the `vacuum` command and warn about stale ingest-dir files, see 772fb1a9bff6 and
|
|
32
|
+
1ee34c574b7d.
|
|
33
|
+
* Add more verbose output to file vacuuming, see #372.
|
|
34
|
+
* Broaden phonehome triggers and avoid unnecessary queueing, see 2f76eacfbf68.
|
|
35
|
+
* Fix API catch-all logging for non-JSON bodies, see d13e5eff132b.
|
|
36
|
+
* Ensure `release` is a string before ingesting, see 374914c96f62.
|
|
37
|
+
* Fix direct minidump endpoint calls, see 5324d802cc50.
|
|
38
|
+
|
|
39
|
+
### Upgrading
|
|
40
|
+
|
|
41
|
+
Sourcemap uploads should include a meaningful project slug. Existing unscoped
|
|
42
|
+
sourcemaps keep working, but installations that prefer to remove that fallback
|
|
43
|
+
can run `delete_legacy_sourcemaps` and re-upload sourcemaps with project slugs.
|
|
44
|
+
|
|
45
|
+
## 2.1.3 (2 May 2026)
|
|
46
|
+
|
|
47
|
+
### Security
|
|
48
|
+
|
|
49
|
+
Fix: harden webhook URL validation parsing and reject non-RFC characters.
|
|
50
|
+
|
|
51
|
+
In some malformed URLs, Python’s standard URL parser (urllib) and the HTTP
|
|
52
|
+
client stack (requests / urllib3) do not agree on which host is actually being
|
|
53
|
+
targeted. That could allow a webhook URL to pass Bugsink’s outbound-host checks
|
|
54
|
+
while the actual HTTP request is sent somewhere else. See:
|
|
55
|
+
|
|
56
|
+
https://github.com/bugsink/bugsink/security/advisories/GHSA-fp53-qcf8-2xx2
|
|
57
|
+
|
|
58
|
+
### Smaller fixes
|
|
59
|
+
|
|
60
|
+
* Add issue-level markdown, see #334.
|
|
61
|
+
* Fix installation quota counting across projects, see #359.
|
|
62
|
+
* When vacuuming files, don't load them in memory, and allow long-running totals queries, see #363, #373 and #372.
|
|
63
|
+
* Refuse to send email as something@bugsink.com for self-hosters, see 3ff3a6fbeb6d.
|
|
64
|
+
* Fix `MultipleObjectsReturned` when user has unaccepted project memberships, see 653be6968f6e.
|
|
65
|
+
* Cleanup lingering files for `MAX_EVENT_SIZE` overshoots, see #370.
|
|
66
|
+
* Fix some `.get(context, {})` usages and an exception-path double-exception, see #369.
|
|
67
|
+
* Upgrade `gunicorn` requirement from `==25.1.*` to `==25.3.*`, see 2d5e0071cf66.
|
|
68
|
+
* Upgrade monofy, see #367.
|
|
69
|
+
|
|
3
70
|
## 2.1.2 (11 April 2026)
|
|
4
71
|
|
|
5
72
|
* Add stored file count and byte caps, see #355
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bugsink
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Self-hosted Error Tracking
|
|
5
5
|
Author-email: "Bugsink B.V." <info@bugsink.com>
|
|
6
6
|
Project-URL: homepage, https://www.bugsink.com/
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: gunicorn==
|
|
19
|
+
Requires-Dist: gunicorn==26.0.*
|
|
20
20
|
Requires-Dist: Django==5.2.*
|
|
21
21
|
Requires-Dist: verbose_csrf_middleware==5.2.*
|
|
22
22
|
Requires-Dist: sentry-sdk==2.*
|
|
@@ -29,8 +29,8 @@ Requires-Dist: inotify_simple==2.0.*
|
|
|
29
29
|
Requires-Dist: Brotli==1.2.*
|
|
30
30
|
Requires-Dist: python-dateutil==2.9.*
|
|
31
31
|
Requires-Dist: whitenoise==6.12.*
|
|
32
|
-
Requires-Dist: requests==2.
|
|
33
|
-
Requires-Dist: monofy==1.
|
|
32
|
+
Requires-Dist: requests==2.34.*
|
|
33
|
+
Requires-Dist: monofy==1.2.*
|
|
34
34
|
Requires-Dist: user-agents==2.2.*
|
|
35
35
|
Requires-Dist: fastjsonschema==2.21.*
|
|
36
36
|
Requires-Dist: ecma426>=0.2.0
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import ipaddress
|
|
2
2
|
import socket
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
from requests import RequestException
|
|
6
|
+
from urllib3.exceptions import LocationParseError
|
|
7
|
+
|
|
8
|
+
from urllib3.util import parse_url as parse_url_from_urllib3
|
|
4
9
|
|
|
5
10
|
from bugsink.app_settings import get_settings
|
|
6
11
|
|
|
7
12
|
|
|
13
|
+
_URL_ALLOWED_CHARACTERS = set(
|
|
14
|
+
"abcdefghijklmnopqrstuvwxyz" +
|
|
15
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + # ASCII letters
|
|
16
|
+
"0123456789" + # ASCII digits
|
|
17
|
+
"-._~" + # RFC 3986 unreserved marks
|
|
18
|
+
":/?#[]@" + # RFC 3986 gen-delims
|
|
19
|
+
"!$&'()*+,;=" + # RFC 3986 sub-delims
|
|
20
|
+
"%" # Percent-encoding marker
|
|
21
|
+
)
|
|
22
|
+
|
|
8
23
|
def _parse_hosts_and_networks(entries, setting_name):
|
|
9
24
|
hosts = set()
|
|
10
25
|
networks = []
|
|
@@ -41,14 +56,57 @@ def _match_entries(target_hostname, resolved_ips, hosts, networks):
|
|
|
41
56
|
return False
|
|
42
57
|
|
|
43
58
|
|
|
59
|
+
def _validate_raw_url_characters(webhook_url):
|
|
60
|
+
# There's no need to be user-friendly in accepting malformed URLs in code that is security-sensitive; we just want
|
|
61
|
+
# to be strict and reject anything that isn't a valid URL character. People will be able to set up their webhooks
|
|
62
|
+
# without the "friendlyness" of a browser's URL bar.
|
|
63
|
+
for char in webhook_url:
|
|
64
|
+
if char in _URL_ALLOWED_CHARACTERS:
|
|
65
|
+
continue
|
|
66
|
+
raise ValueError("Webhook URL must contain only ASCII URL characters.")
|
|
67
|
+
|
|
68
|
+
def _prepare_webhook_url(webhook_url):
|
|
69
|
+
try:
|
|
70
|
+
return requests.Request("POST", webhook_url).prepare().url
|
|
71
|
+
except RequestException as e:
|
|
72
|
+
raise ValueError("Webhook URL is malformed.") from e
|
|
73
|
+
|
|
74
|
+
|
|
44
75
|
def validate_webhook_url(webhook_url):
|
|
45
|
-
|
|
76
|
+
_validate_raw_url_characters(webhook_url)
|
|
77
|
+
|
|
78
|
+
# Both requests and urllib3 make some attempts to normalize malformed URLs. We must apply the
|
|
79
|
+
# same normalization before our own parsing and checks, to avoid discrepancies between what we
|
|
80
|
+
# check and what then actually happens.
|
|
81
|
+
prepared_webhook_url = _prepare_webhook_url(webhook_url)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# NOTE: requests uses urllib3's URL parsing logic, so we must use the same to ensure consistency; do not change
|
|
85
|
+
# this import to something else, doing so may have security implications if the URL parsing logic differs in how
|
|
86
|
+
# it normalizes or rejects certain inputs.
|
|
87
|
+
parsed = parse_url_from_urllib3(prepared_webhook_url)
|
|
88
|
+
except LocationParseError as e:
|
|
89
|
+
raise ValueError("Webhook URL is malformed.") from e
|
|
90
|
+
|
|
46
91
|
if parsed.scheme not in {"http", "https"}:
|
|
47
92
|
raise ValueError("Webhook URL must use http:// or https://.")
|
|
48
93
|
if parsed.hostname is None:
|
|
49
94
|
raise ValueError("Webhook URL must include a hostname.")
|
|
50
95
|
|
|
51
96
|
hostname = parsed.hostname.lower()
|
|
97
|
+
if hostname.startswith("[") and hostname.endswith("]"):
|
|
98
|
+
# In URL authority syntax, brackets are only used for IP literals. urllib3 keeps those brackets in `hostname`,
|
|
99
|
+
# but the rest of this function expects the plain host value so we must strip them back out.
|
|
100
|
+
inner_hostname = hostname[1:-1]
|
|
101
|
+
try:
|
|
102
|
+
# Rather than "just strip brackets if present", we verify that the inner value is actually a valid IP
|
|
103
|
+
# address; we don't want to "just generally strip stuff" in security-sentitive code.
|
|
104
|
+
ipaddress.ip_address(inner_hostname)
|
|
105
|
+
except ValueError:
|
|
106
|
+
pass
|
|
107
|
+
else:
|
|
108
|
+
hostname = inner_hostname
|
|
109
|
+
|
|
52
110
|
settings = get_settings()
|
|
53
111
|
mode = settings.ALERTS_WEBHOOK_OUTBOUND_MODE
|
|
54
112
|
|
|
@@ -63,9 +121,9 @@ def validate_webhook_url(webhook_url):
|
|
|
63
121
|
|
|
64
122
|
# Resolve on every send to defend against DNS changes after configuration time.
|
|
65
123
|
try:
|
|
66
|
-
resolved_ips = {ipaddress.ip_address(ip) for ip in _resolve_ip_addresses(
|
|
124
|
+
resolved_ips = {ipaddress.ip_address(ip) for ip in _resolve_ip_addresses(hostname, port)}
|
|
67
125
|
except OSError as e:
|
|
68
|
-
raise ValueError(f"Webhook hostname could not be resolved: {
|
|
126
|
+
raise ValueError(f"Webhook hostname could not be resolved: {hostname}") from e
|
|
69
127
|
|
|
70
128
|
allow_match = _match_entries(hostname, resolved_ips, allow_hosts, allow_networks)
|
|
71
129
|
deny_match = _match_entries(hostname, resolved_ips, deny_hosts, deny_networks)
|
|
@@ -527,6 +527,27 @@ class TestWebhookSecurityValidation(DjangoTestCase):
|
|
|
527
527
|
mock_resolve.return_value = {"93.184.216.34"}
|
|
528
528
|
validate_webhook_url("https://hooks.example.com/webhook")
|
|
529
529
|
|
|
530
|
+
@patch("alerts.service_backends.webhook_security._validate_raw_url_characters")
|
|
531
|
+
def test_rejects_backslash_exploit_that_tries_to_bypass_allowlist(self, mock_validate_raw_characters):
|
|
532
|
+
# We can't think of an exploit that would exploit discrepancies between requests' parser and other parsers but
|
|
533
|
+
# not already be caught by our raw character validation. Hence we need to turn off raw character validation in
|
|
534
|
+
# the test to test against the case of mismatch-exploiting that we do know to exist.
|
|
535
|
+
mock_validate_raw_characters.return_value = None
|
|
536
|
+
|
|
537
|
+
with override_bugsink_settings(
|
|
538
|
+
ALERTS_WEBHOOK_OUTBOUND_MODE="allowlist_only",
|
|
539
|
+
ALERTS_WEBHOOK_ALLOW_LIST=["whitelist.com"]):
|
|
540
|
+
with self.assertRaisesRegex(ValueError, "not allowlisted"):
|
|
541
|
+
validate_webhook_url(r"http://127.0.0.1:6666\@whitelist.com")
|
|
542
|
+
|
|
543
|
+
def test_rejects_raw_unicode_character(self):
|
|
544
|
+
with self.assertRaisesRegex(ValueError, "must contain only ASCII URL characters"):
|
|
545
|
+
validate_webhook_url("https://hooks.example.com/caf\xe9")
|
|
546
|
+
|
|
547
|
+
def test_rejects_raw_backslash_character(self):
|
|
548
|
+
with self.assertRaisesRegex(ValueError, "must contain only ASCII URL characters"):
|
|
549
|
+
validate_webhook_url(r"https://hooks.example.com\path")
|
|
550
|
+
|
|
530
551
|
@patch("alerts.service_backends.webhook_security._resolve_ip_addresses")
|
|
531
552
|
def test_denied_when_allow_and_deny_both_match(self, mock_resolve):
|
|
532
553
|
mock_resolve.return_value = {"93.184.216.34"}
|
|
@@ -5,6 +5,7 @@ from django.core.checks import Warning, register
|
|
|
5
5
|
from django.conf import settings
|
|
6
6
|
|
|
7
7
|
from bugsink.app_settings import get_settings
|
|
8
|
+
from bugsink.utils import sends_email_as_bugsink_but_is_not_hosted_on_bugsink
|
|
8
9
|
from events.storage_registry import get_write_storage
|
|
9
10
|
from files.storage_registry import get_write_storage as get_object_write_storage
|
|
10
11
|
|
|
@@ -91,3 +92,23 @@ def check_proxy_env_vars_consistency(app_configs, **kwargs):
|
|
|
91
92
|
)]
|
|
92
93
|
|
|
93
94
|
return []
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@register("bsmain")
|
|
98
|
+
def check_email_sender_domain(app_configs, **kwargs):
|
|
99
|
+
errors = []
|
|
100
|
+
|
|
101
|
+
for setting_name in ["DEFAULT_FROM_EMAIL", "SERVER_EMAIL"]:
|
|
102
|
+
if sends_email_as_bugsink_but_is_not_hosted_on_bugsink(
|
|
103
|
+
getattr(settings, setting_name),
|
|
104
|
+
settings.ALLOWED_HOSTS,
|
|
105
|
+
):
|
|
106
|
+
errors.append(Warning(
|
|
107
|
+
f"{setting_name} uses the bugsink.com domain, but ALLOWED_HOSTS does not. This looks like a "
|
|
108
|
+
f"self-hosted Bugsink sending email as bugsink.com. That is effectively spam, deliverability will "
|
|
109
|
+
f"suffer badly, and those messages show up in Bugsink's DKIM reports. Configure your own sender "
|
|
110
|
+
f"address instead.",
|
|
111
|
+
id="bsmain.W006",
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
return errors
|
|
@@ -34,7 +34,7 @@ class Command(BaseCommand):
|
|
|
34
34
|
def handle(self, *args, **options):
|
|
35
35
|
self._create_superuser_if_needed()
|
|
36
36
|
|
|
37
|
-
# Similar considerations apply here as those which are documented in
|
|
37
|
+
# Similar considerations apply here as those which are documented in phonehome.utils.phone_home().
|
|
38
38
|
# By putting this in prestart, we add one more location to the list of kick-off locations; with the added
|
|
39
39
|
# benefit that this particular location also gives some signal for (Docker) installations that are prematurely
|
|
40
40
|
# aborted (i.e. we get a ping even if 'home' is never even reached).
|
|
@@ -6,6 +6,7 @@ from django.utils import timezone
|
|
|
6
6
|
from bugsink.app_settings import get_settings
|
|
7
7
|
from events.tasks import delete_events_older_than_sync
|
|
8
8
|
from files.tasks import vacuum_files_sync
|
|
9
|
+
from ingest.management.commands.vacuum_ingest_dir import vacuum_ingest_dir_sync
|
|
9
10
|
from tags.tasks import vacuum_eventless_issuetags_sync, vacuum_tags_sync
|
|
10
11
|
|
|
11
12
|
|
|
@@ -33,6 +34,11 @@ class Command(BaseCommand):
|
|
|
33
34
|
action='store_true',
|
|
34
35
|
help="Delete events older than the configured maximum age.",
|
|
35
36
|
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
'--ingest-dir',
|
|
39
|
+
action='store_true',
|
|
40
|
+
help="Clean up stale files from the ingest directory.",
|
|
41
|
+
)
|
|
36
42
|
parser.add_argument(
|
|
37
43
|
'--chunk-max-days',
|
|
38
44
|
type=int,
|
|
@@ -60,30 +66,41 @@ class Command(BaseCommand):
|
|
|
60
66
|
type=int,
|
|
61
67
|
help="Keep at most this many bytes across stored File objects. Defaults to MAX_STORED_FILE_BYTES.",
|
|
62
68
|
)
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
'--ingest-max-days',
|
|
71
|
+
type=int,
|
|
72
|
+
default=7,
|
|
73
|
+
help="Delete ingest-dir files older than this many days (default: 7).",
|
|
74
|
+
)
|
|
63
75
|
|
|
64
76
|
def handle(self, *args, **options):
|
|
65
77
|
run_files = options['files']
|
|
66
78
|
run_tags = options['tags']
|
|
67
79
|
run_eventless_issuetags = options['eventless_issuetags']
|
|
68
80
|
run_old_events = options['old_events']
|
|
81
|
+
run_ingest_dir = options['ingest_dir']
|
|
69
82
|
|
|
70
|
-
if not any([run_files, run_tags, run_eventless_issuetags, run_old_events]):
|
|
83
|
+
if not any([run_files, run_tags, run_eventless_issuetags, run_old_events, run_ingest_dir]):
|
|
71
84
|
# If no specific options were provided, run all vacuum tasks by default.
|
|
72
85
|
run_files = True
|
|
73
86
|
run_tags = True
|
|
74
87
|
run_eventless_issuetags = True
|
|
75
88
|
run_old_events = True
|
|
89
|
+
run_ingest_dir = True
|
|
76
90
|
|
|
77
91
|
if run_files:
|
|
78
92
|
settings = get_settings()
|
|
79
93
|
max_file_count = options['max_file_count']
|
|
80
94
|
max_file_bytes = options['max_file_bytes']
|
|
81
95
|
self.stdout.write("Vacuuming files...")
|
|
96
|
+
|
|
97
|
+
log_progress = self.stdout.write if options["verbosity"] >= 2 else lambda _message: None
|
|
82
98
|
vacuum_files_sync(
|
|
83
99
|
chunk_max_days=options['chunk_max_days'],
|
|
84
100
|
file_max_days=options['file_max_days'],
|
|
85
101
|
max_file_count=settings.MAX_STORED_FILE_COUNT if max_file_count is None else max_file_count,
|
|
86
102
|
max_file_bytes=settings.MAX_STORED_FILE_BYTES if max_file_bytes is None else max_file_bytes,
|
|
103
|
+
log_progress=log_progress,
|
|
87
104
|
)
|
|
88
105
|
|
|
89
106
|
if run_eventless_issuetags:
|
|
@@ -107,4 +124,8 @@ class Command(BaseCommand):
|
|
|
107
124
|
cutoff=timezone.now() - timedelta(days=days),
|
|
108
125
|
)
|
|
109
126
|
|
|
127
|
+
if run_ingest_dir:
|
|
128
|
+
self.stdout.write("Vacuuming ingest dir...")
|
|
129
|
+
vacuum_ingest_dir_sync(days=options["ingest_max_days"], stdout=self.stdout, stderr=self.stderr)
|
|
130
|
+
|
|
110
131
|
self.stdout.write(self.style.SUCCESS("Vacuum complete."))
|
|
@@ -217,8 +217,9 @@ class VacuumFilesBatchTestCase(TransactionTestCase):
|
|
|
217
217
|
File.objects.filter(id=recent.id).update(accessed_at=now - timedelta(days=2))
|
|
218
218
|
File.objects.filter(id=newest.id).update(accessed_at=now - timedelta(days=1))
|
|
219
219
|
|
|
220
|
-
has_more_work = vacuum_files_batch(file_max_days=90, max_file_count=1)
|
|
220
|
+
has_more_work, num_deleted = vacuum_files_batch(file_max_days=90, max_file_count=1)
|
|
221
221
|
|
|
222
222
|
self.assertFalse(has_more_work)
|
|
223
|
+
self.assertEqual(3, num_deleted)
|
|
223
224
|
self.assertEqual([newest.id], list(File.objects.values_list("id", flat=True)))
|
|
224
225
|
self.assertEqual([newest.id], list(FileMetadata.objects.values_list("file_id", flat=True)))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from django.core.checks import run_checks
|
|
2
|
+
from django.test import SimpleTestCase, override_settings
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SystemChecksTestCase(SimpleTestCase):
|
|
6
|
+
def _warnings(self):
|
|
7
|
+
return [warning for warning in run_checks(tags=["bsmain"]) if warning.id == "bsmain.W006"]
|
|
8
|
+
|
|
9
|
+
@override_settings(DEFAULT_FROM_EMAIL="Bugsink <bugsink@example.org>", SERVER_EMAIL="server@example.org")
|
|
10
|
+
def test_email_sender_domain_check_allows_non_bugsink_domain(self):
|
|
11
|
+
self.assertEqual([], self._warnings())
|
|
12
|
+
|
|
13
|
+
@override_settings(DEFAULT_FROM_EMAIL="Bugsink <alerts@bugsink.com>", SERVER_EMAIL="server@example.org")
|
|
14
|
+
def test_email_sender_domain_check_warns_for_default_from_email(self):
|
|
15
|
+
warnings = self._warnings()
|
|
16
|
+
|
|
17
|
+
self.assertEqual(1, len(warnings))
|
|
18
|
+
self.assertEqual(
|
|
19
|
+
"DEFAULT_FROM_EMAIL uses the bugsink.com domain, but ALLOWED_HOSTS does not. This looks like a "
|
|
20
|
+
"self-hosted Bugsink sending email as bugsink.com. That is effectively spam, deliverability will "
|
|
21
|
+
"suffer badly, and those messages show up in Bugsink's DKIM reports. Configure your own sender "
|
|
22
|
+
"address instead.",
|
|
23
|
+
warnings[0].msg,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
@override_settings(DEFAULT_FROM_EMAIL="Bugsink <bugsink@example.org>", SERVER_EMAIL="server@bugsink.com")
|
|
27
|
+
def test_email_sender_domain_check_warns_for_server_email(self):
|
|
28
|
+
warnings = self._warnings()
|
|
29
|
+
|
|
30
|
+
self.assertEqual(1, len(warnings))
|
|
31
|
+
self.assertEqual(
|
|
32
|
+
"SERVER_EMAIL uses the bugsink.com domain, but ALLOWED_HOSTS does not. This looks like a "
|
|
33
|
+
"self-hosted Bugsink sending email as bugsink.com. That is effectively spam, deliverability will "
|
|
34
|
+
"suffer badly, and those messages show up in Bugsink's DKIM reports. Configure your own sender "
|
|
35
|
+
"address instead.",
|
|
36
|
+
warnings[0].msg,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@override_settings(
|
|
40
|
+
DEFAULT_FROM_EMAIL="alerts@bugsink.com",
|
|
41
|
+
SERVER_EMAIL="server@example.org",
|
|
42
|
+
ALLOWED_HOSTS=["selfhosted.bugsink.com"],
|
|
43
|
+
)
|
|
44
|
+
def test_email_sender_domain_check_allows_bugsink_domain_when_allowed_hosts_match(self):
|
|
45
|
+
self.assertEqual([], self._warnings())
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gdac28a708'
|
|
@@ -52,7 +52,7 @@ EMAIL_BACKEND = "bugsink.email_backends.QuietConsoleEmailBackend" # instead of
|
|
|
52
52
|
# EMAIL_PORT = ...
|
|
53
53
|
# EMAIL_USE_TLS = ...
|
|
54
54
|
# EMAIL_USE_SSL = ...
|
|
55
|
-
# SERVER_EMAIL = DEFAULT_FROM_EMAIL = "Bugsink <bugsink@example.org>"
|
|
55
|
+
# SERVER_EMAIL = DEFAULT_FROM_EMAIL = "Bugsink <bugsink@example.org>" # don't use ...@bugsink.com if you're not bugsink
|
|
56
56
|
|
|
57
57
|
# constants for "create by" (user/team/project) settings
|
|
58
58
|
CB_ANYBODY = "CB_ANYBODY"
|
|
@@ -77,8 +77,7 @@ EMAIL_BACKEND = "bugsink.email_backends.QuietConsoleEmailBackend" # instead of
|
|
|
77
77
|
# Uncomment the line below to show all sent emails in the logs
|
|
78
78
|
# LOGGING['loggers']['bugsink.email']['level'] = "INFO"
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
SERVER_EMAIL = DEFAULT_FROM_EMAIL = "Bugsink <bugsink@example.org>"
|
|
80
|
+
SERVER_EMAIL = DEFAULT_FROM_EMAIL = "Bugsink <bugsink@example.org>" # don't use ...@bugsink.com if you're not bugsink
|
|
82
81
|
|
|
83
82
|
# constants for "create by" (user/team/project) settings
|
|
84
83
|
CB_ANYBODY = "CB_ANYBODY"
|
|
@@ -79,7 +79,7 @@ EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
|
|
|
79
79
|
EMAIL_PORT = 587
|
|
80
80
|
EMAIL_USE_TLS = True
|
|
81
81
|
|
|
82
|
-
SERVER_EMAIL = DEFAULT_FROM_EMAIL = '
|
|
82
|
+
SERVER_EMAIL = DEFAULT_FROM_EMAIL = 'Bugsink Development Server <bugsink@example.org>'
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
BUGSINK = {
|
|
@@ -4,9 +4,12 @@ import re
|
|
|
4
4
|
import brotli
|
|
5
5
|
|
|
6
6
|
from unittest import TestCase as RegularTestCase
|
|
7
|
+
from unittest.mock import patch
|
|
7
8
|
from django.test import TestCase as DjangoTestCase
|
|
9
|
+
from django.test import SimpleTestCase
|
|
8
10
|
from django.test import override_settings
|
|
9
11
|
from django.core.exceptions import SuspiciousOperation
|
|
12
|
+
from django.core import mail
|
|
10
13
|
from django.contrib.auth import get_user_model
|
|
11
14
|
from django.test.utils import CaptureQueriesContext
|
|
12
15
|
from django.db import connection
|
|
@@ -15,6 +18,7 @@ from .wsgi import allowed_hosts_error_message
|
|
|
15
18
|
from .test_utils import TransactionTestCase25251 as TransactionTestCase
|
|
16
19
|
from .transaction import immediate_atomic
|
|
17
20
|
from .volume_based_condition import VolumeBasedCondition
|
|
21
|
+
from .utils import send_rendered_email
|
|
18
22
|
from .streams import (
|
|
19
23
|
compress_with_zlib, GeneratorReader, WBITS_PARAM_FOR_GZIP, WBITS_PARAM_FOR_DEFLATE, MaxDataReader,
|
|
20
24
|
MaxDataWriter, zlib_generator, brotli_generator, BrotliError)
|
|
@@ -38,6 +42,37 @@ class VolumeBasedConditionTestCase(RegularTestCase):
|
|
|
38
42
|
self.assertEqual(vbc, vbc2)
|
|
39
43
|
|
|
40
44
|
|
|
45
|
+
@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
|
|
46
|
+
class SendRenderedEmailTestCase(SimpleTestCase):
|
|
47
|
+
def setUp(self):
|
|
48
|
+
mail.outbox = []
|
|
49
|
+
|
|
50
|
+
@override_settings(DEFAULT_FROM_EMAIL="Bugsink <alerts@bugsink.com>", ALLOWED_HOSTS=["selfhosted.example.com"])
|
|
51
|
+
def test_send_rendered_email_refuses_self_hosted_bugsink_sender_domain(self):
|
|
52
|
+
with self.assertLogs("bugsink.email", level="ERROR") as logs:
|
|
53
|
+
send_rendered_email(
|
|
54
|
+
"Subject",
|
|
55
|
+
"mails/welcome_email",
|
|
56
|
+
["user@example.com"],
|
|
57
|
+
{"reason": "Reason", "reset_url": "https://example.com/reset"},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
self.assertEqual([], mail.outbox)
|
|
61
|
+
self.assertIn("Refusing to send email with subject 'Subject'", logs.output[0])
|
|
62
|
+
|
|
63
|
+
@override_settings(DEFAULT_FROM_EMAIL="Bugsink <alerts@bugsink.com>", ALLOWED_HOSTS=["tenant.bugsink.com"])
|
|
64
|
+
@patch("phonehome.models.Installation.check_and_inc_email_quota", return_value=True)
|
|
65
|
+
def test_send_rendered_email_allows_bugsink_sender_domain_when_hosted_on_bugsink_domain(self, _quota_ok):
|
|
66
|
+
send_rendered_email(
|
|
67
|
+
"Subject",
|
|
68
|
+
"mails/welcome_email",
|
|
69
|
+
["user@example.com"],
|
|
70
|
+
{"reason": "Reason", "reset_url": "https://example.com/reset"},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
self.assertEqual(1, len(mail.outbox))
|
|
74
|
+
|
|
75
|
+
|
|
41
76
|
class StreamsTestCase(RegularTestCase):
|
|
42
77
|
|
|
43
78
|
def test_compress_decompress_gzip(self):
|
|
@@ -5,6 +5,7 @@ from collections import defaultdict
|
|
|
5
5
|
from django.utils import timezone
|
|
6
6
|
from django.utils.http import url_has_allowed_host_and_scheme
|
|
7
7
|
from django.core.mail import EmailMultiAlternatives
|
|
8
|
+
from django.conf import settings
|
|
8
9
|
from django.template.loader import get_template
|
|
9
10
|
from django.apps import apps
|
|
10
11
|
from django.db.models import ForeignKey, F
|
|
@@ -33,6 +34,20 @@ def is_safe_next_url(url, request):
|
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def sends_email_as_bugsink_but_is_not_hosted_on_bugsink(from_email, allowed_hosts):
|
|
38
|
+
if from_email is None:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
normalized_from_email = str(from_email).strip().lower()
|
|
42
|
+
if not (
|
|
43
|
+
normalized_from_email.endswith("@bugsink.com") or
|
|
44
|
+
normalized_from_email.endswith("@bugsink.com>")
|
|
45
|
+
):
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
return not any(str(host).strip().lower().lstrip(".").endswith("bugsink.com") for host in allowed_hosts)
|
|
49
|
+
|
|
50
|
+
|
|
36
51
|
def send_rendered_email(subject, base_template_name, recipient_list, context=None):
|
|
37
52
|
from phonehome.models import Installation
|
|
38
53
|
|
|
@@ -42,6 +57,17 @@ def send_rendered_email(subject, base_template_name, recipient_list, context=Non
|
|
|
42
57
|
if not recipient_list:
|
|
43
58
|
return
|
|
44
59
|
|
|
60
|
+
if sends_email_as_bugsink_but_is_not_hosted_on_bugsink(settings.DEFAULT_FROM_EMAIL, settings.ALLOWED_HOSTS):
|
|
61
|
+
logger.error(
|
|
62
|
+
"Refusing to send email with subject '%s' to %s because DEFAULT_FROM_EMAIL=%r uses bugsink.com while "
|
|
63
|
+
"ALLOWED_HOSTS=%r does not. This looks like a self-hosted Bugsink trying to send as bugsink.com.",
|
|
64
|
+
subject,
|
|
65
|
+
recipient_list,
|
|
66
|
+
settings.DEFAULT_FROM_EMAIL,
|
|
67
|
+
settings.ALLOWED_HOSTS,
|
|
68
|
+
)
|
|
69
|
+
return
|
|
70
|
+
|
|
45
71
|
if not Installation.check_and_inc_email_quota(timezone.now()):
|
|
46
72
|
logger.warning(
|
|
47
73
|
"Email quota exceeded; not sending email with subject '%s' to %s",
|
|
@@ -31,7 +31,7 @@ from bugsink.decorators import atomic_for_request_method
|
|
|
31
31
|
from bugsink.timed_sqlite_backend.base import different_runtime_limit
|
|
32
32
|
from bugsink.utils import is_safe_next_url
|
|
33
33
|
|
|
34
|
-
from phonehome.
|
|
34
|
+
from phonehome.utils import phone_home
|
|
35
35
|
from phonehome.models import Installation
|
|
36
36
|
|
|
37
37
|
from ingest.views import BaseIngestAPIView
|
|
@@ -58,31 +58,12 @@ debug.technical_404_response = cors_for_api_view(debug.technical_404_response)
|
|
|
58
58
|
debug.technical_500_response = cors_for_api_view(debug.technical_500_response)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
# I need a way to cron-like run tasks that works for the setup with and without snappea. With snappea it's straight-
|
|
63
|
-
# forward (though not part of snappea _yet_). Without snappea, you'd need _some_ location to do a "poor man's cron"
|
|
64
|
-
# check. Server-start would be the first thing to consider, but how to do this across gunicorn, debugserver, and
|
|
65
|
-
# possibly even non-standard (for Bugsink) wsgi servers? Better go the "just pick some request to do the check"
|
|
66
|
-
# route. I've picked "home", because [a] it's assumed to be somewhat regularly visited [b] there's no transaction
|
|
67
|
-
# logic in it, which leaves space for transaction-logic in the phone-home task itself and [c] some alternatives are
|
|
68
|
-
# a no-go (ingestion: on a tight budget; login: not visited when a long-lived session is active).
|
|
69
|
-
#
|
|
70
|
-
# having chosen the solution for the non-snappea case, I got the crazy idea of using it for the snappea case too,
|
|
71
|
-
# i.e. just put a .delay() here and let the config choose. Not so crazy though, because [a] saves us from new
|
|
72
|
-
# features in snappea, [b] we introduce a certain symmetry of measurement between the 2 setups, i.e. the choice of
|
|
73
|
-
# lazyness does not influence counting and [c] do I really want to get pings for sites where nobody visits home()?
|
|
74
|
-
|
|
75
|
-
# NOTE: each time this function is called, it will schedule a new task, even when the task would quickly return
|
|
76
|
-
# (nothing due, or configured to never send). We _could_ improve that, but doesn't seem performance-critical enough.
|
|
77
|
-
send_if_due.delay() # _phone_home() wrapper serves as a place for the comment above
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
@phone_home
|
|
80
62
|
def home(request):
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if request.user.project_set.filter(projectmembership__accepted=True).distinct().count() == 1:
|
|
63
|
+
accepted_projects = request.user.project_set.filter(projectmembership__accepted=True).distinct()
|
|
64
|
+
if accepted_projects.count() == 1:
|
|
84
65
|
# if the user has exactly one project, we redirect them to that project
|
|
85
|
-
project =
|
|
66
|
+
project = accepted_projects.get()
|
|
86
67
|
return redirect("issue_list_open", project_pk=project.id)
|
|
87
68
|
|
|
88
69
|
if request.user.project_set.all().distinct().count() > 0:
|
|
@@ -94,7 +94,7 @@ class CustomWSGIRequest(WSGIRequest):
|
|
|
94
94
|
We're leaking a bit of information here, but I don't think it's too much TBH -- especially in the light of ssl
|
|
95
95
|
certificates being specifically tied to the domain name.
|
|
96
96
|
"""
|
|
97
|
-
if self.path.startswith
|
|
97
|
+
if self.path.startswith("/health/"):
|
|
98
98
|
# For /health/ endpoints, we skip the ALLOWED_HOSTS validation (see #140).
|
|
99
99
|
return self._get_raw_host()
|
|
100
100
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bugsink
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Self-hosted Error Tracking
|
|
5
5
|
Author-email: "Bugsink B.V." <info@bugsink.com>
|
|
6
6
|
Project-URL: homepage, https://www.bugsink.com/
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: gunicorn==
|
|
19
|
+
Requires-Dist: gunicorn==26.0.*
|
|
20
20
|
Requires-Dist: Django==5.2.*
|
|
21
21
|
Requires-Dist: verbose_csrf_middleware==5.2.*
|
|
22
22
|
Requires-Dist: sentry-sdk==2.*
|
|
@@ -29,8 +29,8 @@ Requires-Dist: inotify_simple==2.0.*
|
|
|
29
29
|
Requires-Dist: Brotli==1.2.*
|
|
30
30
|
Requires-Dist: python-dateutil==2.9.*
|
|
31
31
|
Requires-Dist: whitenoise==6.12.*
|
|
32
|
-
Requires-Dist: requests==2.
|
|
33
|
-
Requires-Dist: monofy==1.
|
|
32
|
+
Requires-Dist: requests==2.34.*
|
|
33
|
+
Requires-Dist: monofy==1.2.*
|
|
34
34
|
Requires-Dist: user-agents==2.2.*
|
|
35
35
|
Requires-Dist: fastjsonschema==2.21.*
|
|
36
36
|
Requires-Dist: ecma426>=0.2.0
|