Skip to content

PMM-14880 anonymous role#5170

Open
fabio-silva wants to merge 3 commits intoPMM-14880-rta-pmm-demofrom
PMM-14880-anonymous-role
Open

PMM-14880 anonymous role#5170
fabio-silva wants to merge 3 commits intoPMM-14880-rta-pmm-demofrom
PMM-14880-anonymous-role

Conversation

@fabio-silva
Copy link
Copy Markdown
Contributor

@fabio-silva fabio-silva commented Mar 24, 2026

PMM-14880

Allow org roles to anonymous users

Tied to percona/grafana#886

Note: we fallback to "Viewer" is any other org_role is used on anon mode, due to this deprecation notice:
https://github.com/grafana/grafana/pull/101411/changes

@fabio-silva fabio-silva changed the base branch from v3 to PMM-14880-rta-pmm-demo March 24, 2026 12:04
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 40.14085% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.49%. Comparing base (fdc8177) to head (dedcc55).

Files with missing lines Patch % Lines
managed/services/grafana/client.go 52.88% 20 Missing and 29 partials ⚠️
managed/services/user/current_http.go 0.00% 29 Missing ⚠️
managed/cmd/pmm-managed/main.go 0.00% 5 Missing ⚠️
managed/services/grafana/auth_server.go 50.00% 2 Missing ⚠️
Additional details and impacted files
@@                    Coverage Diff                     @@
##           PMM-14880-rta-pmm-demo    #5170      +/-   ##
==========================================================
- Coverage                   47.79%   47.49%   -0.30%     
==========================================================
  Files                         410      411       +1     
  Lines                       41974    42158     +184     
