@@ -17,6 +17,7 @@ use cosmic_settings_subscriptions::network_manager::{
17
17
} ;
18
18
use futures:: { FutureExt , StreamExt } ;
19
19
use indexmap:: IndexMap ;
20
+ use secure_string:: SecureString ;
20
21
use slab:: Slab ;
21
22
use zbus:: zvariant:: ObjectPath ;
22
23
@@ -29,6 +30,10 @@ pub enum Message {
29
30
Activate ( ConnectionId ) ,
30
31
/// Add a network connection
31
32
AddNetwork ,
33
+ /// Cancels an active dialog.
34
+ CancelDialog ,
35
+ /// Connect to a VPN with the given username and password
36
+ ConnectWithPassword ,
32
37
/// Deactivate a connection.
33
38
Deactivate ( ConnectionId ) ,
34
39
/// An error occurred.
@@ -44,16 +49,22 @@ pub enum Message {
44
49
tokio:: sync:: mpsc:: Sender < crate :: pages:: Message > ,
45
50
) ,
46
51
) ,
52
+ /// Updates the password text input
53
+ PasswordUpdate ( String ) ,
47
54
/// Refresh devices and their connection profiles
48
55
Refresh ,
49
56
/// Remove a connection profile
50
57
RemoveProfile ( ConnectionId ) ,
51
58
/// Opens settings page for the access point.
52
59
Settings ( ConnectionId ) ,
60
+ /// Toggles visibility of password input.
61
+ TogglePasswordVisibility ,
53
62
/// Update NetworkManagerState
54
63
UpdateState ( NetworkManagerState ) ,
55
64
/// Update the devices lists
56
65
UpdateDevices ( Vec < network_manager:: devices:: DeviceInfo > ) ,
66
+ /// Updates the username text input
67
+ UsernameUpdate ( String ) ,
57
68
/// Display more options for an access point
58
69
ViewMore ( Option < ConnectionId > ) ,
59
70
}
@@ -74,7 +85,56 @@ impl From<Message> for crate::pages::Message {
74
85
struct VpnConnectionSettings {
75
86
path : ObjectPath < ' static > ,
76
87
id : String ,
77
- connection_type : String ,
88
+ username : Option < String > ,
89
+ connection_type : Option < ConnectionType > ,
90
+ password_flag : Option < PasswordFlag > ,
91
+ }
92
+
93
+ impl VpnConnectionSettings {
94
+ fn password_required ( & self ) -> bool {
95
+ self . connection_type . as_ref ( ) . map_or ( false , |ct| match ct {
96
+ ConnectionType :: Password => true ,
97
+ ConnectionType :: Unknown => false ,
98
+ } ) && self
99
+ . password_flag
100
+ . as_ref ( )
101
+ . map_or ( false , |flag| match flag {
102
+ PasswordFlag :: NotRequired => false ,
103
+ _ => true ,
104
+ } )
105
+ }
106
+ }
107
+
108
+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
109
+ enum ConnectionType {
110
+ Password ,
111
+ Unknown ,
112
+ }
113
+
114
+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
115
+ enum PasswordFlag {
116
+ /// The system is responsible for providing and storing this secret.
117
+ None = 0 ,
118
+ /// A user-session secret agent is responsible for providing and storing
119
+ /// this secret; when it is required, agents will be asked to provide it.
120
+ AgentOwned = 1 ,
121
+ /// This secret should not be saved but should be requested from the user
122
+ /// each time it is required. This flag should be used for One-Time-Pad
123
+ /// secrets, PIN codes from hardware tokens, or if the user simply does not
124
+ /// want to save the secret.
125
+ NotSaved = 2 ,
126
+ /// in some situations it cannot be automatically determined that a secret is required or not. This flag hints that the secret is not required and should not be requested from the user.
127
+ NotRequired = 4 ,
128
+ }
129
+
130
+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
131
+ enum VpnDialog {
132
+ Password {
133
+ path : ObjectPath < ' static > ,
134
+ username : String ,
135
+ password : SecureString ,
136
+ password_hidden : bool ,
137
+ } ,
78
138
}
79
139
80
140
#[ derive( Debug ) ]
@@ -89,6 +149,7 @@ pub struct NmState {
89
149
pub struct Page {
90
150
nm_task : Option < tokio:: sync:: oneshot:: Sender < ( ) > > ,
91
151
nm_state : Option < NmState > ,
152
+ dialog : Option < VpnDialog > ,
92
153
view_more_popup : Option < ConnectionId > ,
93
154
known_connections : IndexMap < UUID , VpnConnectionSettings > ,
94
155
/// Withhold device update if the view more popup is shown.
@@ -113,7 +174,50 @@ impl page::Page<crate::pages::Message> for Page {
113
174
Some ( vec ! [ sections. insert( devices_view( ) ) ] )
114
175
}
115
176
116
- fn header_view ( & self ) -> Option < cosmic:: Element < ' _ , crate :: pages:: Message > > {
177
+ fn dialog ( & self ) -> Option < Element < crate :: pages:: Message > > {
178
+ self . dialog . as_ref ( ) . map ( |dialog| match dialog {
179
+ VpnDialog :: Password {
180
+ path,
181
+ username,
182
+ password,
183
+ password_hidden,
184
+ } => {
185
+ let username = widget:: text_input ( fl ! ( "username" ) , username. as_str ( ) )
186
+ . on_input ( Message :: UsernameUpdate ) ;
187
+
188
+ let password = widget:: text_input:: secure_input (
189
+ fl ! ( "password" ) ,
190
+ password. unsecure ( ) ,
191
+ Some ( Message :: TogglePasswordVisibility ) ,
192
+ * password_hidden,
193
+ )
194
+ . on_input ( Message :: PasswordUpdate )
195
+ . on_submit ( Message :: ConnectWithPassword ) ;
196
+
197
+ let controls = widget:: column:: with_capacity ( 2 )
198
+ . spacing ( 12 )
199
+ . push ( username)
200
+ . push ( password)
201
+ . apply ( Element :: from) ;
202
+
203
+ let primary_action = widget:: button:: suggested ( fl ! ( "connect" ) )
204
+ . on_press ( Message :: ConnectWithPassword ) ;
205
+
206
+ let secondary_action =
207
+ widget:: button:: standard ( fl ! ( "cancel" ) ) . on_press ( Message :: CancelDialog ) ;
208
+
209
+ widget:: dialog ( fl ! ( "auth-dialog" ) )
210
+ . body ( fl ! ( "auth-dialog" , "vpn-description" ) )
211
+ . control ( controls)
212
+ . primary_action ( primary_action)
213
+ . secondary_action ( secondary_action)
214
+ . apply ( Element :: from)
215
+ . map ( crate :: pages:: Message :: Vpn )
216
+ }
217
+ } )
218
+ }
219
+
220
+ fn header_view ( & self ) -> Option < Element < ' _ , crate :: pages:: Message > > {
117
221
Some (
118
222
widget:: button:: standard ( fl ! ( "add-network" ) )
119
223
. on_press ( Message :: AddNetwork )
@@ -150,6 +254,7 @@ impl page::Page<crate::pages::Message> for Page {
150
254
self . nm_state = None ;
151
255
self . withheld_active_conns = None ;
152
256
self . withheld_devices = None ;
257
+ self . dialog = None ;
153
258
154
259
if let Some ( cancel) = self . nm_task . take ( ) {
155
260
_ = cancel. send ( ( ) ) ;
@@ -236,10 +341,19 @@ impl Page {
236
341
237
342
if let Some ( NmState { ref sender, .. } ) = self . nm_state {
238
343
if let Some ( settings) = self . known_connections . get ( & uuid) {
239
- _ = sender. unbounded_send ( network_manager:: Request :: Activate (
240
- ObjectPath :: from_static_str_unchecked ( "/" ) ,
241
- settings. path . clone ( ) ,
242
- ) ) ;
344
+ if settings. password_required ( ) {
345
+ self . dialog = Some ( VpnDialog :: Password {
346
+ path : settings. path . clone ( ) ,
347
+ username : settings. username . clone ( ) . unwrap_or_default ( ) ,
348
+ password : SecureString :: from ( "" ) ,
349
+ password_hidden : true ,
350
+ } ) ;
351
+ } else {
352
+ _ = sender. unbounded_send ( network_manager:: Request :: Activate (
353
+ ObjectPath :: from_static_str_unchecked ( "/" ) ,
354
+ settings. path . clone ( ) ,
355
+ ) ) ;
356
+ }
243
357
}
244
358
}
245
359
}
@@ -290,6 +404,64 @@ impl Page {
290
404
}
291
405
}
292
406
407
+ Message :: PasswordUpdate ( pass) => {
408
+ if let Some ( VpnDialog :: Password {
409
+ ref mut password, ..
410
+ } ) = self . dialog
411
+ {
412
+ * password = SecureString :: from ( pass) ;
413
+ }
414
+ }
415
+
416
+ Message :: ConnectWithPassword => {
417
+ let Some ( NmState { ref mut sender, .. } ) = self . nm_state else {
418
+ return Command :: none ( ) ;
419
+ } ;
420
+
421
+ let Some ( dialog) = self . dialog . take ( ) else {
422
+ return Command :: none ( ) ;
423
+ } ;
424
+
425
+ match dialog {
426
+ VpnDialog :: Password {
427
+ path,
428
+ username,
429
+ password,
430
+ ..
431
+ } => {
432
+ _ = sender. unbounded_send ( network_manager:: Request :: ActivateWithPassword (
433
+ ObjectPath :: from_static_str_unchecked ( "/" ) ,
434
+ path,
435
+ username,
436
+ password,
437
+ ) ) ;
438
+ }
439
+ }
440
+ }
441
+
442
+ Message :: UsernameUpdate ( user) => {
443
+ if let Some ( VpnDialog :: Password {
444
+ ref mut username, ..
445
+ } ) = self . dialog
446
+ {
447
+ * username = user;
448
+ }
449
+ }
450
+
451
+ Message :: CancelDialog => {
452
+ self . dialog = None ;
453
+ }
454
+
455
+ Message :: TogglePasswordVisibility => {
456
+ if let Some ( VpnDialog :: Password {
457
+ ref mut password_hidden,
458
+ ..
459
+ } ) = self . dialog
460
+ {
461
+ * password_hidden = !* password_hidden;
462
+ }
463
+ }
464
+
293
465
Message :: Error ( why) => {
294
466
tracing:: error!( why, "error in VPN settings page" ) ;
295
467
}
@@ -561,14 +733,42 @@ fn connection_settings(conn: zbus::Connection) -> Command<crate::app::Message> {
561
733
let id = connection. get ( "id" ) ?. downcast_ref :: < String > ( ) . ok ( ) ?;
562
734
let uuid = connection. get ( "uuid" ) ?. downcast_ref :: < String > ( ) . ok ( ) ?;
563
735
564
- let connection_type = vpn
736
+ let ( username , connection_type, password_flag ) = vpn
565
737
. get ( "data" )
566
738
. and_then ( |data| data. downcast_ref :: < zbus:: zvariant:: Dict > ( ) . ok ( ) )
567
- . and_then ( |dict| {
568
- dict. get :: < String , String > ( & String :: from ( "connection-type" ) )
739
+ . map ( |dict| {
740
+ let ( mut username, mut connection_type, mut password_flag) =
741
+ ( None , None , None ) ;
742
+
743
+ username = dict
744
+ . get :: < String , String > ( & String :: from ( "username" ) )
745
+ . ok ( )
746
+ . flatten ( )
747
+ . filter ( |value| !value. is_empty ( ) ) ;
748
+
749
+ if let Some ( "password" ) = dict
750
+ . get :: < String , String > ( & String :: from ( "connection-type" ) )
569
751
. ok ( )
752
+ . flatten ( )
753
+ . as_deref ( )
754
+ {
755
+ connection_type = Some ( ConnectionType :: Password ) ;
756
+
757
+ password_flag = dict
758
+ . get :: < String , String > ( & String :: from ( "password-flags" ) )
759
+ . ok ( )
760
+ . flatten ( )
761
+ . and_then ( |value| match value. as_str ( ) {
762
+ "0" => Some ( PasswordFlag :: None ) ,
763
+ "1" => Some ( PasswordFlag :: AgentOwned ) ,
764
+ "2" => Some ( PasswordFlag :: NotSaved ) ,
765
+ "4" => Some ( PasswordFlag :: NotRequired ) ,
766
+ _ => None ,
767
+ } ) ;
768
+ }
769
+
770
+ ( username, connection_type, password_flag)
570
771
} )
571
- . flatten ( )
572
772
. unwrap_or_default ( ) ;
573
773
574
774
let path = conn. inner ( ) . path ( ) . to_owned ( ) ;
@@ -579,6 +779,8 @@ fn connection_settings(conn: zbus::Connection) -> Command<crate::app::Message> {
579
779
path,
580
780
id,
581
781
connection_type,
782
+ password_flag,
783
+ username,
582
784
} ,
583
785
) )
584
786
} )
0 commit comments