diff --git a/src/Core/Dirt/CLAUDE.md b/src/Core/Dirt/CLAUDE.md new file mode 100644 index 000000000000..5a5729410484 --- /dev/null +++ b/src/Core/Dirt/CLAUDE.md @@ -0,0 +1,101 @@ +# Access Intelligence, Reports & Event Integrations (Server) + +> Scope: `src/Core/Dirt/` and its children. Claude Code reads CLAUDE.md files hierarchically +> (repo root + every parent dir), so this supplements the repo-wide `.claude/CLAUDE.md` with +> domain-specific context. It does NOT auto-load in sibling subtrees such as +> `src/Api/Dirt/`, `src/Sql/dbo/Dirt/`, `src/Infrastructure.*/Dirt/`, `src/Events/`, or +> `src/EventsProcessor/` - see "Where this code lives" for those. + +## Project Overview + +This subtree holds the server-side domain logic for **Access Intelligence / Reports**, **Event +Integrations**, the **Events audit pipeline**, and **Phishing Detection** support. This is the +.NET (C#) server; it stores and serves data. Unencrypted vault data never reaches the server +(zero-knowledge) - the server persists encrypted blobs and non-secret metadata. + +> Owned by `@bitwarden/team-data-insights-and-reporting-dev` (DIRT) via CODEOWNERS. + +## Where this code lives + +- `src/Core/Dirt/` - domain logic: `Entities/`, `Repositories/` (interfaces), `Reports/` (report + command/query handlers + file storage), `EventIntegrations/` + `Services/` (Slack/Teams/webhook/ + HEC/Datadog), `Enums/`, `Models/`, `Utilities/`. +- `src/Api/Dirt/` - controllers (`OrganizationReportsController`, `ReportsController`, the + `OrganizationIntegration*`/`SlackIntegration`/`TeamsIntegration` controllers, `EventsController`, + `HibpController`) + request/response models + `Public/` API. +- `src/Sql/dbo/Dirt/` - raw SQL: `Tables/`, `Stored Procedures/`, `Views/`. +- `src/Infrastructure.Dapper/Dirt/` - Dapper repositories (production data path). +- `src/Infrastructure.EntityFramework/Dirt/` - EF Core repositories + `Configurations/` + `Models/`. +- `src/Events/` - the Events collector app (ingests audit events). `src/EventsProcessor/` - Azure + queue processor. Both are DIRT-owned but live outside `src/Core/Dirt/`. + +## Architecture & Patterns + +- **Data access is a triple-write.** Adding or changing a stored entity touches three places that + MUST stay in sync: + 1. `src/Sql/dbo/Dirt/` - table, stored procedures, and any view (plus a migration script under + `util/Migrator/DbScripts/`). + 2. `src/Infrastructure.Dapper/Dirt/` - the Dapper repository (production path). + 3. `src/Infrastructure.EntityFramework/Dirt/` - the EF Core repository, entity `Configurations/`, + and `Models/` (EF providers + integration tests). + Reference existing entities that already span all three: `OrganizationReport`, + `OrganizationApplication`, `Event`. +- **Commands / Queries.** Report operations under `Reports/ReportFeatures/` are one-class-per-operation + handlers (`*Command` / `*Query`) registered in `ReportingServiceCollectionExtensions`. Follow that + one-class-per-operation shape; keep controllers thin and put logic in Core. (That extension predates + the `TryAdd*` convention and still uses `AddScoped` - new registrations should prefer `TryAdd*`.) +- **DI:** use the `TryAdd*` pattern (ADR 0026). No code regions (root rule). +- **Naming:** "Access Intelligence" is the current name; "Risk Insights" is the deprecated name for + the same feature (legacy paths/classes like `RiskInsightsReportQuery` remain during migration). + Use "Access Intelligence" in new code, comments, and API surface. V2 report work is gated behind + `FeatureFlagKeys.AccessIntelligenceNewArchitecture`. + +## Feature Flags + +DIRT server flags are string constants in `src/Core/Constants.cs` (`FeatureFlagKeys`): +`PhishingDetection`, `AccessIntelligenceNewArchitecture` +(`pm-31936-access-intelligence-new-architecture`), `AccessIntelligenceVersion2`, +`AccessIntelligenceAdoptionUxImprovements`. Gate new endpoints/behavior on the appropriate flag; +default new flags off. + +## Common Commands + +- `dotnet build` / `dotnet test` +- `dotnet format` - format C# before committing +- `dotnet test test/Core.Test/ --filter "FullyQualifiedName~"` - run one test +- `pwsh dev/migrate.ps1` - apply local DB migrations after editing `src/Sql/` + adding a migrator script + +## Testing Standards + +- xUnit. Add/maintain tests for new logic (root rule). DIRT test projects: `test/Core.Test/Dirt`, + `test/Api.Test/Dirt`, `test/Infrastructure.EFIntegration.Test/Dirt`, and the `Events`/ + `EventsProcessor` test projects. +- Use `NSubstitute` + `AutoFixture` (`[SutAutoData]`/`[BitAutoData]`) following existing DIRT specs. +- Deterministic data only; no PII or real secrets in fixtures. + +## Security & Compliance + +- **Zero-knowledge:** the server never sees plaintext vault data. Never log or return PII, keys, or + decrypted data; audit events and report metadata may carry org/user IDs - keep them out of logs. +- New encryption logic does not belong here; follow the security definitions + (https://contributing.bitwarden.com/architecture/security/definitions). +- Respect CODEOWNERS; AI-config and security-sensitive changes are review-required, never auto-merged. + +## Gotchas & Tips + +- Forgetting one leg of the triple-write (SQL / Dapper / EF) is the most common DIRT server mistake - + a Dapper-only change passes some tests but breaks EF-provider/integration paths. +- Report file storage has Azure / Local / Noop implementations (`Reports/Services/`); pick by + environment, do not hardcode Azure. +- When extending an abstraction, confirm it still serves more than one consumer before adding to it. + +## References + +- Event Integrations design (deep dive): `src/Core/Dirt/EventIntegrations/README.md` + (and the scoped `src/Core/Dirt/EventIntegrations/CLAUDE.md`) +- Events pipeline apps context: `src/Events/CLAUDE.md` +- Repo-wide rules and commands: `.claude/CLAUDE.md` +- Contributing Claude context to this repo: `.claude/CONTRIBUTING.md` +- Server architecture: https://contributing.bitwarden.com/architecture/server/ +- ADRs: https://contributing.bitwarden.com/architecture/adr/ +- Caching (used by Event Integrations): `src/Core/Utilities/CACHING.md` diff --git a/src/Core/Dirt/EventIntegrations/CLAUDE.md b/src/Core/Dirt/EventIntegrations/CLAUDE.md new file mode 100644 index 000000000000..71b52c2ee8d5 --- /dev/null +++ b/src/Core/Dirt/EventIntegrations/CLAUDE.md @@ -0,0 +1,46 @@ +# Event Integrations - Subsystem Context + +> Scope: `src/Core/Dirt/EventIntegrations/`. Supplements the team-level file +> `src/Core/Dirt/CLAUDE.md` and the repo-wide `.claude/CLAUDE.md`. +> +> **Read first:** `src/Core/Dirt/EventIntegrations/README.md` - the full design doc +> (two-tier exchange, listener/handler pattern, retries, caching, and a step-by-step +> "Building a new integration" guide). This file only surfaces the rules most easily missed. + +## What this subsystem does + +Fans out organization audit events to external destinations (Slack, Teams, webhook, HTTP Event +Collector, Datadog) over a two-tier AMQP pipeline that runs on RabbitMQ (self-host) or Azure +Service Bus (cloud). Organizations configure which events go where via `OrganizationIntegration` ++ `OrganizationIntegrationConfiguration`, with optional filters. + +## Critical rules & gotchas + +- **Two tiers, decoupled listeners/handlers.** Event tier (fan-out of `EventMessage`) -> + `EventIntegrationHandler` -> integration tier (`IntegrationMessage`) -> per-integration handler. + Handlers know nothing about the messaging platform; listeners own platform specifics. Keep new + handlers platform-agnostic and unit-testable in isolation (they return `IntegrationHandlerResult`). +- **Cache invalidation is tag-based and easy to get wrong.** `OrganizationIntegrationConfigurationDetails` + is served from a named extended cache with a long (1-day) TTL. Admin create/update/delete commands + MUST invalidate by tag using `EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration`, + and `EventIntegrationHandler` MUST fetch with the same tag. Change one side without the other and reads + go stale. See the README "Caching" section. +- **Azure Service Bus subscriptions must exist before deploy.** ASB does NOT create resources on the + fly (RabbitMQ does). New integrations need their event- and integration-level subscriptions created + in ASB first, and added to `servicebusemulator_config.json` locally. See README "Deploying a new + integration." +- **Retries live in the listener, not the handler.** Backoff, jitter, `MaxRetries`, and DLQ routing are + the listener's job via `IntegrationMessage.ApplyRetry(...)`. Handlers only report success/retryable. + +## Adding a new integration + +Follow the README's "Building a new integration" checklist exactly (IntegrationType, configuration +models, request/response model switch cases + tests, handler, GlobalSettings queue/subscription names, +`ListenerConfiguration` subclass, `ServiceCollectionExtensions` wiring) and add a row to the README's +integrations table. + +## References + +- Full design doc: `src/Core/Dirt/EventIntegrations/README.md` +- Caching internals: `src/Core/Utilities/CACHING.md` +- Team-level context: `src/Core/Dirt/CLAUDE.md` diff --git a/src/Events/CLAUDE.md b/src/Events/CLAUDE.md new file mode 100644 index 000000000000..f058511836a8 --- /dev/null +++ b/src/Events/CLAUDE.md @@ -0,0 +1,42 @@ +# Events Pipeline - App Context + +> Scope: `src/Events/`. Supplements the repo-wide `.claude/CLAUDE.md`. The team-level DIRT file at +> `src/Core/Dirt/CLAUDE.md` does NOT auto-load here (different subtree), so the key team context +> is linked below. + +## What this app does + +`src/Events/` is the Events collector: a lightweight ASP.NET app that ingests organization/user +audit events (the `/collect` path via `EventsController`) and hands them to the event write pipeline. +The write path is selected in `AddEventWriteServices` by deployment: +- **Cloud:** the collector enqueues to Azure Queue Storage, and its sibling `src/EventsProcessor/` + (`AzureQueueHostedService`) drains the queue and persists events to Azure Table Storage. +- **Self-hosted:** there is no queue and no processor - the collector writes events straight to the + database via `RepositoryEventWriteService` (selected when `GlobalSettings.SelfHosted` is true). + `src/EventsProcessor/` is a cloud-only component (it has no `appsettings.SelfHosted.json`). + +When Event Integrations are enabled, `EventIntegrationEventWriteService` also broadcasts events onto +the AMQP exchange (see `src/Core/Dirt/EventIntegrations/README.md`). + +## Critical rules + +- **Events are metadata, not vault data**, but they carry org/user/entity IDs. Never log PII, tokens, + or anything that could deanonymize a user; keep the zero-knowledge invariant. +- **This is a hot path.** The collector runs at high volume - avoid per-request DB calls where a cached + lookup exists, and keep handlers cheap. Prefer the existing write-service abstractions over ad-hoc + persistence. +- **Auth/membership:** logging org events requires validated org membership (see recent `/collect` + membership guard). Do not weaken those checks. +- Add/maintain xUnit tests in `test/Events.Test`, `test/Events.IntegrationTest`, and + `test/EventsProcessor.Test`. + +## Common commands + +- Run locally: `dotnet run --project src/Events` +- Test: `dotnet test test/Events.Test` (and the integration/processor test projects) + +## References + +- Event write / integration pipeline: `src/Core/Dirt/EventIntegrations/README.md` +- Team-level context: `src/Core/Dirt/CLAUDE.md` +- Repo-wide rules: `.claude/CLAUDE.md`