# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Common Commands

```bash
# Build (skip tests)
./mvnw package -DskipTests

# Run all tests (uses H2 in-memory DB — no external services needed)
./mvnw test

# Run a single test class
./mvnw test -Dtest=StandardRoleApiDocTest

# Run the service locally (requires MySQL + Redis)
./mvnw spring-boot:run

# Start local MySQL (port 3309, not 3306)
docker-compose up -d

# Run Sonar analysis
./sonar.sh

# OWASP dependency check
./mvnw verify -P dependency_check
```

The build produces two artifacts from the same source tree:
- **Main fat jar** — the runnable authorization service
- **`authorization-*-client.jar`** — a thin client jar consumed by other NetFoundry services (contains `client/`, `clientv2/`, `domain/`, `domainv2/` minus `*Service`/`*Repository` classes, plus controller inner classes like `*Request`/`*Response`)

## Architecture Overview

### What this service does

The Authorization Service is the single source of truth for identity permissions in the NetFoundry platform. It:
1. **Issues internal NF JWTs** by exchanging Auth0 tokens (`POST /exchange`) or masquerading as another identity (`POST /masquerade`)
2. **Serves the JWKS** endpoint (`GET /auth/jwks`) used by all other services to validate NF tokens
3. **Provides the full authorization set** for an identity (`GET /authorizations`)
4. **Manages the permission data model** — resource types, actions, domains, grants, standard roles, custom roles

### Token flow

```
Auth0 JWT  →  POST /exchange  →  NF JWT (with grant claims embedded)
NF JWT (service identity)  →  POST /masquerade  →  NF JWT (user identity)
```

The NF JWT carries five custom claims (`grants-public`, `grants-super`, `grants-action`, `grants-custom`, `grants-standard`), each a JSON-serialized set of `{id, version}` references — **not** the full grant data. The full grant data is resolved lazily by `AuthorizationSetBuilder` when the token is presented to a downstream service.

### Package structure and responsibilities

| Package | Role |
|---|---|
| `logic/` | `AuthorizationLogic` — signs and issues NF JWTs using the current RSA key from `JwksService` |
| `service/JwksService` | Manages RSA key pairs in Redis (`auth/jwks` map). Rotates keys on a 24h schedule; removes the public key on shutdown. All auth instances share the same Redis map so any instance can validate any token. |
| `service/controller/` | REST controllers. `AuthorizationController` is the primary entry point. |
| `domainv2/` | Core permission domain: entities (`StandardRole`, `CustomRole`, `Grant`, `PublicGrant`, `SuperUser`, `IdentityResourceAction`, `ResourceType`, `ResourceAction`, `Domain`) and their services. Services use `InMemoryMarkDeleteBeanRepository` from `common-util-framework` — data is loaded from MySQL into memory on startup. |
| `domainv2/Authorization.java` | Pure static utility. Implements the authorization check: given an `AuthorizationSet` and a target `ResourceIdPath`, returns true/false. Handles owner-identity dynamic grants. |
| `domainv2/AuthorizationSetBuilder` | Assembles an `AuthorizationSet` from grant references by resolving each ID to its full domain object via `AuthEntityResolver`. By default, `computeIncludedAuthorizations=false`; it is set to `true` only within the auth service itself (in `AuthorizationConfiguration.postConstructInit()`). |
| `domainv2/ancestry/` | `ResourceAncestryService` manages the hierarchical `ResourceIdPath` used to scope grants (e.g., `org → network → endpoint`). Each domain entity with path data has its own ancestry table and `ResourceAncestryService` instance. |
| `clientv2/` | **Client framework** — shipped as part of the client jar to downstream services. `AuthorizationContext` parses the NF JWT from the `Authorization` header (thread-local) and builds an `AuthorizationSet`. The `@AuthAction`/`@AuthCreate`/`@AuthRead`/`@AuthUpdate`/`@AuthDelete` annotations enforce authorization as Bean Validation constraints on controller method parameters. |
| `client/` | Legacy v1 client: `Auth0JwtValidator`, `InterServiceTokenProvider`, `AuthExceptionAdvice`. |
| `configuration/` | Spring config. `AuthorizationConfiguration` wires up the JWT consumer (NF token validator), sets `computeIncludedAuthorizations=true`, and registers the `clientv2` beans. |

