Skip to content

Commit 8c8473a

Browse files
authored
[client] Add support for disabling profiles feature via command line flag (#4235)
* Add support for disabling profiles feature via command line flag * Add profiles disabling flag to service command * Refactor profile menu initialization and enhance error notifications in event handlers
1 parent e1c66a8 commit 8c8473a

File tree

9 files changed

+117
-32
lines changed

9 files changed

+117
-32
lines changed

client/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ var (
7272
anonymizeFlag bool
7373
dnsRouteInterval time.Duration
7474
lazyConnEnabled bool
75+
profilesDisabled bool
7576

7677
rootCmd = &cobra.Command{
7778
Use: "netbird",

client/cmd/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func init() {
4242
}
4343

4444
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd, svcStatusCmd, installCmd, uninstallCmd, reconfigureCmd)
45+
serviceCmd.PersistentFlags().BoolVar(&profilesDisabled, "disable-profiles", false, "Disables profiles feature. If enabled, the client will not be able to change or edit any profile.")
4546

4647
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
4748
serviceEnvDesc := `Sets extra environment variables for the service. ` +

client/cmd/service_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (p *program) Start(svc service.Service) error {
6161
}
6262
}
6363

64-
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles))
64+
serverInstance := server.New(p.ctx, util.FindFirstLogPath(logFiles), profilesDisabled)
6565
if err := serverInstance.Start(); err != nil {
6666
log.Fatalf("failed to start daemon: %v", err)
6767
}

client/cmd/testutil_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func startClientDaemon(
134134
s := grpc.NewServer()
135135

136136
server := client.New(ctx,
137-
"")
137+
"", false)
138138
if err := server.Start(); err != nil {
139139
t.Fatal(err)
140140
}

client/server/server.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
defaultRetryMultiplier = 1.7
4545

4646
errRestoreResidualState = "failed to restore residual state: %v"
47+
errProfilesDisabled = "profiles are disabled, you cannot use this feature without profiles enabled"
4748
)
4849

4950
// Server for service control.
@@ -68,7 +69,8 @@ type Server struct {
6869
persistNetworkMap bool
6970
isSessionActive atomic.Bool
7071

71-
profileManager profilemanager.ServiceManager
72+
profileManager profilemanager.ServiceManager
73+
profilesDisabled bool
7274
}
7375

