Skip to content

fix(security): admit team members to their own memberships endpoint - #119

Merged
julia-shtal merged 1 commit into
devfrom
fix/security-and-timeouts
Sep 15, 2026
Merged

julia-shtal merged 1 commit into
devfrom
fix/security-and-timeouts

Conversation

@julia-shtal

Copy link
Copy Markdown
Owner

The /api/teams/** rule in SecurityConfig required MANAGER or ADMIN and runs
before any controller, so TeamController#getMyMemberships never reached its
method-level @PreAuthorize("isAuthenticated()"). Every DEVELOPER got 403, and
the Sidebar query gating the Team nav entry left isMember permanently false.

The exemption names the endpoint in full rather than the /me subtree: every
sibling route binds a {teamId}, so a wildcard would hand nine manager-only
handlers to MVC and leave Long conversion as the only thing stopping them.

TeamMembershipsControllerTest ran with addFilters = false, disabling the filter
chain the bug lived in, and so asserted 200 against code that returned 403. It
now loads the real chain and pins that the neighbouring routes stay at 403.

fix(http): bound the shared RestTemplate's connect and read timeouts

The default factory waits forever. Its only consumers are JiraCollector and
JiraProjectService, and discoverProjectsFromJira runs on the request thread, so
an unreachable Jira host held a Tomcat worker until the client disconnected.
The bounds are app.http.* properties, overridable like the rest of the config.

Also drops an appended MappingJackson2HttpMessageConverter that never resolved:
both consumers read String and parse with their own ObjectMapper, and
RestTemplate registers a Jackson converter ahead of it by default.

fix(scheduling): give scheduled jobs a thread pool and serialise metric writers

All six @scheduled jobs shared Spring Boot's default pool of one, so a long
enrichment pass delayed the nightly metric, backfill and weekly AI summary jobs.

metric_snapshots carries no unique key, so MetricSnapshotWriter reads before it
writes: with more than one thread the three scheduled writers can overlap and
each insert. MetricWriteGate makes them mutually exclusive. The daily jobs skip
on contention, since the next run covers the gap; the weekly job gates only its
metric refresh and still summarises stored snapshots, because skipping there
would cost a user their brief for seven days.

The gate is process-local and does not cover request threads, the collect pool
or the attribution listener; its Javadoc says so.

All six @scheduled jobs shared Spring Boot's default pool of one, so a long
enrichment pass delayed the nightly metric, backfill and weekly AI summary jobs.

metric_snapshots carries no unique key, so MetricSnapshotWriter reads before it
writes: with more than one thread the three scheduled writers can overlap and
each insert. MetricWriteGate makes them mutually exclusive. The daily jobs skip
on contention, since the next run covers the gap; the weekly job gates only its
metric refresh and still summarises stored snapshots, because skipping there
would cost a user their brief for seven days.

The gate is process-local and does not cover request threads, the collect pool
or the attribution listener; its Javadoc says so.

  The /api/teams/** rule in SecurityConfig required MANAGER or ADMIN and runs
  before any controller, so TeamController#getMyMemberships never reached its
  method-level @PreAuthorize("isAuthenticated()"). Every DEVELOPER got 403, and
  the Sidebar query gating the Team nav entry left isMember permanently false.

  The exemption names the endpoint in full rather than the /me subtree: every
  sibling route binds a {teamId}, so a wildcard would hand nine manager-only
  handlers to MVC and leave Long conversion as the only thing stopping them.

  TeamMembershipsControllerTest ran with addFilters = false, disabling the filter
  chain the bug lived in, and so asserted 200 against code that returned 403. It
  now loads the real chain and pins that the neighbouring routes stay at 403.

  fix(http): bound the shared RestTemplate's connect and read timeouts

  The default factory waits forever. Its only consumers are JiraCollector and
  JiraProjectService, and discoverProjectsFromJira runs on the request thread, so
  an unreachable Jira host held a Tomcat worker until the client disconnected.
  The bounds are app.http.* properties, overridable like the rest of the config.

  Also drops an appended MappingJackson2HttpMessageConverter that never resolved:
  both consumers read String and parse with their own ObjectMapper, and
  RestTemplate registers a Jackson converter ahead of it by default.

  fix(scheduling): give scheduled jobs a thread pool and serialise metric writers

  All six @scheduled jobs shared Spring Boot's default pool of one, so a long
  enrichment pass delayed the nightly metric, backfill and weekly AI summary jobs.

  metric_snapshots carries no unique key, so MetricSnapshotWriter reads before it
  writes: with more than one thread the three scheduled writers can overlap and
  each insert. MetricWriteGate makes them mutually exclusive. The daily jobs skip
  on contention, since the next run covers the gap; the weekly job gates only its
  metric refresh and still summarises stored snapshots, because skipping there
  would cost a user their brief for seven days.

  The gate is process-local and does not cover request threads, the collect pool
  or the attribution listener; its Javadoc says so.

  All six @scheduled jobs shared Spring Boot's default pool of one, so a long
  enrichment pass delayed the nightly metric, backfill and weekly AI summary jobs.

  metric_snapshots carries no unique key, so MetricSnapshotWriter reads before it
  writes: with more than one thread the three scheduled writers can overlap and
  each insert. MetricWriteGate makes them mutually exclusive. The daily jobs skip
  on contention, since the next run covers the gap; the weekly job gates only its
  metric refresh and still summarises stored snapshots, because skipping there
  would cost a user their brief for seven days.

  The gate is process-local and does not cover request threads, the collect pool
  or the attribution listener; its Javadoc says so.
@julia-shtal
julia-shtal merged commit 2533d1a into dev Sep 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant