-
-
Notifications
You must be signed in to change notification settings - Fork 973
[client] Android profile switch #4884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughPlatform-specific config/state file handling moved out of the Android Client struct into method parameters; an Android ProfileManager wrapper was added; ServiceManager gained an optional profiles directory override; the engine now uses mobile-provided state paths on iOS/Android while preserving desktop behavior. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
client/internal/engine.go (1)
2108-2111:fileExistsreturnstrueon permission errors.The current implementation returns
trueif the error is anything other thanos.ErrNotExist. This includes permission errors, which could lead to skipping file creation when the file doesn't actually exist but is inaccessible.Consider using
errors.Isfor more precise checking:func fileExists(path string) bool { _, err := os.Stat(path) - return !os.IsNotExist(err) + return err == nil }This returns
trueonly when the file definitively exists and is accessible.client/android/profile_manager.go (2)
121-135: Consider validating profile existence before switching.
SwitchProfilesets the active profile without verifying that the target profile actually exists. This could lead to runtime errors when the client attempts to use a non-existent profile.func (pm *ProfileManager) SwitchProfile(profileName string) error { + // Validate profile exists before switching + configPath, err := pm.getProfileConfigPath(profileName) + if err != nil { + return fmt.Errorf("failed to get profile path: %w", err) + } + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return fmt.Errorf("profile '%s' does not exist", profileName) + } + err := pm.serviceMgr.SetActiveProfileState(&profilemanager.ActiveProfileState{ Name: profileName, Username: androidUsername, })
245-256:sanitizeProfileNamecan return empty string.If the input contains only invalid characters (e.g.,
"!!!"or"..."), the function returns an empty string. This could cause unexpected behavior when the sanitized name is used in file paths.Consider either returning an error or providing a fallback:
-func sanitizeProfileName(name string) string { +func sanitizeProfileName(name string) (string, error) { var result strings.Builder for _, r := range name { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-' { result.WriteRune(r) } } - return result.String() + sanitized := result.String() + if sanitized == "" { + return "", fmt.Errorf("profile name '%s' contains no valid characters", name) + } + return sanitized, nil }Note: This would require updating callers to handle the error.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
client/android/client.go(3 hunks)client/android/profile_manager.go(1 hunks)client/internal/engine.go(1 hunks)client/internal/profilemanager/service.go(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
client/internal/engine.go (1)
client/internal/profilemanager/service.go (1)
NewServiceManager(82-87)
client/android/profile_manager.go (3)
client/internal/profilemanager/service.go (6)
ServiceManager(78-80)DefaultConfigPathDir(23-23)DefaultConfigPath(24-24)ActiveProfileStatePath(25-25)NewServiceManagerWithProfilesDir(91-98)ActiveProfileState(56-59)client/internal/profilemanager/config.go (2)
ReadConfig(772-774)WriteOutConfig(812-814)client/internal/profilemanager/profilemanager.go (1)
DefaultProfileName(16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Management / Unit (amd64, sqlite)
- GitHub Check: Management / Unit (amd64, postgres)
- GitHub Check: Management / Unit (amd64, mysql)
- GitHub Check: Relay / Unit (386)
- GitHub Check: Relay / Unit (amd64, -race)
- GitHub Check: Client / Unit (amd64)
- GitHub Check: Client (Docker) / Unit
- GitHub Check: Client / Unit (386)
- GitHub Check: JS / Lint
- GitHub Check: Client / Unit
- GitHub Check: Client / Unit
- GitHub Check: Client / Unit
- GitHub Check: release_ui_darwin
- GitHub Check: release_ui
- GitHub Check: release
- GitHub Check: Android / Build
- GitHub Check: iOS / Build
- GitHub Check: Linux
- GitHub Check: Darwin
- GitHub Check: Windows
🔇 Additional comments (7)
client/internal/engine.go (1)
255-270: LGTM - Platform-specific state path resolution looks correct.The logic correctly differentiates between mobile (iOS/Android) and desktop platforms. On mobile, the state file path is provided via
mobileDep.StateFilePathand created if missing. On desktop, theServiceManageris used to resolve the profile-aware state path.One minor observation: the error from
createFileis logged but doesn't prevent engine creation, which is the intended behavior per the comment. This is appropriate for resilience.client/internal/profilemanager/service.go (2)
385-392: LGTM - Clean override pattern for profiles directory.The
getConfigDirmethod provides a clean abstraction that allows the Android-specificprofilesDirto override the default behavior while maintaining backward compatibility with existing code paths.
89-98: Platform separation negates global state mutation risk.The
//go:build androidtag at line 1 ofclient/android/profile_manager.gorestricts this code to Android-only builds, andclient/server/server.gohas no build tags, meaning they never coexist in the same binary. The globalDefaultConfigPathmutation is not a practical concern since each platform receives its own isolated binary with its own initialization sequence.However, the pattern of mutating globals in constructors remains poor design practice. If this pattern is intentional for the Android use case, consider documenting why platform separation makes this safe or refactor to avoid the mutation entirely.
client/android/client.go (2)
91-128: LGTM - Clean refactor to accept paths per-call.Moving
PlatformFilesfrom the constructor to theRunmethod parameters enables profile switching by allowing different config/state paths for each run. The added logging at lines 97-98 improves debuggability.
133-161: LGTM - Consistent withRunmethod changes.The
RunWithoutLoginmethod follows the same pattern asRun, acceptingPlatformFilesas a parameter and extracting config/state paths locally. This maintains consistency across the API.client/android/profile_manager.go (2)
69-88: LGTM - Android-specific ProfileManager initialization.The constructor properly sets up Android-specific paths:
- Default config at
configDir/netbird.cfg- Profiles in
configDir/profiles/subdirectory- Active profile state at
configDir/active_profile.jsonThe use of
NewServiceManagerWithProfilesDircorrectly configures the profiles directory override.
138-146: No duplicate sanitization in this code path.
AddProfilepasses the profile name directly toserviceMgr.AddProfile, which handles sanitization internally. There is no redundant sanitization occurring.Likely an incorrect or invalid review comment.
| // LogoutProfile logs out from a profile (clears authentication) | ||
| func (pm *ProfileManager) LogoutProfile(profileName string) error { | ||
| profileName = sanitizeProfileName(profileName) | ||
|
|
||
| configPath, err := pm.getProfileConfigPath(profileName) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Check if profile exists | ||
| if _, err := os.Stat(configPath); os.IsNotExist(err) { | ||
| return fmt.Errorf("profile '%s' does not exist", profileName) | ||
| } | ||
|
|
||
| // Read current config using internal profilemanager | ||
| config, err := profilemanager.ReadConfig(configPath) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to read profile config: %w", err) | ||
| } | ||
|
|
||
| // Clear authentication by removing private key | ||
| config.PrivateKey = "" | ||
|
|
||
| // Save config using internal profilemanager | ||
| if err := profilemanager.WriteOutConfig(configPath, config); err != nil { | ||
| return fmt.Errorf("failed to save config: %w", err) | ||
| } | ||
|
|
||
| log.Infof("logged out from profile: %s", profileName) | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
ast-grep --pattern $'type Config struct {
$$$
}'Repository: netbirdio/netbird
Length of output: 12551
Clear all authentication credentials on logout, not just PrivateKey.
The LogoutProfile method only clears the PrivateKey field. The Config struct contains multiple authentication-related fields that persist after logout:
PreSharedKey(line 92)SSHKey(line 122, private SSH key in PEM format)ClientCertPathandClientCertKeyPath(lines 150-153, mTLS certificate paths)ClientCertKeyPair(line 155, mTLS certificate object)
These should be cleared to ensure a complete logout.
🤖 Prompt for AI Agents
In client/android/profile_manager.go around lines 148 to 178, LogoutProfile
currently only clears config.PrivateKey; update it to clear all
authentication-related fields (set to empty string or nil as appropriate):
PreSharedKey, SSHKey, ClientCertPath, ClientCertKeyPath, ClientCertKeyPair (and
any other auth fields on the Config struct), then write the config back with
profilemanager.WriteOutConfig and preserve existing logging/return behavior.
|



Describe your changes
Expose the profile-manager service for Android. Logout was not part of the manager service implementation. In the future, I recommend moving this logic there.
Issue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.