Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/Core/Dirt/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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~<Name>"` - 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`
46 changes: 46 additions & 0 deletions src/Core/Dirt/EventIntegrations/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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<T>`) -> 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`
42 changes: 42 additions & 0 deletions src/Events/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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`
Loading