Files
netbird/management/server/users/manager.go
mlsmaycon 627ee71fa8 fix(review): coderabbit follow-ups
- openapi.yml: declare default: false on ServiceTargetOptions.direct_upstream
  so generated clients/validators reflect the documented default.
- proto/proxy_service.proto: ValidateTunnelPeer doc + denied_reason list
  said "distribution_groups" (bearer-auth field) but the actual gate is
  service.access_groups. Replaced both occurrences to match the code path
  in checkPeerGroupAccess.
- peers/manager.go (GetPeerWithGroups) + users/manager.go (GetUserWithGroups):
  on store error after a successful first lookup, both now return
  (nil, nil, err) so callers can't get a valid entity alongside a non-nil
  error.

Findings skipped with reasons:
- embedded.go merged CLI/Dashboard redirect URIs: pre-existing on
  origin/main, not introduced by this PR.
- account_mock.go MarkPeerDisconnected zero-time UnixNano: same — pre-existing.
- openapi Service schema if/then conditionals: Go-side Validate() already
  enforces these invariants (Private + non-empty AccessGroups, mode=http,
  mutually-exclusive with bearer), and oapi-codegen on OpenAPI 3.1.x
  doesn't honour allOf/if/then anyway.
- *.patch / *.diff / b-n-p.sh: untracked personal artifacts, not part of
  any commit.
2026-05-21 11:45:11 +02:00

84 lines
2.5 KiB
Go

package users
import (
"context"
"errors"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types"
)
type Manager interface {
GetUser(ctx context.Context, userID string) (*types.User, error)
GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error)
}
type managerImpl struct {
store store.Store
}
type managerMock struct {
}
func NewManager(store store.Store) Manager {
return &managerImpl{
store: store,
}
}
func (m *managerImpl) GetUser(ctx context.Context, userID string) (*types.User, error) {
return m.store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
}
// GetUserWithGroups returns the user and the *types.Group records for the user's AutoGroups, in the same order as
// AutoGroups. Group ids that don't resolve to a stored group are skipped from the returned slice (the parallel id list is
// derivable from the returned User). Wraps two store calls today; can be optimised to a single JOIN later if needed.
// Any store error returns (nil, nil, err) so callers never receive a valid user alongside a non-nil error.
func (m *managerImpl) GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error) {
user, err := m.store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
if err != nil {
return nil, nil, err
}
if len(user.AutoGroups) == 0 {
return user, nil, nil
}
groupsMap, err := m.store.GetGroupsByIDs(ctx, store.LockingStrengthNone, user.AccountID, user.AutoGroups)
if err != nil {
return nil, nil, err
}
groups := make([]*types.Group, 0, len(user.AutoGroups))
for _, id := range user.AutoGroups {
if g, ok := groupsMap[id]; ok && g != nil {
groups = append(groups, g)
}
}
return user, groups, nil
}
func NewManagerMock() Manager {
return &managerMock{}
}
func (m *managerMock) GetUser(ctx context.Context, userID string) (*types.User, error) {
switch userID {
case "adminUser":
return &types.User{Id: userID, Role: types.UserRoleAdmin}, nil
case "regularUser":
return &types.User{Id: userID, Role: types.UserRoleUser}, nil
case "ownerUser":
return &types.User{Id: userID, Role: types.UserRoleOwner}, nil
case "billingUser":
return &types.User{Id: userID, Role: types.UserRoleBillingAdmin}, nil
default:
return nil, errors.New("user not found")
}
}
func (m *managerMock) GetUserWithGroups(ctx context.Context, userID string) (*types.User, []*types.Group, error) {
user, err := m.GetUser(ctx, userID)
if err != nil {
return nil, nil, err
}
return user, nil, nil
}