==========================================================
- Hits                        20062    20025      -37     
- Misses                      19935    20030      +95     
- Partials                     1977     2103     +126     
Flag Coverage Δ
managed 47.25% <40.14%> (-0.47%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fabio-silva
Copy link
Copy Markdown
Contributor Author

We need profiling on this one.
CC @maxkondr

Comment thread managed/services/grafana/client.go Outdated
Comment thread managed/services/grafana/client.go
Comment thread managed/services/grafana/client.go Outdated
Comment thread managed/services/grafana/client.go Outdated
Comment thread managed/services/grafana/client.go Outdated
Comment thread managed/services/user/current_http.go Outdated
Comment thread managed/services/user/current_http.go Outdated
Comment thread managed/services/grafana/client.go Outdated
Comment thread managed/services/grafana/client.go Outdated
Comment thread managed/services/grafana/client.go Outdated
@fabio-silva fabio-silva marked this pull request as ready for review April 23, 2026 14:19
@fabio-silva fabio-silva requested review from a team as code owners April 23, 2026 14:19
@fabio-silva fabio-silva requested review from JiriCtvrtka, Copilot, matejkubinec and maxkondr and removed request for a team April 23, 2026 14:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Enables “anonymous mode” users to receive an organization role (clamped to Viewer when Grafana is configured with a higher anonymous role), and updates PMM UI/back-end wiring to use new PMM-managed “current user” endpoints rather than calling Grafana’s user endpoints directly.

Changes:

  • Added /v1/users/current and /v1/users/current/orgs HTTP endpoints in pmm-managed that proxy/normalize Grafana “current user” payloads and support anonymous fallback.
  • Extended Grafana client/auth logic to detect anonymous mode via /api/frontend/settings and clamp anonymous org role to Viewer.
  • Updated PMM UI user loading and feature gating to support anonymous users (new isAnonymous field, updated HA/settings enablement, updated API calls).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
ui/docker-compose.yml Adds developer guidance for mounting/building/swapping Grafana backend locally.
ui/apps/pmm/src/utils/testStubs.ts Updates test user stub to include isAnonymous.
ui/apps/pmm/src/types/user.types.ts Adds isAnonymous to User and GetUserResponse types.
ui/apps/pmm/src/hooks/api/useHA.ts Allows useHaInfo to accept query options (e.g., enabled).
ui/apps/pmm/src/contexts/user/user.utils.ts Plumbs isAnonymous into the derived PMM User.
ui/apps/pmm/src/contexts/user/user.provider.tsx Fetches current user/orgs regardless of login state and synthesizes minimal info/preferences for anonymous users.
ui/apps/pmm/src/contexts/settings/settings.provider.tsx Prevents settings queries from running for anonymous users.
ui/apps/pmm/src/contexts/navigation/navigation.provider.tsx Disables HA info fetching for anonymous users; adjusts service-types fetching behavior.
ui/apps/pmm/src/api/user.ts Switches current-user API calls from Grafana API to PMM-managed /v1/users/current* endpoints.
ui/apps/pmm/src/App.tsx Disables React Query retries globally.
ui/Makefile Adds helper targets to build/swap Grafana backend binary inside the dev container.
managed/services/user/current_http.go New HTTP handler serving /v1/users/current and /v1/users/current/orgs.
managed/services/grafana/client_test.go Adds unit tests for anonymous fallback behavior in Grafana client methods.
managed/services/grafana/client.go Implements anonymous-role resolution/clamping and exposes “current user/orgs” methods with anonymous fallback.
managed/services/grafana/auth_server.go Allows unauthenticated access to the new endpoints and skips LBAC filters for anonymous users.
managed/cmd/pmm-managed/main.go Wires the new current-user HTTP handler into the HTTP/1 server mux.

Comment on lines +296 to 300
l.Debugf("maybeAddLBACFilters: userID=%d", userID)
if !s.shallAddLBACFilters(req) {
l.Debugf("Skipping LBAC filters for non-proxied request.")
return nil
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These per-request Debug logs in maybeAddLBACFilters will generate a lot of log volume whenever debug logging is enabled (and they run even for the common no-op path). Consider removing them, lowering to Trace, or logging only when filters are actually added / when an error occurs.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +49
if err != nil {
h.l.Errorf("failed to get current user: %v", err)
rw.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(rw).Encode(map[string]string{"message": "Unauthorized"})
return
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On error, this endpoint always responds with 401 Unauthorized and a generic body. That will mask real failures (e.g., Grafana unreachable/5xx) as auth problems; consider propagating upstream status codes for 401/403 and returning an appropriate 5xx (e.g., 502) for upstream/internal errors.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +58
if err != nil {
h.l.Errorf("failed to get current user orgs: %v", err)
rw.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(rw).Encode(map[string]string{"message": "Unauthorized"})
return
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On error, this endpoint always responds with 401 Unauthorized and a generic body. That will mask real failures (e.g., Grafana unreachable/5xx) as auth problems; consider propagating upstream status codes for 401/403 and returning an appropriate 5xx (e.g., 502) for upstream/internal errors.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +36
func (h *currentHTTPHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
authHeaders := make(http.Header)
for _, k := range []string{"Authorization", "Cookie"} {
if v := req.Header.Get(k); v != "" {
authHeaders.Set(k, v)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServeHTTP doesn't validate the HTTP method; non-GET requests to these endpoints will currently be processed the same way. Consider restricting to GET and returning 405 for other methods to keep the API contract tight.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +28
// NewCurrentHTTPHandler creates handler for current user JSON endpoints.
func NewCurrentHTTPHandler(c currentUserClient) http.Handler {
return &currentHTTPHandler{
c: c,
l: logrus.WithField("component", "user/current-http"),
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New current-user HTTP endpoints are introduced here, but there are no unit tests covering the handler behavior (routing, method handling, status code mapping, anonymous vs authenticated headers). Since this package already has unit tests, adding coverage for this handler would help prevent regressions.

Copilot generated this review using guidance from repository custom instructions.
Comment thread ui/docker-compose.yml
Comment on lines +35 to +38
# Build/swap/verify Grafana backend via helper targets in ui/Makefile:
# make -C ui grafana-be-build
# make -C ui grafana-be-swap
# make -C ui grafana-be-verify
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docker-compose comment references a grafana-be-verify Make target, but ui/Makefile doesn't define it. This makes the instructions misleading; either add the missing target or remove/update the reference here.

Suggested change
# Build/swap/verify Grafana backend via helper targets in ui/Makefile:
# make -C ui grafana-be-build
# make -C ui grafana-be-swap
# make -C ui grafana-be-verify
# Build/swap Grafana backend via helper targets in ui/Makefile:
# make -C ui grafana-be-build
# make -C ui grafana-be-swap

Copilot uses AI. Check for mistakes.

const navTree = useMemo<NavItem[]>(() => {
const items: NavItem[] = [];
// provide all service types for anonymous mode
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "provide all service types for anonymous mode", but currentServiceTypes is now always derived from serviceTypes?.serviceTypes with an empty-array fallback. Either restore the anonymous fallback behavior or update/remove the comment to match the actual logic.

Suggested change
// provide all service types for anonymous mode
// use fetched service types, falling back to an empty list while unavailable

Copilot uses AI. Check for mistakes.
Comment on lines +269 to +287
var (
anonymousEnabled bool
anonymousRole role
)
if !hasAuthorizationHeader(authHeaders) {
anonymousEnabled, anonymousRole = c.getAnonymousRoleFromSettings(ctx, l)
}

// https://grafana.com/docs/http_api/user/#actual-user - works only with Basic Auth
var m map[string]interface{}
err := c.do(ctx, http.MethodGet, "/api/user", "", authHeaders, nil, &m)
if err != nil {
var cErr *clientError
if anonymousEnabled && errors.As(err, &cErr) && cErr.Code == http.StatusUnauthorized {
l.Debugf("Grafana returned 401 for /api/user with no credentials; using anonymous role %q.", anonymousRole.String())
return authUser{
role: anonymousRole,
userID: 0,
}, nil
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAuthUser fetches /api/frontend/settings whenever there is no Authorization header, even if /api/user would succeed (e.g., valid session cookie). With the auth cache TTL at 3s, this adds extra Grafana API calls; consider fetching frontend settings only after /api/user returns 401 when deciding to fall back to anonymous.

Suggested change
var (
anonymousEnabled bool
anonymousRole role
)
if !hasAuthorizationHeader(authHeaders) {
anonymousEnabled, anonymousRole = c.getAnonymousRoleFromSettings(ctx, l)
}
// https://grafana.com/docs/http_api/user/#actual-user - works only with Basic Auth
var m map[string]interface{}
err := c.do(ctx, http.MethodGet, "/api/user", "", authHeaders, nil, &m)
if err != nil {
var cErr *clientError
if anonymousEnabled && errors.As(err, &cErr) && cErr.Code == http.StatusUnauthorized {
l.Debugf("Grafana returned 401 for /api/user with no credentials; using anonymous role %q.", anonymousRole.String())
return authUser{
role: anonymousRole,
userID: 0,
}, nil
noAuthorizationHeader := !hasAuthorizationHeader(authHeaders)
// https://grafana.com/docs/http_api/user/#actual-user - works only with Basic Auth
var m map[string]interface{}
err := c.do(ctx, http.MethodGet, "/api/user", "", authHeaders, nil, &m)
if err != nil {
var cErr *clientError
if noAuthorizationHeader && errors.As(err, &cErr) && cErr.Code == http.StatusUnauthorized {
anonymousEnabled, anonymousRole := c.getAnonymousRoleFromSettings(ctx, l)
if anonymousEnabled {
l.Debugf("Grafana returned 401 for /api/user with no credentials; using anonymous role %q.", anonymousRole.String())
return authUser{
role: anonymousRole,
userID: 0,
}, nil
}

Copilot uses AI. Check for mistakes.
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.

3 participants