1using System.Collections.ObjectModel;
2using System.ComponentModel;
3using System.Globalization;
4using System.Security.Cryptography;
7using CommunityToolkit.Mvvm.ComponentModel;
8using CommunityToolkit.Mvvm.Input;
9using Microsoft.Maui.Controls.Shapes;
32 await base.OnInitialize();
34 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
35 LocalizationManager.Current.PropertyChanged += this.Localization_Changed;
41 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
42 LocalizationManager.Current.PropertyChanged -= this.Localization_Changed;
44 await base.OnDispose();
50 await base.DoAssignProperties();
56 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
58 base.OnPropertyChanged(e);
60 switch (e.PropertyName)
74 case nameof(this.IsBusy):
75 this.ContinueCommand.NotifyCanExecuteChanged();
76 this.ScanQrCodeCommand.NotifyCanExecuteChanged();
85 private string domainName =
string.Empty;
92 private string localizedName =
string.Empty;
99 private string localizedDescription =
string.Empty;
134 private async
void TagProfile_Changed(
object? Sender, PropertyChangedEventArgs e)
137 await this.SetDomainName();
140 private async
void Localization_Changed(
object? Sender, PropertyChangedEventArgs e)
142 await this.SetDomainName();
145 private async Task SetDomainName()
149 this.DomainName =
string.Empty;
150 this.LocalizedName =
string.Empty;
151 this.LocalizedDescription =
string.Empty;
159 Uri DomainInfo =
new(
"https://" + this.DomainName +
"/Agent/Account/DomainInfo");
162 if (AcceptLanguage !=
"en")
163 AcceptLanguage +=
";q=1,en;q=0.9";
166 new KeyValuePair<string, string>(
"Accept",
"application/json"),
167 new KeyValuePair<string, string>(
"Accept-Language", AcceptLanguage),
168 new KeyValuePair<string, string>(
"Accept-Encoding",
"0"));
169 if (Result is Dictionary<string, object> Response)
171 if (Response.TryGetValue(
"humanReadableName", out
object? Obj) && Obj is
string LocalizedName)
172 this.LocalizedName = LocalizedName;
174 if (Response.TryGetValue(
"humanReadableDescription", out Obj) && Obj is
string LocalizedDescription)
175 this.LocalizedDescription = LocalizedDescription;
184 public bool CanScanQrCode => !this.IsBusy;
186 public bool CanContinue => !this.IsBusy;
190 private void Continue()
196 private static async Task ServiceProviderInfo()
205 private async Task SelectedServiceProviderInfo()
207 string title = this.LocalizedName;
208 string message = this.LocalizedDescription;
214 private static void UndoSelection()
219 [RelayCommand(CanExecute = nameof(CanScanQrCode))]
220 private async Task ScanQrCode()
222 string? Url = await Services.UI.QR.QrCode.ScanQrCode(nameof(AppResources.QrPageTitleScanInvitation),
225 if (
string.IsNullOrEmpty(Url))
233 string[] Parts = Url.Split(
':');
235 if (Parts.Length != 5)
245 string Domain = Parts[1];
246 string Code = Parts[2];
247 string KeyStr = Parts[3];
248 string IVStr = Parts[4];
254 Uri =
new Uri(
"https://" + Domain +
"/Onboarding/GetInfo");
275 new KeyValuePair<string, string>(
"Accept",
"text/plain"));
279 EncryptedStr = (string)Decoded;
294 byte[] Key = Convert.FromBase64String(KeyStr);
295 byte[] IV = Convert.FromBase64String(IVStr);
296 byte[] Encrypted = Convert.FromBase64String(EncryptedStr);
298 using Aes Aes = Aes.Create();
301 Aes.Mode = CipherMode.CBC;
302 Aes.Padding = PaddingMode.PKCS7;
304 using ICryptoTransform Decryptor = Aes.CreateDecryptor(Key, IV);
305 byte[] Decrypted = Decryptor.TransformFinalBlock(Encrypted, 0, Encrypted.Length);
306 string Xml = Encoding.UTF8.GetString(Decrypted);
308 XmlDocument Doc =
new()
310 PreserveWhitespace =
true
316 throw new Exception(
"Invalid Invitation XML");
318 LinkedList<XmlElement> ToProcess =
new();
319 ToProcess.AddLast(Doc.DocumentElement);
321 bool AccountDone =
false;
322 XmlElement? LegalIdDefinition =
null;
325 while (ToProcess.First is not
null)
327 XmlElement E = ToProcess.First.Value;
328 ToProcess.RemoveFirst();
337 await SelectDomain(Domain, KeyStr, Secret);
348 string PasswordMethod =
XML.
Attribute(E,
"passwordMethod");
356 await SelectDomain(Domain,
string.Empty,
string.Empty);
358 if (!await this.ConnectToAccount(UserName, Password, PasswordMethod,
string.Empty, LegalIdDefinition, Pin ??
string.Empty))
361 throw new Exception(
"Invalid account.");
364 LegalIdDefinition =
null;
369 LegalIdDefinition = E;
377 foreach (XmlNode N
in E.ChildNodes)
379 if (N is XmlElement E2)
381 ToProcess.AddLast(E2);
387 throw new Exception(
"Invalid Invitation XML");
391 if (LegalIdDefinition is not
null)
415 private static async Task SelectDomain(
string Domain,
string Key,
string Secret)
417 bool DefaultConnectivity;
421 (
string HostName,
int PortNumber,
bool IsIpAddress) = await
ServiceRef.
NetworkService.LookupXmppHostnameAndPort(Domain);
427 DefaultConnectivity =
false;
434 private async Task<bool> ConnectToAccount(
string AccountName,
string Password,
string PasswordMethod,
string LegalIdentityJid, XmlElement? LegalIdDefinition,
string Pin)
440 DateTime now = DateTime.Now;
444 bool serviceDiscoverySucceeded;
452 serviceDiscoverySucceeded =
true;
457 bool DestroyContractsClient =
false;
463 DestroyContractsClient =
true;
468 if (LegalIdDefinition is not
null)
477 if ((
string.IsNullOrEmpty(LegalIdentityJid) ||
string.Compare(LegalIdentityJid, Identity.
Id, StringComparison.OrdinalIgnoreCase) == 0) &&
480 Identity.
From <= now &&
481 Identity.
To >= now &&
488 approvedIdentity = Identity;
492 createdIdentity ??= Identity;
510 LegalIdentity? selectedIdentity = approvedIdentity ?? createdIdentity;
514 if (selectedIdentity is not
null)
517 SelectedId = selectedIdentity.
Id;
522 SelectedId =
string.Empty;
525 if (!
string.IsNullOrEmpty(Pin))
527 ServiceRef.TagProfile.LocalPassword = Pin;
532 if (Identity.
Id == SelectedId)
537 switch (Identity.
State)
548 if (DestroyContractsClient)
559 (
bool succeeded,
string? errorMessage,
string[]? alternatives) = await
ServiceRef.
XmppService.TryConnectAndConnectToAccount(
562 typeof(
App).Assembly, OnConnected);
568 errorMessage ??
string.Empty,
The Application class, representing an instance of the Neuro-Access app.
static LanguageInfo SelectedLanguage
Selected language.
const string Default
The default language code.
const string Onboarding
Onboarding URI Scheme (obinfo)
static ? string GetScheme(string Url)
Gets the predefined scheme from an IoT Code
A set of never changing property constants and helpful values.
Base class that references services in the app.
static ILogService LogService
Log service.
static INetworkService NetworkService
Network service.
static IUiService UiService
Service serializing and managing UI-related tasks.
static ITagProfile TagProfile
TAG Profile service.
static IStringLocalizer Localizer
Localization service
static IXmppService XmppService
The XMPP service for XMPP communication.
bool HasLocalizedDescription
The localized intro text to display to the user for explaining what 'choose account' is for.
bool HasLocalizedName
The localized intro text to display to the user for explaining what 'choose account' is for.
static bool IsAccountCreated
If App has an XMPP account defined.
override async Task DoAssignProperties()
override async Task OnInitialize()
override async Task OnDispose()
Static class managing encoding and decoding of internet content.
static Task< object > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
static Task< object > GetAsync(Uri Uri, params KeyValuePair< string, string >[] Headers)
Gets a resource, given its URI.
static Task< object > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Adds support for legal identities, smart contracts and signatures to an XMPP client.
Task< bool > ImportKeys(string Xml)
Imports keys
Task< LegalIdentity > ObsoleteLegalIdentityAsync(string LegalIdentityId)
Obsoletes one of the legal identities of the account, given its ID.
Task< bool > HasPrivateKey(LegalIdentity Identity)
Checks if the private key of a legal identity is available. Private keys are required to be able to s...
const string NamespaceOnboarding
http://waher.se/schema/Onboarding/v1.xsd
Task< LegalIdentity[]> GetLegalIdentitiesAsync()
Gets legal identities registered with the account.
override void Dispose()
Disposes of the extension.
bool HasClientPublicKey
If the identity has a client public key
DateTime From
From what point in time the legal identity is valid.
DateTime To
To what point in time the legal identity is valid.
IdentityState State
Current state of identity
string Id
ID of the legal identity
bool ValidateClientSignature()
Validates the client signature of the legal identity
bool HasClientSignature
If the identity has a client signature
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
string PasswordHashMethod
Password hash method.
bool TryGetExtension(Type Type, out IXmppExtension Extension)
Tries to get a registered extension of a specific type from the client.
string PasswordHash
Hash value of password. Depends on method used to authenticate user.
Class containing credentials for an XMPP client connection.
const int DefaultPort
Default XMPP Server port.
void UndoDomainSelection()
Reverses the SetDomain to the Initial* values.
string? Account
The account name for this profile
void SetDomain(string DomainName, bool DefaultXmppConnectivity, string Key, string Secret)
Set the domain name to connect to.
string? ApiKey
API Key, for creating new account.
string? LegalJid
The Jabber Legal JID for this user/profile.
string? ApiSecret
API Secret, for creating new account.
bool DefaultXmppConnectivity
If connecting to the domain can be done using default parameters (host=domain, default c2s port).
bool NeedsUpdating()
Returns true if the current ITagProfile needs to have its values updated, false otherwise.
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.
Task SetAccountAndLegalIdentity(string AccountName, string ClientPasswordHash, string ClientPasswordHashMethod, LegalIdentity Identity)
Set the account name and password for an existing account.
RegistrationStep
The different steps of a TAG Profile registration journey.
IdentityState
Lists recognized legal identity states.