Skip to content

PMM-14880 anonymous role#5170

Draft
fabio-silva wants to merge 2 commits intoPMM-14880-rta-pmm-demofrom
PMM-14880-anonymous-role
Draft

PMM-14880 anonymous role#5170
fabio-silva wants to merge 2 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

@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 53.33333% with 63 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.82%. Comparing base (fdc8177) to head (53ca5b2).

Files with missing lines Patch % Lines
managed/services/user/current_http.go 0.00% 32 Missing ⚠️
managed/services/grafana/client.go 74.46% 12 Missing and 12 partials ⚠️
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.82%   +0.02%     
==========================================================
  Files                         410      411       +1     
  Lines                       41974    42106     +132     
==========================================================
+ Hits                        20062    20136      +74     
- Misses                      19935    19981      +46     
- Partials                     1977     1989      +12     
Flag Coverage Δ
managed 47.75% <53.33%> (+0.03%) ⬆️

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


func (c *Client) getAnonymousRoleFromSettings(ctx context.Context, l *logrus.Entry) (bool, role) {
var settings frontendSettings
if err := c.do(ctx, http.MethodGet, "/api/frontend/settings", "", nil, nil, &settings); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there is a separate method introduced getFrontendSettings for this

}

// CurrentUser represents Grafana user payload.
type CurrentUser struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

are all fields needed in these structs

}

func (c *Client) getAnonymousRoleFromSettings(ctx context.Context, l *logrus.Entry) (bool, role) {
var settings frontendSettings
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

in this method the result from API endpoint /api/frontend/settings is parsed into frontendSettings struct, but the same endpoint call in getFrontendSettings is parsed into frontendSettingsFull struct. What is the purpose?

Comment on lines +447 to +452
IsDisabled: false,
IsExternal: false,
IsExtarnallySynced: false,
IsGrafanaAdmin: false,
IsGrafanaAdminExternallySynced: false,
Theme: "",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

not sure these fields are required here

}

var cErr *clientError
if !errors.As(errors.Cause(err), &cErr) || cErr.Code != http.StatusUnauthorized || hasAuthHeaders(authHeaders) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. looks like no need to call Cause() because errors.As() iterates though errors chain
  2. seems the condition shall be
if (errors.As(err, &cErr) && cErr.Code != http.StatusUnauthorized) || hasAuthHeaders(authHeaders) {

because cErr.Code has value only in case errors.As() call was successful

case "/v1/users/current":
user, err := h.c.GetCurrentUser(req.Context(), authHeaders)
if err != nil {
h.l.WithError(err).Warn("failed to get current user")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

in pmm-managed the following way for printing errors is used:

Suggested change
h.l.WithError(err).Warn("failed to get current user")
h.l.Errorf("failed to get current user: %v", err)

case "/v1/users/current/orgs":
orgs, err := h.c.GetCurrentUserOrgs(req.Context(), authHeaders)
if err != nil {
h.l.WithError(err).Warn("failed to get current user orgs")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
h.l.WithError(err).Warn("failed to get current user orgs")
h.l.Errorf("failed to get current user orgs: %v", err)

err := c.do(ctx, http.MethodGet, "/api/user", "", authHeaders, nil, &m)
if err != nil {
var cErr *clientError
if anonymousEnabled && errors.As(errors.Cause(err), &cErr) && cErr.Code == http.StatusUnauthorized {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks like errors.Cause() is not needed here

if c.resolveAnonymousRole(settings) == none.String() {
// Anonymous mode is enabled but role is not configured.
// Return empty payload instead of Unauthorized.
return CurrentUser{}, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it is better to return an Unauthorized error here and decrease the number of variations (empty user w/ error vs empty user w/o error, etc). Anything (wrong credentials, other errors, not properly configured anonymous mode) - returns error

if role == none.String() {
// Anonymous mode is enabled but role is not configured.
// Return empty payload instead of Unauthorized.
return []CurrentUserOrg{}, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the same regarding error as in GetCurrentUser

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.

2 participants