7476
type oauthAuthFlow struct {
@@ -79,13 +81,14 @@ type oauthAuthFlow struct {
7981
}
8082

8183
// New server instance constructor.
82-
func New(ctx context.Context, logFile string) *Server {
84+
func New(ctx context.Context, logFile string, profilesDisabled bool) *Server {
8385
return &Server{
8486
rootCtx: ctx,
8587
logFile: logFile,
8688
persistNetworkMap: true,
8789
statusRecorder: peer.NewRecorder(""),
8890
profileManager: profilemanager.ServiceManager{},
91+
profilesDisabled: profilesDisabled,
8992
}
9093
}
9194

@@ -320,6 +323,10 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques
320323
s.mutex.Lock()
321324
defer s.mutex.Unlock()
322325

326+
if s.checkProfilesDisabled() {
327+
return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
328+
}
329+
323330
profState := profilemanager.ActiveProfileState{
324331
Name: msg.ProfileName,
325332
Username: msg.Username,
@@ -737,6 +744,11 @@ func (s *Server) switchProfileIfNeeded(profileName string, userName *string, act
737744
}
738745

739746
if profileName != activeProf.Name || username != activeProf.Username {
747+
if s.checkProfilesDisabled() {
748+
log.Errorf("profiles are disabled, you cannot use this feature without profiles enabled")
749+
return gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
750+
}
751+
740752
log.Infof("switching to profile %s for user %s", profileName, username)
741753
if err := s.profileManager.SetActiveProfileState(&profilemanager.ActiveProfileState{
742754
Name: profileName,
@@ -1069,6 +1081,10 @@ func (s *Server) AddProfile(ctx context.Context, msg *proto.AddProfileRequest) (
10691081
s.mutex.Lock()
10701082
defer s.mutex.Unlock()
10711083

1084+
if s.checkProfilesDisabled() {
1085+
return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
1086+
}
1087+
10721088
if msg.ProfileName == "" || msg.Username == "" {
10731089
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name and username must be provided")
10741090
}
@@ -1086,6 +1102,10 @@ func (s *Server) RemoveProfile(ctx context.Context, msg *proto.RemoveProfileRequ
10861102
s.mutex.Lock()
10871103
defer s.mutex.Unlock()
10881104

1105+
if s.checkProfilesDisabled() {
1106+
return nil, gstatus.Errorf(codes.Unavailable, errProfilesDisabled)
1107+
}
1108+
10891109
if msg.ProfileName == "" {
10901110
return nil, gstatus.Errorf(codes.InvalidArgument, "profile name must be provided")
10911111
}
@@ -1142,3 +1162,13 @@ func (s *Server) GetActiveProfile(ctx context.Context, msg *proto.GetActiveProfi
11421162
Username: activeProfile.Username,
11431163
}, nil
11441164
}
1165+
1166+
func (s *Server) checkProfilesDisabled() bool {
1167+
// Check if the environment variable is set to disable profiles
1168+
if s.profilesDisabled {
1169+
log.Warn("Profiles are disabled via NB_DISABLE_PROFILES environment variable")
1170+
return true
1171+
}
1172+
1173+
return false
1174+
}

client/server/server_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestConnectWithRetryRuns(t *testing.T) {
9494
t.Fatalf("failed to set active profile state: %v", err)
9595
}
9696

97-
s := New(ctx, "debug")
97+
s := New(ctx, "debug", false)
9898

9999
s.config = config
100100

@@ -151,7 +151,7 @@ func TestServer_Up(t *testing.T) {
151151
t.Fatalf("failed to set active profile state: %v", err)
152152
}
153153

154-
s := New(ctx, "console")
154+
s := New(ctx, "console", false)
155155

156156
err = s.Start()
157157
require.NoError(t, err)
@@ -227,7 +227,7 @@ func TestServer_SubcribeEvents(t *testing.T) {
227227
t.Fatalf("failed to set active profile state: %v", err)
228228
}
229229

230-
s := New(ctx, "console")
230+
s := New(ctx, "console", false)
231231

232232
err = s.Start()
233233
require.NoError(t, err)

client/ui/client_ui.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,21 @@ func (s *serviceClient) onTrayReady() {
802802

803803
profileMenuItem := systray.AddMenuItem("", "")
804804
emailMenuItem := systray.AddMenuItem("", "")
805-
s.mProfile = newProfileMenu(s.ctx, s.profileManager, *s.eventHandler, profileMenuItem, emailMenuItem, s.menuDownClick, s.menuUpClick, s.getSrvClient, s.loadSettings)
805+
806+
newProfileMenuArgs := &newProfileMenuArgs{
807+
ctx: s.ctx,
808+
profileManager: s.profileManager,
809+
eventHandler: s.eventHandler,
810+
profileMenuItem: profileMenuItem,
811+
emailMenuItem: emailMenuItem,
812+
downClickCallback: s.menuDownClick,
813+
upClickCallback: s.menuUpClick,
814+
getSrvClientCallback: s.getSrvClient,
815+
loadSettingsCallback: s.loadSettings,
816+
app: s.app,
817+
}
818+
819+
s.mProfile = newProfileMenu(*newProfileMenuArgs)
806820

807821
systray.AddSeparator()
808822
s.mUp = systray.AddMenuItem("Connect", "Connect")

client/ui/event_handler.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,35 +86,60 @@ func (h *eventHandler) handleDisconnectClick() {
8686

8787
func (h *eventHandler) handleAllowSSHClick() {
8888
h.toggleCheckbox(h.client.mAllowSSH)
89-
h.updateConfigWithErr()
89+
if err := h.updateConfigWithErr(); err != nil {
90+
h.toggleCheckbox(h.client.mAllowSSH) // revert checkbox state on error
91+
log.Errorf("failed to update config: %v", err)
92+
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update SSH settings"))
93+
}
94+
9095
}
9196

9297
func (h *eventHandler) handleAutoConnectClick() {
9398
h.toggleCheckbox(h.client.mAutoConnect)
94-
h.updateConfigWithErr()
99+
if err := h.updateConfigWithErr(); err != nil {
100+
h.toggleCheckbox(h.client.mAutoConnect) // revert checkbox state on error
101+
log.Errorf("failed to update config: %v", err)
102+
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update auto-connect settings"))
103+
}
95104
}
96105

97106
func (h *eventHandler) handleRosenpassClick() {
98107
h.toggleCheckbox(h.client.mEnableRosenpass)
99-
h.updateConfigWithErr()
108+
if err := h.updateConfigWithErr(); err != nil {
109+
h.toggleCheckbox(h.client.mEnableRosenpass) // revert checkbox state on error
110+
log.Errorf("failed to update config: %v", err)
111+
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update Rosenpass settings"))
112+
}
100113
}
101114

102115
func (h *eventHandler) handleLazyConnectionClick() {
103116
h.toggleCheckbox(h.client.mLazyConnEnabled)
104-
h.updateConfigWithErr()
117+
if err := h.updateConfigWithErr(); err != nil {
118+
h.toggleCheckbox(h.client.mLazyConnEnabled) // revert checkbox state on error
119+
log.Errorf("failed to update config: %v", err)
120+
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update lazy connection settings"))
121+
}
105122
}
106123

107124
func (h *eventHandler) handleBlockInboundClick() {
108125
h.toggleCheckbox(h.client.mBlockInbound)
109-
h.updateConfigWithErr()
126+
if err := h.updateConfigWithErr(); err != nil {
127+
h.toggleCheckbox(h.client.mBlockInbound) // revert checkbox state on error
128+
log.Errorf("failed to update config: %v", err)
129+
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update block inbound settings"))
130+
}
110131
}
111132

112133
func (h *eventHandler) handleNotificationsClick() {
113134
h.toggleCheckbox(h.client.mNotifications)
114-
if h.client.eventManager != nil {
135+
if err := h.updateConfigWithErr(); err != nil {
136+
h.toggleCheckbox(h.client.mNotifications) // revert checkbox state on error
137+
log.Errorf("failed to update config: %v", err)
138+
h.client.app.SendNotification(fyne.NewNotification("Error", "Failed to update notifications settings"))
139+
} else if h.client.eventManager != nil {
115140
h.client.eventManager.SetNotificationsEnabled(h.client.mNotifications.Checked())
116141
}
117-
h.updateConfigWithErr()
142+
118143
}
119144

120145
func (h *eventHandler) handleAdvancedSettingsClick() {
@@ -166,10 +191,12 @@ func (h *eventHandler) toggleCheckbox(item *systray.MenuItem) {
166191
}
167192
}
168193

169-
func (h *eventHandler) updateConfigWithErr() {
194+
func (h *eventHandler) updateConfigWithErr() error {
170195
if err := h.client.updateConfig(); err != nil {
171-
log.Errorf("failed to update config: %v", err)
196+
return err
172197
}
198+
199+
return nil
173200
}
174201

175202
func (h *eventHandler) runSelfCommand(ctx context.Context, command, arg string) {

client/ui/profile.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ type profileMenu struct {
334334
mu sync.Mutex
335335
ctx context.Context
336336
profileManager *profilemanager.ProfileManager
337-
eventHandler eventHandler
337+
eventHandler *eventHandler
338338
profileMenuItem *systray.MenuItem
339339
emailMenuItem *systray.MenuItem
340340
profileSubItems []*subItem
@@ -344,24 +344,34 @@ type profileMenu struct {
344344
upClickCallback func() error
345345
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error)
346346
loadSettingsCallback func()
347+
app fyne.App
347348
}
348349

349-
func newProfileMenu(ctx context.Context, profileManager *profilemanager.ProfileManager,
350+
type newProfileMenuArgs struct {
351+
ctx context.Context
352+
profileManager *profilemanager.ProfileManager
353+
eventHandler *eventHandler
354+
profileMenuItem *systray.MenuItem
355+
emailMenuItem *systray.MenuItem
356+
downClickCallback func() error
357+
upClickCallback func() error
358+
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error)
359+
loadSettingsCallback func()
360+
app fyne.App
361+
}
350362

351-
eventHandler eventHandler, profileMenuItem, emailMenuItem *systray.MenuItem,
352-
downClickCallback, upClickCallback func() error,
353-
getSrvClientCallback func(timeout time.Duration) (proto.DaemonServiceClient, error),
354-
loadSettingsCallback func()) *profileMenu {
363+
func newProfileMenu(args newProfileMenuArgs) *profileMenu {
355364
p := profileMenu{
356-
ctx: ctx,
357-
profileManager: profileManager,
358-
eventHandler: eventHandler,
359-
profileMenuItem: profileMenuItem,
360-
emailMenuItem: emailMenuItem,
361-
downClickCallback: downClickCallback,
362-
upClickCallback: upClickCallback,
363-
getSrvClientCallback: getSrvClientCallback,
364-
loadSettingsCallback: loadSettingsCallback,
365+
ctx: args.ctx,
366+
profileManager: args.profileManager,
367+
eventHandler: args.eventHandler,
368+
profileMenuItem: args.profileMenuItem,
369+
emailMenuItem: args.emailMenuItem,
370+
downClickCallback: args.downClickCallback,
371+
upClickCallback: args.upClickCallback,
372+
getSrvClientCallback: args.getSrvClientCallback,
373+
loadSettingsCallback: args.loadSettingsCallback,
374+
app: args.app,
365375
}
366376

367377
p.emailMenuItem.Disable()
@@ -479,6 +489,8 @@ func (p *profileMenu) refresh() {
479489
})
480490
if err != nil {
481491
log.Errorf("failed to switch profile: %v", err)
492+
// show notification dialog
493+
p.app.SendNotification(fyne.NewNotification("Error", "Failed to switch profile"))
482494
return
483495
}
484496

0 commit comments

Comments
 (0)