trueppm-api 0.4.0b2__py3-none-any.whl
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.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""DRF authentication class for project-scoped API tokens (ADR-0068)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import logging
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from django.contrib.auth.models import AnonymousUser
|
|
10
|
+
from django.db.models import Q
|
|
11
|
+
from django.utils import timezone
|
|
12
|
+
from drf_spectacular.extensions import OpenApiAuthenticationExtension
|
|
13
|
+
from rest_framework import exceptions
|
|
14
|
+
from rest_framework.authentication import BaseAuthentication, get_authorization_header
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from rest_framework.request import Request
|
|
18
|
+
|
|
19
|
+
from trueppm_api.apps.projects.models import ProjectApiToken
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
TOKEN_PREFIX = "tppm_"
|
|
25
|
+
TOKEN_RAW_HEX_LEN = 64 # SHA-256 hex digest length, also = raw token hex length
|
|
26
|
+
TOKEN_TOTAL_LEN = len(TOKEN_PREFIX) + TOKEN_RAW_HEX_LEN # 69
|
|
27
|
+
|
|
28
|
+
# Deliberately generic 401 details — prevent enumeration of token states.
|
|
29
|
+
_INVALID_TOKEN_HEADER_DETAIL = "Invalid token header."
|
|
30
|
+
_INVALID_TOKEN_DETAIL = "Invalid token."
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sha256_hex(raw: str) -> str:
|
|
34
|
+
"""Return the SHA-256 hex digest of the raw token (no salt; 256-bit entropy).
|
|
35
|
+
|
|
36
|
+
Encodes as UTF-8 rather than ASCII so a non-ASCII ``raw`` (e.g. a hand-crafted
|
|
37
|
+
``/api/v1/share/<token>/`` path segment) hashes to a non-matching digest and
|
|
38
|
+
resolves as "unknown token" (→ 404 / 401) instead of raising an unhandled
|
|
39
|
+
``UnicodeEncodeError`` → 500 (#2126). Minted tokens are always URL-safe ASCII
|
|
40
|
+
(``secrets.token_urlsafe``), for which UTF-8 and ASCII produce identical bytes,
|
|
41
|
+
so every existing ``token_hash`` is unchanged — this only stops the crash.
|
|
42
|
+
"""
|
|
43
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProjectApiTokenAuthentication(BaseAuthentication):
|
|
47
|
+
"""Authenticate a request via ``Authorization: Bearer tppm_<64-hex>``.
|
|
48
|
+
|
|
49
|
+
On success returns ``(token.created_by, token)`` so DRF sets ``request.user``
|
|
50
|
+
to the token's creator (history rows attribute to a real user) and
|
|
51
|
+
``request.auth`` to the token itself (the throttle and view use it for the
|
|
52
|
+
project_id and the backfill window).
|
|
53
|
+
|
|
54
|
+
On any failure returns a generic 401 with no body detail — prevents
|
|
55
|
+
enumeration of valid token prefixes or revocation states.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
keyword = "Bearer"
|
|
59
|
+
|
|
60
|
+
def authenticate(self, request: Request) -> tuple[object, ProjectApiToken] | None:
|
|
61
|
+
# Import here so the module can be imported during Django settings load
|
|
62
|
+
# (the apps registry is not yet ready at import time).
|
|
63
|
+
from trueppm_api.apps.projects.models import ProjectApiToken
|
|
64
|
+
|
|
65
|
+
auth = get_authorization_header(request).split()
|
|
66
|
+
if not auth or auth[0].lower() != self.keyword.lower().encode("ascii"):
|
|
67
|
+
return None # let other authenticators try (JWT, Session)
|
|
68
|
+
if len(auth) == 1:
|
|
69
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_HEADER_DETAIL)
|
|
70
|
+
if len(auth) > 2:
|
|
71
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_HEADER_DETAIL)
|
|
72
|
+
try:
|
|
73
|
+
raw_token = auth[1].decode("ascii")
|
|
74
|
+
except UnicodeDecodeError as exc:
|
|
75
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_HEADER_DETAIL) from exc
|
|
76
|
+
|
|
77
|
+
# A ``Bearer`` value that is not one of our ``tppm_``-prefixed tokens is
|
|
78
|
+
# almost certainly a JWT (simplejwt also uses ``Bearer``). Defer to the
|
|
79
|
+
# next authenticator instead of raising, so token auth and JWT auth can
|
|
80
|
+
# coexist on the same view when a read viewset additively lists both.
|
|
81
|
+
# This is required for the read-only MCP scaffold (ADR-0186): the MCP
|
|
82
|
+
# viewsets accept *either* a human JWT session *or* an ``mcp:read`` token.
|
|
83
|
+
if not raw_token.startswith(TOKEN_PREFIX):
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
# Cheap structural validation before any DB lookup. Avoids exposing the
|
|
87
|
+
# DB to malformed input and makes the timing of a malformed ``tppm_``
|
|
88
|
+
# token indistinguishable from "valid prefix, unknown hash" (both return
|
|
89
|
+
# 401 with no body), preventing enumeration of valid token prefixes.
|
|
90
|
+
if len(raw_token) != TOKEN_TOTAL_LEN:
|
|
91
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_DETAIL)
|
|
92
|
+
|
|
93
|
+
token_body = raw_token[len(TOKEN_PREFIX) :]
|
|
94
|
+
try:
|
|
95
|
+
int(token_body, 16) # confirm it parses as hex; rejects arbitrary input
|
|
96
|
+
except ValueError as exc:
|
|
97
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_DETAIL) from exc
|
|
98
|
+
|
|
99
|
+
# Expiry filter (ADR-0214): a Personal Access Token past its ``expires_at``
|
|
100
|
+
# is treated as if it did not exist — no row, generic 401, no enumeration
|
|
101
|
+
# signal. Applies uniformly: project/program tokens leave ``expires_at``
|
|
102
|
+
# null and match the ``isnull`` branch, so they are unaffected. Folding
|
|
103
|
+
# expiry into the same indexed hash lookup keeps the hot path a single
|
|
104
|
+
# query and preserves the timing-safe "no match → no row" property (no
|
|
105
|
+
# Python-side string compare on secrets is introduced).
|
|
106
|
+
token = (
|
|
107
|
+
ProjectApiToken.objects.filter(
|
|
108
|
+
Q(expires_at__isnull=True) | Q(expires_at__gt=timezone.now()),
|
|
109
|
+
token_hash=sha256_hex(raw_token),
|
|
110
|
+
revoked_at__isnull=True,
|
|
111
|
+
is_deleted=False,
|
|
112
|
+
)
|
|
113
|
+
.select_related("project", "created_by", "owner")
|
|
114
|
+
.first()
|
|
115
|
+
)
|
|
116
|
+
if token is None:
|
|
117
|
+
# Identity refusal (ADR-0112 RC1): a well-formed token that did not resolve.
|
|
118
|
+
# Audit it *only* when the hash matches a real-but-dead token (revoked,
|
|
119
|
+
# expired, or soft-deleted) — that is a real credential being misused and is
|
|
120
|
+
# bounded by the number of tokens ever minted. An unknown hash is someone
|
|
121
|
+
# guessing; auditing those would be an unbounded, chain-locking DoS amplifier,
|
|
122
|
+
# so it is deliberately not recorded.
|
|
123
|
+
self._audit_identity_refusal(request, raw_token)
|
|
124
|
+
self._mark_identity_refusal(request)
|
|
125
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_DETAIL)
|
|
126
|
+
|
|
127
|
+
# request.user resolution (ADR-0214):
|
|
128
|
+
# - Personal Access Token → the ``owner`` (the acting user). Because
|
|
129
|
+
# request.user becomes the owner, ALL downstream DRF object-level RBAC
|
|
130
|
+
# applies exactly as that user's own session — a PAT is not a superuser
|
|
131
|
+
# credential, it sees only what its owner sees.
|
|
132
|
+
# - Project/program token → the ``created_by`` minter (owner is null), so
|
|
133
|
+
# django-simple-history attributes task mutations back to the human who
|
|
134
|
+
# minted the integration.
|
|
135
|
+
# - Neither present (a project/program token whose SET_NULL minter was
|
|
136
|
+
# deleted) → AnonymousUser.
|
|
137
|
+
resolved = token.owner or token.created_by
|
|
138
|
+
|
|
139
|
+
# Disabled-account handling (#2832). The invariant: a credential that derives
|
|
140
|
+
# its authority from a human must stop working when that human's account is
|
|
141
|
+
# disabled. Every other authenticator in DEFAULT_AUTHENTICATION_CLASSES already
|
|
142
|
+
# held it — simplejwt rejects an inactive user by default and DRF's
|
|
143
|
+
# SessionAuthentication checks ``user.is_active`` — while this class had no
|
|
144
|
+
# ``is_active`` reference at all, so off-boarding a member killed their sessions
|
|
145
|
+
# and JWTs and left every Personal Access Token they had minted live, bearing
|
|
146
|
+
# their full pre-departure authority. ``IsAuthenticated`` cannot close that:
|
|
147
|
+
# ``User.is_authenticated`` is unconditionally True.
|
|
148
|
+
#
|
|
149
|
+
# This is defense in depth, not the primary control — the workspace off-boarding
|
|
150
|
+
# paths revoke the tokens outright (apps/workspace/views.py). It catches accounts
|
|
151
|
+
# disabled by some *other* path (Django admin, a management command, a future
|
|
152
|
+
# SSO/SCIM deprovision), which never touch ``revoked_at``.
|
|
153
|
+
#
|
|
154
|
+
# The guard is scoped to ``is_personal`` on purpose, and the scope is the whole
|
|
155
|
+
# design decision — read this before widening it:
|
|
156
|
+
#
|
|
157
|
+
# * A **personal** token IS the human's credential. It exists to act as them
|
|
158
|
+
# and inherits their permissions wholesale, so disabling the account has to
|
|
159
|
+
# kill it. That is #2832.
|
|
160
|
+
# * A **project/program** token is an ORG asset. Its authority comes from its
|
|
161
|
+
# own project/program scope plus ``IsTokenForProject``; ``created_by`` is
|
|
162
|
+
# history attribution for whoever minted it, not the source of its rights.
|
|
163
|
+
# Rejecting it would kill a team's CI the moment an unrelated colleague is
|
|
164
|
+
# off-boarded — the very outcome ``revoke_all_personal_access_tokens``'s
|
|
165
|
+
# ``owner=user`` scoping (and the off-boarding path that calls it) exists to
|
|
166
|
+
# avoid. Downgrading to ``AnonymousUser`` instead is no better: both
|
|
167
|
+
# token-only views pair ``IsTokenForProject`` with ``IsAuthenticated``, so an
|
|
168
|
+
# anonymous principal 403s there — same broken pipeline, different status
|
|
169
|
+
# code. Leaving these tokens alone is therefore the only option that keeps
|
|
170
|
+
# the promise the revocation scope makes.
|
|
171
|
+
#
|
|
172
|
+
# Residual, pre-existing and deliberately NOT closed here: on the MCP-readable
|
|
173
|
+
# surface a project/program token still resolves ``request.user`` to its minter,
|
|
174
|
+
# so a deactivated minter's memberships still drive that read-only RBAC. That is
|
|
175
|
+
# a property of ADR-0214's attribution model, not of off-boarding, and narrowing
|
|
176
|
+
# it belongs with the MCP guards rather than in the shared base authenticator.
|
|
177
|
+
#
|
|
178
|
+
# The refusal is placed BEFORE the ``last_used_at`` stamp below so a refused
|
|
179
|
+
# request leaves no trace of "use" on the token, exactly as a revoked or expired
|
|
180
|
+
# token does — the stamp means "this credential successfully authenticated," and
|
|
181
|
+
# it would be misleading on a token that did not.
|
|
182
|
+
if token.is_personal and resolved is not None and not resolved.is_active:
|
|
183
|
+
# Same refusal path as a dead token: identical generic 401, same audit
|
|
184
|
+
# bounding, so a caller cannot distinguish "owner disabled" from "revoked"
|
|
185
|
+
# from "never existed" — anti-enumeration is unchanged.
|
|
186
|
+
self._audit_identity_refusal(request, raw_token)
|
|
187
|
+
self._mark_identity_refusal(request)
|
|
188
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_DETAIL)
|
|
189
|
+
|
|
190
|
+
# last_used_at is updated in a single UPDATE so we don't perturb the
|
|
191
|
+
# token's server_version or audit_history. The audit row for the
|
|
192
|
+
# specific use is written by the view (which holds the URL kwargs).
|
|
193
|
+
ProjectApiToken.objects.filter(pk=token.pk).update(last_used_at=timezone.now())
|
|
194
|
+
|
|
195
|
+
user = resolved or AnonymousUser()
|
|
196
|
+
return (user, token)
|
|
197
|
+
|
|
198
|
+
def _mark_identity_refusal(self, request: Request) -> None:
|
|
199
|
+
"""Tag the request so the 401 body carries the taxonomy (#2689).
|
|
200
|
+
|
|
201
|
+
Separate from :meth:`_audit_identity_refusal`, which is bounded to one
|
|
202
|
+
write ever per dead token. This mark is free and must happen on **every**
|
|
203
|
+
rejection, or the second replay of a revoked token would get a bare 401
|
|
204
|
+
while the first got an explanation — the sort of inconsistency that reads
|
|
205
|
+
as a bug in the caller.
|
|
206
|
+
|
|
207
|
+
``token_identity`` is disclosable: it describes the credential the caller
|
|
208
|
+
supplied, and reveals nothing about any resource. The response detail
|
|
209
|
+
itself stays ``_INVALID_TOKEN_DETAIL``, so this does not distinguish
|
|
210
|
+
"revoked" from "expired" from "never existed" — the enumeration-resistance
|
|
211
|
+
of that message is unchanged.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
from trueppm_api.apps.agents.models import AgentActionRefusalReason, RefusalConstraint
|
|
215
|
+
from trueppm_api.apps.agents.refusal import mark_refusal
|
|
216
|
+
|
|
217
|
+
mark_refusal(
|
|
218
|
+
request,
|
|
219
|
+
AgentActionRefusalReason.IDENTITY,
|
|
220
|
+
RefusalConstraint.TOKEN_IDENTITY,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _audit_identity_refusal(self, request: Request, raw_token: str) -> None:
|
|
224
|
+
"""Record a refused/identity AgentAction for a real-but-unusable token, at most once.
|
|
225
|
+
|
|
226
|
+
"Unusable" is either a dead token (revoked/expired/soft-deleted) or a live token
|
|
227
|
+
whose owning account has been disabled (#2832) — both are a real credential being
|
|
228
|
+
presented after it stopped conferring authority, and both are bounded by the
|
|
229
|
+
number of tokens ever minted.
|
|
230
|
+
|
|
231
|
+
Runs on the authentication-failure path, before ``AuthenticationFailed`` is
|
|
232
|
+
raised. The row is **queued**, not written here: DRF turns that exception into a
|
|
233
|
+
401 via ``exception_handler``, which calls ``set_rollback()``, so an INSERT
|
|
234
|
+
issued inside the request's ATOMIC_REQUESTS transaction would execute and then be
|
|
235
|
+
discarded. It is drained by ``AgentActionAuditMiddleware`` once the transaction
|
|
236
|
+
has closed (#3017, ADR-0902).
|
|
237
|
+
|
|
238
|
+
This docstring previously claimed the opposite — "the transaction still commits
|
|
239
|
+
and the audit row survives". It did not: a revoked token's 401 left zero rows,
|
|
240
|
+
measured, exactly like the policy refusals #3017 was filed for.
|
|
241
|
+
|
|
242
|
+
This path runs during authentication, which DRF executes *before* throttling — so
|
|
243
|
+
it must be bounded on its own. Two guards make it safe against a replay flood
|
|
244
|
+
(ADR-0112 RC1):
|
|
245
|
+
|
|
246
|
+
* an **unknown** hash writes nothing (an attacker spraying random tokens never
|
|
247
|
+
reaches the chain-locking write);
|
|
248
|
+
* a **known** dead token (revoked/expired/deleted — revocation is precisely the
|
|
249
|
+
response to a leak, so it *will* be replayed) is audited at most **once ever**
|
|
250
|
+
via a cheap indexed ``exists()`` gate before the write, so a replay loop pays
|
|
251
|
+
only one indexed read and never re-takes the global chain lock.
|
|
252
|
+
|
|
253
|
+
The write is also best-effort: a failure is logged and swallowed so it can never
|
|
254
|
+
convert the 401 into a 500 (matching the permission-layer refusal path). Since
|
|
255
|
+
the drain runs after this method returns, every value it needs is resolved here
|
|
256
|
+
and captured in the queued kwargs.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
from trueppm_api.apps.agents.deferred import queue_agent_action
|
|
260
|
+
from trueppm_api.apps.agents.models import (
|
|
261
|
+
AgentAction,
|
|
262
|
+
AgentActionRefusalReason,
|
|
263
|
+
AgentActionVerdict,
|
|
264
|
+
AgentActorKind,
|
|
265
|
+
RefusalConstraint,
|
|
266
|
+
)
|
|
267
|
+
from trueppm_api.apps.agents.services import hash_request_payload
|
|
268
|
+
from trueppm_api.apps.projects.models import ProjectApiToken
|
|
269
|
+
|
|
270
|
+
dead = (
|
|
271
|
+
ProjectApiToken.objects.filter(token_hash=sha256_hex(raw_token))
|
|
272
|
+
.select_related("owner")
|
|
273
|
+
.first()
|
|
274
|
+
)
|
|
275
|
+
if dead is None:
|
|
276
|
+
return # unknown hash — do not audit (unbounded; DoS-safe)
|
|
277
|
+
|
|
278
|
+
# Bound a replayed dead token to a single chain-locking write, ever.
|
|
279
|
+
if AgentAction.objects.filter(
|
|
280
|
+
actor_token=dead, refusal_reason=AgentActionRefusalReason.IDENTITY
|
|
281
|
+
).exists():
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
# Name the actual refusal class. A responder reading this row for a token whose
|
|
285
|
+
# owner was disabled would otherwise hunt for a revocation that never happened —
|
|
286
|
+
# the row is internal-only (the 401 body stays generic), so it costs no
|
|
287
|
+
# enumeration signal to be accurate here.
|
|
288
|
+
summary = (
|
|
289
|
+
"Rejected an API token whose owning account is disabled"
|
|
290
|
+
if dead.owner is not None and not dead.owner.is_active
|
|
291
|
+
else "Rejected a revoked/expired/deleted API token"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
xff = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
295
|
+
source_ip = xff.split(",")[0].strip() if xff else request.META.get("REMOTE_ADDR")
|
|
296
|
+
queue_agent_action(
|
|
297
|
+
request,
|
|
298
|
+
actor_kind=AgentActorKind.MCP_TOKEN,
|
|
299
|
+
actor_token=dead,
|
|
300
|
+
principal=dead.owner,
|
|
301
|
+
action="authenticate",
|
|
302
|
+
method=request.method or "",
|
|
303
|
+
capability_used="",
|
|
304
|
+
verdict=AgentActionVerdict.REFUSED,
|
|
305
|
+
refusal_reason=AgentActionRefusalReason.IDENTITY,
|
|
306
|
+
# Finer constraint axis (ADR-0421, #1850): a dead/revoked token is a
|
|
307
|
+
# token_identity refusal; an identity rejection carries no schedule impact.
|
|
308
|
+
refusal_constraint=RefusalConstraint.TOKEN_IDENTITY,
|
|
309
|
+
payload_hash=hash_request_payload(request),
|
|
310
|
+
summary=summary,
|
|
311
|
+
source_ip=source_ip,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def authenticate_header(self, request: Request) -> str:
|
|
315
|
+
return self.keyword
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class OwnerScopedApiTokenAuthentication(ProjectApiTokenAuthentication):
|
|
319
|
+
"""Owner-scoped-only variant, wired into the *general* endpoint surface (#2547).
|
|
320
|
+
|
|
321
|
+
Before this class existed, ``ProjectApiTokenAuthentication`` was never a part of
|
|
322
|
+
any default/general authentication path — the only two views that referenced it
|
|
323
|
+
explicitly (``TaskSyncView``, the acceptance-result ingest view) both additionally
|
|
324
|
+
require ``IsTokenForProject``, which a personal token (``project=None``,
|
|
325
|
+
``program=None``) can never satisfy. A minted Personal Access Token therefore had
|
|
326
|
+
no endpoint to authenticate against at all, contradicting ADR-0214's description
|
|
327
|
+
of a PAT as a credential that "acts as that user" the way a JWT/session would.
|
|
328
|
+
|
|
329
|
+
This class is what ``DEFAULT_AUTHENTICATION_CLASSES`` references (prepended
|
|
330
|
+
before JWT, mirroring ``McpReadableViewMixin.get_authenticators``'s ordering — the
|
|
331
|
+
base class returns ``None`` for a non-``tppm_`` bearer, so JWT still gets a turn).
|
|
332
|
+
It accepts a token here only when **both**:
|
|
333
|
+
|
|
334
|
+
* it is owner-scoped (``token.is_personal``) — a project/program token resolves
|
|
335
|
+
``request.user`` to its ``created_by`` *minter*, not itself. Accepting one on
|
|
336
|
+
the general surface would silently grant that narrow integration credential
|
|
337
|
+
every permission the minting human holds account-wide (every project/program
|
|
338
|
+
they belong to), not just the single project/program it was scoped to — the
|
|
339
|
+
confused-deputy widening ``IsTokenForProject`` exists to prevent on the narrow
|
|
340
|
+
surface, reproduced here for the wide one. Project/program tokens keep
|
|
341
|
+
authenticating exactly as before, only via the base class that
|
|
342
|
+
``TaskSyncView``/the acceptance-result view and ``McpReadableViewMixin``
|
|
343
|
+
reference directly (which runs *before* this subclass gets a turn on an
|
|
344
|
+
MCP-wrapped view, since the mixin prepends its own bare-class instance).
|
|
345
|
+
* it carries the ``legacy:full`` scope — an ``mcp:read``-only token stays
|
|
346
|
+
confined to the curated, explicitly opted-in MCP-readable surface (which
|
|
347
|
+
checks scope itself via ``TokenHasScope``/``TokenReadOnlyMethods``). Nothing
|
|
348
|
+
on a general (non-MCP-wrapped) view checks token scope, so without this guard
|
|
349
|
+
an ``mcp:read`` token would gain full read/write here — the exact opposite of
|
|
350
|
+
"rejected at every write path" the token's own scope description promises.
|
|
351
|
+
|
|
352
|
+
Anything else (project/program-scoped, or owner-scoped without ``legacy:full``)
|
|
353
|
+
is rejected with the same generic 401 as every other failure on the base class —
|
|
354
|
+
a caller cannot distinguish "wrong token type for this surface" from "invalid
|
|
355
|
+
token," preserving the anti-enumeration posture.
|
|
356
|
+
|
|
357
|
+
The rejection carries the ``token_identity`` refusal envelope (#2878). *"I minted
|
|
358
|
+
a read-only token and pointed my script at ``/api/v1/tasks/``"* is the most
|
|
359
|
+
likely first-hour integration mistake, and it was answering with a bare
|
|
360
|
+
``{"detail": "Invalid token."}`` — a caller holding a token they can see is live
|
|
361
|
+
in the UI has no way to reason from that to "wrong scope for this surface". The
|
|
362
|
+
envelope names the *class* of problem (the credential, not the request) without
|
|
363
|
+
weakening the constant ``detail``, so "wrong scope", "revoked", "expired" and
|
|
364
|
+
"never existed" remain indistinguishable and enumeration resistance is unchanged.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
def authenticate(self, request: Request) -> tuple[object, ProjectApiToken] | None:
|
|
368
|
+
result = super().authenticate(request)
|
|
369
|
+
if result is None:
|
|
370
|
+
return None
|
|
371
|
+
_, token = result
|
|
372
|
+
|
|
373
|
+
from trueppm_api.apps.projects.models import SCOPE_LEGACY_FULL
|
|
374
|
+
|
|
375
|
+
if not token.is_personal or SCOPE_LEGACY_FULL not in (token.scopes or []):
|
|
376
|
+
# Marked, not audited: `_audit_identity_refusal` is deliberately bounded to
|
|
377
|
+
# tokens that have stopped conferring authority at all (revoked/expired/
|
|
378
|
+
# owner disabled). This token is live and legitimate — it is simply on the
|
|
379
|
+
# wrong surface — so auditing it would turn an ordinary misconfiguration
|
|
380
|
+
# into unbounded audit-chain writes on every retry of a misaimed loop.
|
|
381
|
+
self._mark_identity_refusal(request)
|
|
382
|
+
raise exceptions.AuthenticationFailed(_INVALID_TOKEN_DETAIL)
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# drf-spectacular's OpenApiAuthenticationExtension registers subclasses via an
|
|
387
|
+
# untyped __init_subclass__, which mypy --strict flags as an untyped call on the
|
|
388
|
+
# class definition. The registration is the documented extension mechanism.
|
|
389
|
+
class ProjectApiTokenScheme(OpenApiAuthenticationExtension): # type: ignore[no-untyped-call]
|
|
390
|
+
"""drf-spectacular security scheme for project-scoped API tokens (#1016).
|
|
391
|
+
|
|
392
|
+
Without this, drf-spectacular cannot map ``ProjectApiTokenAuthentication`` to a
|
|
393
|
+
security scheme, so endpoints that override ``authentication_classes`` with it
|
|
394
|
+
(``TaskSyncView``) silently inherit the global ``jwtAuth`` in the schema —
|
|
395
|
+
integrators read the schema, send a JWT, and get 401. Registering the scheme
|
|
396
|
+
here (next to the auth class, imported whenever a view references it) makes the
|
|
397
|
+
schema advertise the correct ``Authorization: Bearer tppm_<64-hex>`` contract.
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
target_class = "trueppm_api.apps.projects.authentication.ProjectApiTokenAuthentication"
|
|
401
|
+
name = "projectApiTokenAuth"
|
|
402
|
+
|
|
403
|
+
def get_security_definition(self, auto_schema: Any) -> dict[str, Any]:
|
|
404
|
+
return {
|
|
405
|
+
"type": "http",
|
|
406
|
+
"scheme": "bearer",
|
|
407
|
+
"bearerFormat": "tppm_<64-hex>",
|
|
408
|
+
"description": (
|
|
409
|
+
"TruePPM API token. Send as `Authorization: Bearer tppm_<64-hex>`. "
|
|
410
|
+
"Three token types share this scheme: personal (owner-scoped) tokens "
|
|
411
|
+
"minted in personal settings; project-scoped tokens (ADR-0068) bound to "
|
|
412
|
+
"a single project's task-sync surface; and program-scoped tokens bound to "
|
|
413
|
+
"a program. A token also carries scopes: `legacy:full` (the historical "
|
|
414
|
+
"unrestricted superset) or `mcp:read` (the read-only MCP surface — "
|
|
415
|
+
"owner-scoped tokens only). MCP-token access can be disabled instance-wide "
|
|
416
|
+
"by the operator (TRUEPPM_MCP_ENABLED)."
|
|
417
|
+
),
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class OwnerScopedApiTokenScheme(OpenApiAuthenticationExtension): # type: ignore[no-untyped-call]
|
|
422
|
+
"""drf-spectacular security scheme for the general-endpoint PAT surface (#2547).
|
|
423
|
+
|
|
424
|
+
Registered separately from ``ProjectApiTokenScheme`` because
|
|
425
|
+
``OwnerScopedApiTokenAuthentication`` is a distinct importable class (the
|
|
426
|
+
``DEFAULT_AUTHENTICATION_CLASSES`` entry) — without its own scheme, every
|
|
427
|
+
endpoint that reaches it only via the default stack (i.e. everything that
|
|
428
|
+
doesn't override ``authentication_classes``) would advertise only ``jwtAuth`` in
|
|
429
|
+
the schema, the same drift ``ProjectApiTokenScheme`` was written to prevent.
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
target_class = "trueppm_api.apps.projects.authentication.OwnerScopedApiTokenAuthentication"
|
|
433
|
+
name = "personalApiTokenAuth"
|
|
434
|
+
|
|
435
|
+
def get_security_definition(self, auto_schema: Any) -> dict[str, Any]:
|
|
436
|
+
return {
|
|
437
|
+
"type": "http",
|
|
438
|
+
"scheme": "bearer",
|
|
439
|
+
"bearerFormat": "tppm_<64-hex>",
|
|
440
|
+
"description": (
|
|
441
|
+
"Personal Access Token. Send as `Authorization: Bearer tppm_<64-hex>`. "
|
|
442
|
+
"Authenticates as the token's owner against the general API surface — "
|
|
443
|
+
"identical RBAC to that user's own session, requires the `legacy:full` "
|
|
444
|
+
"scope. Project- and program-scoped integration tokens are never "
|
|
445
|
+
"accepted here (see `projectApiTokenAuth` for their narrow inbound-sync "
|
|
446
|
+
"surface); an owner-scoped `mcp:read`-only token is also rejected here "
|
|
447
|
+
"and stays confined to the read-only MCP surface."
|
|
448
|
+
),
|
|
449
|
+
}
|