### Authorization model

Permission checks use a path-based model:
- **`ResourceIdPath`** is a chain of `(resourceType, id)` pairs from domain root to the specific resource (e.g., `org/abc → network/xyz`)
- A **grant** gives an identity a role on a path. The grant path can be exact (a specific resource) or a prefix (all resources of that type under that prefix)
- **`Authorization.isAuthorized()`** resolves the matching grant paths and applies owner-identity semantics: if the action is "below" the grant's resource type depth and the action is not a `create*`, the caller's identity must match the resource's owner identity

### Grant types (in precedence order in the token)

- **SuperUser** — full access to everything
- **PublicGrant** — grants applied to all identities (anonymous included)
- **IdentityResourceAction** — a direct action grant on a specific resource
- **CustomRole** — a named set of actions granted to an identity over a resource path
- **StandardRole** — a pre-defined role type (e.g., Network Admin, Read Only) granted over a resource path; may include other standard roles

### Included authorizations (standard role inheritance)

Standard roles can implicitly "include" other roles whose full permission set is a subset of theirs. This is computed by exploding all permissions of each role and testing set inclusion — it is expensive. The `AuthorizationSetBuilder.computeIncludedAuthorizations` flag gates this calculation; it is `false` by default in the client jar and `true` only within the auth service.

## Database & Migrations

- **Production**: MySQL, schema `auth`, default port 3306. Configured via `io.netfoundry.permissions.db.*` properties.
- **Local dev**: `docker-compose up` starts MySQL on **port 3309** (not 3306), database `permissions`, root password `nfpass`.
- **Tests**: H2 in-memory (`permissions` schema). `DataSourceTestConfiguration` pre-loads the v1 cumulative baseline SQL then lets Flyway apply v2 migrations only (v1 migrations are incompatible with H2).

Migration files live in:
- `src/main/resources/database/v1-migration/` — legacy; effectively baselining via `v1-cumulative-as-baseline.sql`
- `src/main/resources/database/v2-migration/` — active development (272+ migrations)
- `src/test/resources/database/v2-overrides/` — H2-compatible replacements for two v2 migrations that use MySQL-specific SQL

When adding new permissions, add a new `V2.<next>__description.sql` in `v2-migration/`. Version numbers are the PR/ticket-driven sequential integers visible in the existing files.

## Testing

Tests use `@ActiveProfiles("test")`, H2, and no Redis — `JwksService` is replaced in `ServerTestConfiguration`. The `ApplicationTest` base class wires up the full Spring context with test configurations. `ServiceTest` extends it and provides helper methods for building signed NF tokens for use in MockMvc calls.

The `*ApiDocTest` classes (one per controller) generate Spring REST Docs snippets used to build the API documentation at `src/main/asciidoc/index.adoc`. Running tests also regenerates the docs.

## Key Configuration Properties

| Property | Default | Purpose |
|---|---|---|
| `server.port` | 8095 | Service HTTP port |
| `nf-token-duration-seconds` | 300 | NF JWT TTL |
| `io.netfoundry.auth.jwks.rotationInterval` | PT24H | RSA key rotation interval |
| `io.netfoundry.auth.jwks.cache.timetolive` | PT25H | Redis JWKS entry TTL |
| `io.netfoundry.auth.provider.auth0.issuer` | sandbox Auth0 tenant | Auth0 tenant for validating incoming tokens on `/exchange` |
| `server.max-http-request-header-size` | 250000 | Raised from Tomcat default to handle large NF tokens |
