Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SettingsViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
3using CommunityToolkit.Mvvm.Messaging;
9using System.ComponentModel;
10using System.Globalization;
11using System.Reflection;
12using System.Security.Cryptography;
13using System.Text;
14using System.Xml;
19
21{
25 public partial class SettingsViewModel : XmppViewModel
26 {
27 private const string allowed = "Allowed";
28 private const string prohibited = "Prohibited";
29
30 private readonly bool initializing = false;
31
36 : base()
37 {
38 this.initializing = true;
39 try
40 {
41 this.CanProhibitScreenCapture = ServiceRef.PlatformSpecific.CanProhibitScreenCapture;
42 this.ScreenCaptureMode = ServiceRef.PlatformSpecific.ProhibitScreenCapture ? prohibited : allowed;
43
44 this.CanUseFingerprint = ServiceRef.PlatformSpecific.SupportsFingerprintAuthentication;
45 this.CanUseAlternativeAuthenticationMethods = this.CanUseFingerprint;
46 this.AuthenticationMethod = ServiceRef.TagProfile.AuthenticationMethod.ToString();
47 this.ApprovedAuthenticationMethod = this.AuthenticationMethod;
48
49 this.DisplayMode = CurrentDisplayMode.ToString();
50
51 // App and Hardware information
52 this.VersionNumber = AppInfo.VersionString;
53 this.BuildNumber = AppInfo.BuildString;
54 this.BuildTime = GetBuildTime();
55 this.DeviceManufactorer = DeviceInfo.Manufacturer.ToString();
56 this.DeviceModel = DeviceInfo.Model.ToString();
57 this.DevicePlatform = DeviceInfo.Platform.ToString();
58 this.DeviceVersion = DeviceInfo.Version.ToString();
59 }
60 finally
61 {
62 this.initializing = false;
63 }
64 }
65
72 internal SettingsPage? Page { get; set; }
73
74 protected override async Task OnInitialize()
75 {
76 await base.OnInitialize();
77 this.NotifyCommandsCanExecuteChanged();
78 }
79
81 protected override Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
82 {
83 return MainThread.InvokeOnMainThreadAsync(async () =>
84 {
85 await base.XmppService_ConnectionStateChanged(Sender, NewState);
86
87 this.NotifyCommandsCanExecuteChanged();
88 });
89 }
90
92 public override void SetIsBusy(bool IsBusy)
93 {
94 base.SetIsBusy(IsBusy);
95 this.NotifyCommandsCanExecuteChanged();
96 }
97
98 private void NotifyCommandsCanExecuteChanged()
99 {
100 this.RevokeCommand.NotifyCanExecuteChanged();
101 this.CompromiseCommand.NotifyCanExecuteChanged();
102 this.TransferCommand.NotifyCanExecuteChanged();
103 this.ChangePasswordCommand.NotifyCanExecuteChanged();
104 }
105
106 #region Properties
107
111 [ObservableProperty]
112 private bool canProhibitScreenCapture;
113
117 [ObservableProperty]
118 private string screenCaptureMode;
119
123 [ObservableProperty]
124 private string displayMode;
125
129 [ObservableProperty]
130 private bool restartNeeded;
131
135 [ObservableProperty]
136 private bool canUseFingerprint;
137
141 [ObservableProperty]
142 private bool canUseAlternativeAuthenticationMethods;
143
147 [ObservableProperty]
148 private string authenticationMethod;
149
153 [ObservableProperty]
154 private string approvedAuthenticationMethod;
155
159 [ObservableProperty]
160 private string versionNumber;
161
165 [ObservableProperty]
166 private string buildNumber;
167
171 [ObservableProperty]
172 private string deviceManufactorer;
173
177 [ObservableProperty]
178 private string deviceModel;
179
183 [ObservableProperty]
184 private string devicePlatform;
185
189 [ObservableProperty]
190 private string deviceVersion;
191
195 [ObservableProperty]
196 private string buildTime;
197
201 public static AppTheme CurrentDisplayMode
202 {
203 get
204 {
205 AppTheme? Result = Application.Current?.UserAppTheme;
206
207 if (!Result.HasValue || Result.Value == AppTheme.Unspecified)
208 Result = Application.Current?.PlatformAppTheme;
209
210 return Result ?? AppTheme.Unspecified;
211 }
212 }
213
217 public bool CanExecuteCommands => !this.IsBusy && this.IsConnected;
218
222 protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
223 {
224 try
225 {
226 switch (e.PropertyName)
227 {
228 case nameof(this.DisplayMode):
229 if (!this.initializing && Enum.TryParse(this.DisplayMode, out AppTheme Theme) && Theme != CurrentDisplayMode)
230 {
232 }
233 break;
234
235 case nameof(this.ScreenCaptureMode):
236 if (!this.initializing)
237 {
238 switch (this.ScreenCaptureMode)
239 {
240 case allowed:
241 await PermitScreenCapture();
242 break;
243
244 case prohibited:
245 await ProhibitScreenCapture();
246 break;
247 }
248 }
249 break;
250
251 case nameof(this.AuthenticationMethod):
252 if (!this.initializing &&
253 this.AuthenticationMethod != this.ApprovedAuthenticationMethod &&
254 Enum.TryParse(this.AuthenticationMethod, out AuthenticationMethod AuthenticationMethod))
255 {
256 if (await App.AuthenticateUser(AuthenticationPurpose.ChangeAuthenticationMethod, true))
257 {
258 ServiceRef.TagProfile.AuthenticationMethod = AuthenticationMethod;
259 this.ApprovedAuthenticationMethod = this.AuthenticationMethod;
260 }
261 else
262 {
263 this.AuthenticationMethod = this.ApprovedAuthenticationMethod;
264
265 if (this.Page is not null)
266 {
267 // Needed to propagate radio-button states, as the current version of Maui does not
268 // handle these properly (at the time of writing).
269 //
270 // TODO: Check if this has been fixed after updating Maui and related components.
271
272 switch (Enum.Parse<AuthenticationMethod>(this.AuthenticationMethod))
273 {
274 case AuthenticationMethod.Password:
275 this.Page.Fingerprint.IsChecked = false;
276 this.Page.UsePassword.IsChecked = true;
277 break;
278
279 case AuthenticationMethod.Fingerprint:
280 this.Page.UsePassword.IsChecked = false;
281 this.Page.Fingerprint.IsChecked = true;
282 break;
283 }
284 }
285 }
286 }
287 break;
288 }
289 }
290 catch (Exception ex)
291 {
292 ServiceRef.LogService.LogException(ex);
293 }
294 }
295
300 private static string GetBuildTime()
301 {
302 Assembly assembly = Assembly.GetExecutingAssembly();
303
304 const string BuildVersionMetadataPrefix = "+build";
305
306 AssemblyInformationalVersionAttribute? attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
307 if (attribute?.InformationalVersion != null)
308 {
309 string value = attribute.InformationalVersion;
310
311 int datePosition = value.IndexOf(BuildVersionMetadataPrefix, System.StringComparison.OrdinalIgnoreCase);
312 if (datePosition > 0)
313 {
314 value = value.Substring(datePosition + BuildVersionMetadataPrefix.Length);
315
316 return value;
317 }
318 }
319
320 return string.Empty;
321 }
322
323 #endregion
324
325 #region Commands
326
327 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
328 internal async Task ChangePassword()
329 {
330 try
331 {
332 //Authenticate user
333 await App.CheckUserBlocking();
334 if (await App.AuthenticateUser(AuthenticationPurpose.ChangePassword, true) == false)
335 return;
336
337 //Update the network password
338 string NewNetworkPassword = ServiceRef.CryptoService.CreateRandomPassword();
339 if (!await ServiceRef.XmppService.ChangePassword(NewNetworkPassword))
340 {
341 await ServiceRef.UiService.DisplayAlert(
342 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
343 ServiceRef.Localizer[nameof(AppResources.UnableToChangePassword)]);
344 return;
345 }
346 ServiceRef.TagProfile.SetAccount(ServiceRef.TagProfile.Account!, NewNetworkPassword, string.Empty);
347
348 //Update the local password
351
352 //Listen for completed event
353 WeakReferenceMessenger.Default.Register<RegistrationPageMessage>(this, this.HandleRegistrationPageMessage);
354 }
355 catch (Exception ex)
356 {
357 ServiceRef.LogService.LogException(ex);
358 await ServiceRef.UiService.DisplayException(ex);
359 }
360 }
361
362 private async void HandleRegistrationPageMessage(object recipient, RegistrationPageMessage msg)
363 {
364 if (msg.Step != RegistrationStep.Complete)
365 return;
366 await ServiceRef.UiService.DisplayAlert(
367 ServiceRef.Localizer[nameof(AppResources.SuccessTitle)],
368 ServiceRef.Localizer[nameof(AppResources.PasswordChanged)]);
369 WeakReferenceMessenger.Default.Unregister<RegistrationPageMessage>(this);
370 }
371
372 private static async Task PermitScreenCapture()
373 {
374 if (!ServiceRef.PlatformSpecific.CanProhibitScreenCapture)
375 return;
376
377 if (!await App.AuthenticateUser(AuthenticationPurpose.PermitScreenCapture))
378 return;
379
380 ServiceRef.PlatformSpecific.ProhibitScreenCapture = false;
381 }
382
383 private static async Task ProhibitScreenCapture()
384 {
385 if (!ServiceRef.PlatformSpecific.CanProhibitScreenCapture)
386 return;
387
388 if (!await App.AuthenticateUser(AuthenticationPurpose.ProhibitScreenCapture))
389 return;
390
391 ServiceRef.PlatformSpecific.ProhibitScreenCapture = true;
392 }
393
395 public override async Task GoBack()
396 {
397 if (this.RestartNeeded)
398 await App.Stop();
399 else
400 await base.GoBack();
401 }
402
403 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
404 private async Task Revoke()
405 {
407 return;
408
409 try
410 {
411 if (!await AreYouSure(ServiceRef.Localizer[nameof(AppResources.AreYouSureYouWantToRevokeYourLegalIdentity)]))
412 return;
413
414 if (!await App.AuthenticateUser(AuthenticationPurpose.RevokeIdentity, true))
415 return;
416
417 (bool succeeded, LegalIdentity? RevokedIdentity) = await ServiceRef.NetworkService.TryRequest(async () =>
418 {
419 try
420 {
421 return await ServiceRef.XmppService.ObsoleteLegalIdentity(ServiceRef.TagProfile.LegalIdentity.Id);
422 }
423 catch (ForbiddenException)
424 {
425 return null;
426 }
427 });
428
429 if (succeeded)
430 {
431 if (RevokedIdentity is not null)
432 await ServiceRef.TagProfile.RevokeLegalIdentity(RevokedIdentity);
433 else
437 }
438 }
439 catch (Exception ex)
440 {
441 ServiceRef.LogService.LogException(ex);
442 await ServiceRef.UiService.DisplayException(ex);
443 }
444 }
445
446 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
447 private async Task Compromise()
448 {
450 return;
451
452 try
453 {
454 if (!await AreYouSure(ServiceRef.Localizer[nameof(AppResources.AreYouSureYouWantToReportYourLegalIdentityAsCompromized)]))
455 return;
456
457 if (!await App.AuthenticateUser(AuthenticationPurpose.ReportAsCompromized, true))
458 return;
459
460 (bool succeeded, LegalIdentity? CompromisedIdentity) = await ServiceRef.NetworkService.TryRequest(
461 () => ServiceRef.XmppService.CompromiseLegalIdentity(ServiceRef.TagProfile.LegalIdentity.Id));
462
463 if (succeeded && CompromisedIdentity is not null)
464 {
465 await ServiceRef.TagProfile.CompromiseLegalIdentity(CompromisedIdentity);
467 }
468 }
469 catch (Exception ex)
470 {
471 ServiceRef.LogService.LogException(ex);
472 await ServiceRef.UiService.DisplayException(ex);
473 }
474 }
475
476 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
477 private async Task Transfer()
478 {
480 return;
481
482 try
483 {
484 string? Password = await App.InputPassword(AuthenticationPurpose.TransferIdentity);
485 if (Password is null)
486 return;
487
488 if (!await ServiceRef.UiService.DisplayAlert(
489 ServiceRef.Localizer[nameof(AppResources.Confirm)],
490 ServiceRef.Localizer[nameof(AppResources.AreYouSureYouWantToTransferYourLegalIdentity)],
491 ServiceRef.Localizer[nameof(AppResources.Yes)],
492 ServiceRef.Localizer[nameof(AppResources.No)]))
493 {
494 return;
495 }
496
497 this.SetIsBusy(true);
498
499 try
500 {
501 StringBuilder Xml = new();
502 XmlWriterSettings Settings = XML.WriterSettings(false, true);
503
504 using (XmlWriter Output = XmlWriter.Create(Xml, Settings))
505 {
506 Output.WriteStartElement("Transfer", ContractsClient.NamespaceOnboarding);
507
508 await ServiceRef.XmppService.ExportSigningKeys(Output);
509
510 Output.WriteStartElement("Pin");
511 Output.WriteAttributeString("pin", Password);
512 Output.WriteEndElement();
513
514 Output.WriteStartElement("Account", ContractsClient.NamespaceOnboarding);
515 Output.WriteAttributeString("domain", ServiceRef.TagProfile.Domain);
516 Output.WriteAttributeString("userName", ServiceRef.TagProfile.Account);
517 Output.WriteAttributeString("password", ServiceRef.TagProfile.XmppPasswordHash);
518
519 if (!string.IsNullOrEmpty(ServiceRef.TagProfile.XmppPasswordHashMethod))
520 {
521 Output.WriteAttributeString("passwordMethod", ServiceRef.TagProfile.XmppPasswordHashMethod);
522 }
523
524 Output.WriteEndElement();
525 Output.WriteEndElement();
526 }
527
528 using RandomNumberGenerator Rnd = RandomNumberGenerator.Create();
529 byte[] Data = Encoding.UTF8.GetBytes(Xml.ToString());
530 byte[] Key = new byte[16];
531 byte[] IV = new byte[16];
532
533 Rnd.GetBytes(Key);
534 Rnd.GetBytes(IV);
535
536 using Aes Aes = Aes.Create();
537 Aes.BlockSize = 128;
538 Aes.KeySize = 256;
539 Aes.Mode = CipherMode.CBC;
540 Aes.Padding = PaddingMode.PKCS7;
541
542 using ICryptoTransform Transform = Aes.CreateEncryptor(Key, IV);
543 byte[] Encrypted = Transform.TransformFinalBlock(Data, 0, Data.Length);
544
545 Xml.Clear();
546
547 using (XmlWriter Output = XmlWriter.Create(Xml, Settings))
548 {
549 Output.WriteStartElement("Info", ContractsClient.NamespaceOnboarding);
550 Output.WriteAttributeString("base64", Convert.ToBase64String(Encrypted));
551 Output.WriteAttributeString("once", "true");
552 Output.WriteAttributeString("expires", XML.Encode(DateTime.UtcNow.AddMinutes(1)));
553 Output.WriteEndElement();
554 }
555
556 XmlElement Response = await ServiceRef.XmppService.IqSetAsync(Constants.Domains.OnboardingDomain, Xml.ToString());
557
558 foreach (XmlNode N in Response.ChildNodes)
559 {
560 if (N is XmlElement Info && Info.LocalName == "Code" && Info.NamespaceURI == ContractsClient.NamespaceOnboarding)
561 {
562 string Code = XML.Attribute(Info, "code");
563 string Url = "obinfo:" + Constants.Domains.IdDomain + ":" + Code + ":" +
564 Convert.ToBase64String(Key) + ":" + Convert.ToBase64String(IV);
565
566 await ServiceRef.XmppService.AddTransferCode(Code);
567 await ServiceRef.UiService.GoToAsync(nameof(TransferIdentityPage), new TransferIdentityNavigationArgs(Url));
568 return;
569 }
570 }
571
572 await ServiceRef.UiService.DisplayAlert(
573 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
574 ServiceRef.Localizer[nameof(AppResources.UnexpectedResponse)]);
575 }
576 finally
577 {
578 this.SetIsBusy(false);
579 }
580 }
581 catch (Exception ex)
582 {
583 ServiceRef.LogService.LogException(ex);
584 await ServiceRef.UiService.DisplayException(ex);
585 }
586 }
587
588 [RelayCommand]
589 private static async Task ChangeLanguage()
590 {
591 await ServiceRef.UiService.PushAsync<SelectLanguagePopup>();
592 }
593
594 [RelayCommand]
595 private void ToggleDarkMode()
596 {
597 this.DisplayMode = "Dark";
598 }
599
600 #endregion
601 }
602}
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static Task SetRegistrationPageAsync()
Switches the application to the on-boarding experience.
Definition: App.xaml.cs:658
static Task< bool > AuthenticateUser(AuthenticationPurpose Purpose, bool Force=false)
Authenticates the user using the configured authentication method.
Definition: App.xaml.cs:981
static async Task CheckUserBlocking()
Verify if the user is blocked and show an alert
Definition: App.xaml.cs:1037
static async Task< string?> InputPassword(AuthenticationPurpose Purpose)
Asks the user to input its password. Password is verified before being returned.
Definition: App.xaml.cs:941
const string OnboardingDomain
Neuro-Access onboarding domain.
Definition: Constants.cs:258
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:103
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static ICryptoService CryptoService
Crypto service.
Definition: ServiceRef.cs:163
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
static async Task< bool > AreYouSure(string Message)
Asks the user to confirm an action.
static void GoToRegistrationStep(RegistrationStep NewStep)
Set a new registration step
A page to display when the user wants to transfer an identity.
The view model to bind to for when displaying the settings page.
override void SetIsBusy(bool IsBusy)
Sets the IsBusy property.
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
override async Task GoBack()
Method called when user wants to navigate to the previous screen.
SettingsViewModel()
Creates an instance of the SettingsViewModel class.
static AppTheme CurrentDisplayMode
Current display mode
override async void OnPropertyChanged(PropertyChangedEventArgs e)
override Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
Listens to connection state changes from the XMPP server.
bool CanExecuteCommands
Used to find out if a command can execute
A view model that holds the XMPP state.
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Definition: XML.cs:1177
Adds support for legal identities, smart contracts and signatures to an XMPP client.
const string NamespaceOnboarding
http://waher.se/schema/Onboarding/v1.xsd
The requesting entity does not possess the necessary permissions to perform an action that only certa...
AuthenticationMethod AuthenticationMethod
How the user authenticates itself with the App.
Definition: ITagProfile.cs:191
string? Account
The account name for this profile
Definition: ITagProfile.cs:101
void SetTheme(AppTheme Theme)
Sets the preferred theme.
Task CompromiseLegalIdentity(LegalIdentity compromisedIdentity)
Sets the current LegalIdentity to the compromised identity, and reverses the Step property.
string? XmppPasswordHash
A hash of the current XMPP password.
Definition: ITagProfile.cs:106
Task ClearLegalIdentity()
Revert the Set LegalIdentity
string? XmppPasswordHashMethod
The hash method used for hashing the XMPP password.
Definition: ITagProfile.cs:111
Task RevokeLegalIdentity(LegalIdentity revokedIdentity)
Sets the current LegalIdentity to the revoked identity, and reverses the Step property.
LegalIdentity? LegalIdentity
The legal identity of the current user/profile.
Definition: ITagProfile.cs:211
void SetAccount(string AccountName, string ClientPasswordHash, string ClientPasswordHashMethod)
Set the account name and password for a new account.
string? Domain
The domain this profile is connected to.
Definition: ITagProfile.cs:51
AuthenticationMethod
How the user authenticates itself with the App.
RegistrationStep
The different steps of a TAG Profile registration journey.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
class RegistrationPageMessage(RegistrationStep Step)
RegistrationPage view change message
Definition: Messages.cs:8
XmppState
State of XMPP connection.
Definition: XmppState.cs:7