diff --git a/client/internal/profilemanager/service.go b/client/internal/profilemanager/service.go index 0fb1d6c6d..726de8236 100644 --- a/client/internal/profilemanager/service.go +++ b/client/internal/profilemanager/service.go @@ -426,7 +426,7 @@ func (s *ServiceManager) AddProfile(displayName string, callerId *ipcauth.Identi }, nil } -func (s *ServiceManager) RenameProfile(id ID, userID ipcauth.Identity, newName string) error { +func (s *ServiceManager) RenameProfile(id ID, newName string) error { displayName, err := sanitizeDisplayName(newName) if err != nil { return fmt.Errorf("invalid profile name: %w", err) @@ -436,7 +436,7 @@ func (s *ServiceManager) RenameProfile(id ID, userID ipcauth.Identity, newName s return fmt.Errorf("invalid profile ID: %q", id) } - profiles, err := s.loadAllProfilesForIdentity(userID) + profiles, err := s.loadAllProfiles() if err != nil { return fmt.Errorf("load profiles: %w", err) } @@ -458,7 +458,7 @@ func (s *ServiceManager) RenameProfile(id ID, userID ipcauth.Identity, newName s // RemoveProfile deletes the profile identified by id. Callers must have // already resolved any user-supplied handle to a concrete ID via // ResolveProfile. -func (s *ServiceManager) RemoveProfile(id ID, userID ipcauth.Identity) error { +func (s *ServiceManager) RemoveProfile(id ID) error { if id == defaultProfileName { defaultName := readProfileName(DefaultConfigPath) if defaultName == "" { @@ -470,7 +470,7 @@ func (s *ServiceManager) RemoveProfile(id ID, userID ipcauth.Identity) error { return fmt.Errorf("invalid profile ID: %q", id) } - profiles, err := s.loadAllProfilesForIdentity(userID) + profiles, err := s.loadAllProfiles() if err != nil { return fmt.Errorf("load profiles: %w", err) } @@ -511,8 +511,7 @@ func (s *ServiceManager) RemoveProfile(id ID, userID ipcauth.Identity) error { return nil } -// ListProfiles returns every profile for the given user, including the -// default profile, with IsActive flags set. +// ListProfiles returns every profile for the given user func (s *ServiceManager) ListProfiles(userID ipcauth.Identity) ([]Profile, error) { return s.loadAllProfilesForIdentity(userID) } @@ -588,22 +587,21 @@ func (s *ServiceManager) profilesDirPath() string { return filepath.Join(DefaultConfigPathDir, DefaultProfilePathDir) } -// loadAllProfiles returns every profile visible to the daemon for the -// given user, including the default profile. The returned slice is sorted -// by ID for a stable display order. +// loadAllProfiles returns every profile acessible by a given kernal attested +// user. The returned slice is sorted by ID for a stable display order. // // Each Profile is fully populated: ID is the filename stem, Name comes // from the JSON's "name" field (falling back to the filename stem when absent) // and Path is built from a basename read off disk. func (s *ServiceManager) loadAllProfilesForIdentity(userID ipcauth.Identity) ([]Profile, error) { + if !userID.Known() { + return []Profile{}, nil + } allProfiles, err := s.loadAllProfiles() if err != nil { return nil, err } - s.claimDefaultProfileIfNeeded(allProfiles, userID) - s.claimLegacyProfiles(allProfiles, userID) - accessible := make([]Profile, 0, len(allProfiles)) for _, p := range allProfiles { if p.AccessibleBy(userID) { @@ -619,20 +617,26 @@ var ( legacyDirCache = map[string]string{} ) -// claimLegacyProfiles stamps the caller on every unowned profile in the +// ClaimLegacyProfiles stamps the caller on every unowned profile in the // directory their own user name produced before the ownership model. // // Ownership lives in the file now, so the directory name is only a leftover. // Flattening is a separate step we are doing in the future. Moving it would // pull the state file out from under an engine that captured its path at // connect time. -func (s *ServiceManager) claimLegacyProfiles(profiles []Profile, id ipcauth.Identity) { +func (s *ServiceManager) ClaimLegacyProfiles(id ipcauth.Identity) { // A privileged caller reaches every profile already and an internal load // has no caller, so neither should leave an owner behind. if ipcauth.IsPrivilegedCaller(id) { return } + profiles, err := s.loadAllProfiles() + if err != nil { + log.Warnf("could not load all profiles: %v", err) + return + } + if !hasUnownedLegacyProfile(profiles) { return } @@ -665,11 +669,17 @@ func (s *ServiceManager) claimLegacyProfiles(profiles []Profile, id ipcauth.Iden } } -func (s *ServiceManager) claimDefaultProfileIfNeeded(profiles []Profile, id ipcauth.Identity) { +func (s *ServiceManager) ClaimDefaultProfileIfNeeded(id ipcauth.Identity) { if !id.Known() || ipcauth.IsPrivilegedCaller(id) { return } + profiles, err := s.loadAllProfiles() + if err != nil { + log.Warnf("could not load all profiles: %w", err) + return + } + var unowned bool var p *Profile for i := range profiles { @@ -1055,12 +1065,12 @@ func (s *ServiceManager) activeProfileID() (ID, bool) { // precedence is: exact ID match, then unique exact name, then unique ID // prefix. Ambiguous matches return *ErrAmbiguousHandle so callers can // surface the candidates. -func (s *ServiceManager) ResolveProfile(handle string, userID ipcauth.Identity) (*Profile, error) { +func (s *ServiceManager) ResolveProfile(handle string) (*Profile, error) { if handle == "" { return nil, fmt.Errorf("profile handle is empty") } - profiles, err := s.loadAllProfilesForIdentity(userID) + profiles, err := s.loadAllProfiles() if err != nil { return nil, err } diff --git a/client/mobile/profile_manager.go b/client/mobile/profile_manager.go index 8cb721c8d..04c5df644 100644 --- a/client/mobile/profile_manager.go +++ b/client/mobile/profile_manager.go @@ -132,7 +132,7 @@ func (pm *ProfileManager) GetActiveProfile() (*Profile, error) { return nil, fmt.Errorf("get active profile: %w", err) } - prof, err := pm.serviceMgr.ResolveProfile(activeState.ID.String(), pm.identity) + prof, err := pm.serviceMgr.ResolveProfile(activeState.ID.String()) if err != nil { return nil, fmt.Errorf("resolve active profile %q: %w", activeState.ID, err) } @@ -182,7 +182,7 @@ func (pm *ProfileManager) RenameProfile(id string, newName string) error { if err := pm.checkProfilesAllowed(); err != nil { return err } - if err := pm.serviceMgr.RenameProfile(profilemanager.ID(id), pm.identity, newName); err != nil { + if err := pm.serviceMgr.RenameProfile(profilemanager.ID(id), newName); err != nil { return fmt.Errorf("rename profile: %w", err) } @@ -236,7 +236,7 @@ func (pm *ProfileManager) RemoveProfile(id string) error { return err } - if err := pm.serviceMgr.RemoveProfile(profilemanager.ID(id), pm.identity); err != nil { + if err := pm.serviceMgr.RemoveProfile(profilemanager.ID(id)); err != nil { return fmt.Errorf("remove profile: %w", err) } diff --git a/client/server/server.go b/client/server/server.go index 85c661adc..6b30db26d 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -533,12 +533,7 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques return nil, err } - callerID, err := callerIdentity(callerCtx) - if err != nil { - return nil, err - } - - stored, err := s.storedProfileConfig(msg.ProfileName, callerID) + stored, err := s.storedProfileConfig(msg.ProfileName) if err != nil { return nil, err } @@ -546,7 +541,7 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques return nil, err } - config, err := s.setConfigInputFromRequest(msg, callerID) + config, err := s.setConfigInputFromRequest(msg) if err != nil { return nil, err } @@ -576,10 +571,10 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques // field is its own optional case. Returns the resolved ConfigInput // and a non-nil error only when the active profile file path cannot // be determined. -func (s *Server) setConfigInputFromRequest(msg *proto.SetConfigRequest, callerID ipcauth.Identity) (profilemanager.ConfigInput, error) { +func (s *Server) setConfigInputFromRequest(msg *proto.SetConfigRequest) (profilemanager.ConfigInput, error) { var config profilemanager.ConfigInput - resolved, err := s.resolveProfileHandle(msg.ProfileName, callerID) + resolved, err := s.resolveProfileHandle(msg.ProfileName) if err != nil { log.Errorf("failed to resolve profile %q: %v", msg.ProfileName, err) return config, err @@ -682,11 +677,6 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro } } - callerID, err := callerIdentity(callerCtx) - if err != nil { - return nil, err - } - activeProf, err := s.profileManager.GetActiveProfileState() if err != nil { log.Errorf("failed to get active profile state: %v", err) @@ -698,7 +688,7 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro // refused login neither switches the profile nor cancels a login already in // progress, and it reads the profile the request targets, which is the one the // switch below would activate. - stored, err := s.storedLoginConfig(activeProf, msg, callerID) + stored, err := s.storedLoginConfig(activeProf, msg) if err != nil { return nil, err } @@ -1092,12 +1082,6 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR return nil, fmt.Errorf("config is not defined, please call login command first") } - callerID, err := callerIdentity(callerCtx) - if err != nil { - s.mutex.Unlock() - return nil, err - } - activeProf, err := s.profileManager.GetActiveProfileState() if err != nil { s.mutex.Unlock() @@ -1106,7 +1090,7 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR } if msg != nil && msg.ProfileName != nil { - if _, err := s.switchProfileIfNeeded(*msg.ProfileName, callerID, activeProf); err != nil { + if _, err := s.switchProfileIfNeeded(*msg.ProfileName, activeProf); err != nil { s.mutex.Unlock() log.Errorf("failed to switch profile: %v", err) return nil, err @@ -1172,8 +1156,8 @@ func (s *Server) waitForUp(callerCtx context.Context) (*proto.UpResponse, error) // targets, so a privileged-change decision can be made against the values the // profile currently holds. A profile that has no config file yet yields nil, // which every caller must read as "nothing enabled yet". -func (s *Server) storedProfileConfig(handle string, callerID ipcauth.Identity) (*profilemanager.Config, error) { - resolved, err := s.resolveProfileHandle(handle, callerID) +func (s *Server) storedProfileConfig(handle string) (*profilemanager.Config, error) { + resolved, err := s.resolveProfileHandle(handle) if err != nil { return nil, err } @@ -1189,7 +1173,7 @@ func (s *Server) storedProfileConfig(handle string, callerID ipcauth.Identity) ( // storedLoginConfig loads the on-disk config of the profile a login request // targets: the one it names, or the active one when it names none. Used to decide // a privileged change before the request is allowed to switch profiles. -func (s *Server) storedLoginConfig(activeProf *profilemanager.ActiveProfileState, msg *proto.LoginRequest, callerID ipcauth.Identity) (*profilemanager.Config, error) { +func (s *Server) storedLoginConfig(activeProf *profilemanager.ActiveProfileState, msg *proto.LoginRequest) (*profilemanager.Config, error) { if msg.ProfileName == nil { cfgPath, err := s.profileManager.ActiveProfilePath(activeProf) if err != nil { @@ -1200,7 +1184,7 @@ func (s *Server) storedLoginConfig(activeProf *profilemanager.ActiveProfileState // Mirrors switchProfileIfNeeded, so this reads the very profile the switch // would activate. - return s.storedProfileConfig(*msg.ProfileName, callerID) + return s.storedProfileConfig(*msg.ProfileName) } // storedConfigAtPath reads a profile config file, yielding nil when it does not @@ -1235,11 +1219,10 @@ func callerIdentity(ctx context.Context) (ipcauth.Identity, error) { } // resolveProfileHandle resolves a wire-level profile handle (display -// name, ID, or unique ID prefix) to a concrete profile owned by, or open to, -// the calling identity. Returns gRPC status errors so handlers can return them -// directly. -func (s *Server) resolveProfileHandle(handle string, callerID ipcauth.Identity) (*profilemanager.Profile, error) { - p, err := s.profileManager.ResolveProfile(handle, callerID) +// name, ID, or unique ID prefix). Returns gRPC status errors so +// handlers can return them directly. +func (s *Server) resolveProfileHandle(handle string) (*profilemanager.Profile, error) { + p, err := s.profileManager.ResolveProfile(handle) if err == nil { return p, nil } @@ -1256,8 +1239,8 @@ func (s *Server) resolveProfileHandle(handle string, callerID ipcauth.Identity) // switchProfileIfNeeded resolves the user-supplied handle, updates the // active profile state if it differs from the current one, and returns // the resolved profile so callers can include its ID in RPC responses. -func (s *Server) switchProfileIfNeeded(handle string, callerID ipcauth.Identity, activeProf *profilemanager.ActiveProfileState) (*profilemanager.Profile, error) { - resolved, err := s.resolveProfileHandle(handle, callerID) +func (s *Server) switchProfileIfNeeded(handle string, activeProf *profilemanager.ActiveProfileState) (*profilemanager.Profile, error) { + resolved, err := s.resolveProfileHandle(handle) if err != nil { return nil, err } @@ -1271,7 +1254,7 @@ func (s *Server) switchProfileIfNeeded(handle string, callerID ipcauth.Identity, return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled) } - log.Infof("switching to profile %s (%s) for %s", resolved.Name, resolved.ID, callerID) + log.Infof("switching to profile %s (%s)", resolved.Name, resolved.ID) if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{ ID: resolved.ID, Username: legacyDirHint(resolved), @@ -1288,11 +1271,6 @@ func (s *Server) SwitchProfile(callerCtx context.Context, msg *proto.SwitchProfi s.mutex.Lock() defer s.mutex.Unlock() - callerID, err := callerIdentity(callerCtx) - if err != nil { - return nil, err - } - activeProf, err := s.profileManager.GetActiveProfileState() if err != nil { log.Errorf("failed to get active profile state: %v", err) @@ -1300,7 +1278,7 @@ func (s *Server) SwitchProfile(callerCtx context.Context, msg *proto.SwitchProfi } if msg != nil && msg.ProfileName != nil { - if _, err := s.switchProfileIfNeeded(*msg.ProfileName, callerID, activeProf); err != nil { + if _, err := s.switchProfileIfNeeded(*msg.ProfileName, activeProf); err != nil { log.Errorf("failed to switch profile: %v", err) return nil, err } @@ -1453,12 +1431,7 @@ func (s *Server) Logout(ctx context.Context, msg *proto.LogoutRequest) (*proto.L } func (s *Server) handleProfileLogout(ctx context.Context, msg *proto.LogoutRequest) (*proto.LogoutResponse, error) { - callerID, err := callerIdentity(ctx) - if err != nil { - return nil, err - } - - resolved, err := s.resolveProfileHandle(*msg.ProfileName, callerID) + resolved, err := s.resolveProfileHandle(*msg.ProfileName) if err != nil { return nil, err } @@ -2270,12 +2243,7 @@ func (s *Server) GetConfig(ctx context.Context, req *proto.GetConfigRequest) (*p return nil, ctx.Err() } - callerID, err := callerIdentity(ctx) - if err != nil { - return nil, err - } - - resolved, err := s.resolveProfileHandle(req.ProfileName, callerID) + resolved, err := s.resolveProfileHandle(req.ProfileName) if err != nil { log.Errorf("failed to resolve profile %q: %v", req.ProfileName, err) return nil, err @@ -2420,17 +2388,12 @@ func (s *Server) RenameProfile(ctx context.Context, msg *proto.RenameProfileRequ return nil, gstatus.Errorf(codes.InvalidArgument, "profile name and new profile name must be provided") } - callerID, err := callerIdentity(ctx) + resolved, err := s.resolveProfileHandle(msg.Handle) if err != nil { return nil, err } - resolved, err := s.resolveProfileHandle(msg.Handle, callerID) - if err != nil { - return nil, err - } - - err = s.profileManager.RenameProfile(resolved.ID, callerID, msg.NewProfileName) + err = s.profileManager.RenameProfile(resolved.ID, msg.NewProfileName) if err != nil { log.Errorf("failed to rename profile: %v", err) return nil, fmt.Errorf("failed to rename profile: %w", err) @@ -2454,12 +2417,7 @@ func (s *Server) RemoveProfile(ctx context.Context, msg *proto.RemoveProfileRequ return nil, gstatus.Errorf(codes.InvalidArgument, "profile name must be provided") } - callerID, err := callerIdentity(ctx) - if err != nil { - return nil, err - } - - resolved, err := s.resolveProfileHandle(msg.ProfileName, callerID) + resolved, err := s.resolveProfileHandle(msg.ProfileName) if err != nil { return nil, err } @@ -2471,7 +2429,7 @@ func (s *Server) RemoveProfile(ctx context.Context, msg *proto.RemoveProfileRequ log.Warnf("removing profile %s locally without deregistering it: %v", resolved.ID, err) } - if err := s.profileManager.RemoveProfile(resolved.ID, callerID); err != nil { + if err := s.profileManager.RemoveProfile(resolved.ID); err != nil { log.Errorf("failed to remove profile: %v", err) return nil, fmt.Errorf("failed to remove profile: %w", err) } @@ -2506,12 +2464,7 @@ func (s *Server) ClaimProfile(ctx context.Context, msg *proto.ClaimProfileReques return nil, gstatus.Errorf(codes.InvalidArgument, "%v", err) } - callerID, err := callerIdentity(ctx) - if err != nil { - return nil, err - } - - resolved, err := s.resolveProfileHandle(msg.Handle, callerID) + resolved, err := s.resolveProfileHandle(msg.Handle) if err != nil { return nil, err } @@ -2595,7 +2548,7 @@ func (s *Server) publishLogLevelChanged(level string) { ) } -// ListProfiles lists all profiles in the daemon. +// ListProfiles lists all profiles for the caller in the context. func (s *Server) ListProfiles(ctx context.Context, msg *proto.ListProfilesRequest) (*proto.ListProfilesResponse, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -2875,15 +2828,10 @@ func (s *Server) authorizeAndPrepareLogin(callerCtx context.Context, msg *proto. afterLoginPreCheck() } - callerID, err := callerIdentity(callerCtx) - if err != nil { - return nil, nil, err - } - s.guardedConfigMu.Lock() defer s.guardedConfigMu.Unlock() - stored, err := s.storedLoginConfig(activeProf, msg, callerID) + stored, err := s.storedLoginConfig(activeProf, msg) if err != nil { return nil, nil, err } @@ -2907,7 +2855,7 @@ func (s *Server) authorizeAndPrepareLogin(callerCtx context.Context, msg *proto. } if msg.ProfileName != nil { - if _, err := s.switchProfileIfNeeded(*msg.ProfileName, callerID, activeProf); err != nil { + if _, err := s.switchProfileIfNeeded(*msg.ProfileName, activeProf); err != nil { return nil, nil, fmt.Errorf("switch profile: %w", err) } } @@ -2954,6 +2902,12 @@ func (s *Server) SessionHolder() (ipcauth.Principal, bool) { // This triggers stamping of legacy profiles, and reloads the active profile's // config so the stamp is visible to SessionHolder. func (s *Server) OwnsProfile(id ipcauth.Identity, handle string) (bool, error) { + s.profileManager.ClaimDefaultProfileIfNeeded(id) + s.profileManager.ClaimLegacyProfiles(id) + // The daemon's copy of the active profile's config goes stale after a the + // possible profile claims above. + s.reloadActiveConfig() + // Without the active profile there is nothing to fall back to and nothing // to refresh, so the gate gets a no rather than a guess. The handle is not // what went wrong here, so the gate is left to refuse in its own words. @@ -2970,19 +2924,12 @@ func (s *Server) OwnsProfile(id ipcauth.Identity, handle string) (bool, error) { handle = activeProfile.ID.String() } - resolved, resolveErr := s.resolveProfileHandle(handle, id) + resolved, resolveErr := s.resolveProfileHandle(handle) if afterProfileResolve != nil { afterProfileResolve() } - // Resolving stamps an owner on every legacy profile the caller can claim, - // not only the one the handle names, so the daemon's copy of the active - // profile's config goes stale whatever the handle was, and whether or not - // resolution succeeded. SessionHolder reads Owners off that copy, so - // refresh it before this answer reaches the gate. - s.reloadActiveConfig() - if resolveErr != nil { log.Debugf("failed to resolve profile %q: %v", handle, resolveErr) return false, resolveErr