Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
OnboardingLink.cs
2using System.Text;
3using System.Xml;
4using CommunityToolkit.Mvvm.Messaging;
10using Waher.Content;
15
17{
22 {
27 {
28 }
29
35 public Grade Supports(Uri Link)
36 {
37 return string.Equals(Link.Scheme, Constants.UriSchemes.Onboarding, StringComparison.OrdinalIgnoreCase) ? Grade.Ok : Grade.NotAtAll;
38 }
39
41 public async Task<bool> TryOpenLink(Uri Link, bool ShowErrorIfUnable)
42 {
43 try
44 {
45
46 if (ServiceRef.TagProfile.Step == Services.Tag.RegistrationStep.GetStarted || ServiceRef.TagProfile.Step == Services.Tag.RegistrationStep.ContactSupport)
47 {
48 WeakReferenceMessenger.Default.Send(new OnboardingLinkProcessingMessage(true));
49 await this.ScanQrCode(Link.ToString());
50 return true;
51 }
52 }
53 catch (Exception Ex)
54 {
55 ServiceRef.LogService.LogException(Ex);
56 }
57
58 if (ShowErrorIfUnable)
59 {
60 await ServiceRef.UiService.DisplayAlert(
63 }
64
65 return false;
66 }
67
68 private async Task ScanQrCode(string Url)
69 {
70 if (string.IsNullOrEmpty(Url))
71 return;
72
73 string Scheme = Constants.UriSchemes.GetScheme(Url) ?? string.Empty;
74
75 // Notify UI that onboarding processing has started. This makes the GetStarted view show its loading indicator
76 // even when the deep link is opened from the OS camera and the app resumes from background on iOS.
77 WeakReferenceMessenger.Default.Send(new OnboardingLinkProcessingMessage(true));
78
79 if (!string.Equals(Scheme, Constants.UriSchemes.Onboarding, StringComparison.OrdinalIgnoreCase))
80 {
81 WeakReferenceMessenger.Default.Send(new OnboardingLinkProcessingMessage(false));
82 return;
83 }
84
85 string[] Parts = Url.Split(':');
86
87 if (Parts.Length != 5)
88 {
89 await ServiceRef.UiService.DisplayAlert(
93
94 CommunityToolkit.Mvvm.Messaging.WeakReferenceMessenger.Default.Send(new OnboardingLinkProcessingMessage(false));
95 return;
96 }
97
98 string Domain = Parts[1];
99 string Code = Parts[2];
100 string KeyStr = Parts[3];
101 string IVStr = Parts[4];
102 string EncryptedStr;
103 Uri Uri;
104
105 try
106 {
107 Uri = new Uri("https://" + Domain + "/Onboarding/GetInfo");
108 }
109 catch (Exception Ex)
110 {
111 ServiceRef.LogService.LogException(Ex);
112
113 await ServiceRef.UiService.DisplayAlert(
117 CommunityToolkit.Mvvm.Messaging.WeakReferenceMessenger.Default.Send(new OnboardingLinkProcessingMessage(false));
118 return;
119 }
120
121 try
122 {
123 ContentBinaryResponse Response = await InternetContent.PostAsync(Uri, Encoding.ASCII.GetBytes(Code), "text/plain",
124 null, // Certificate
125 App.ValidateCertificateCallback, // RemoteCertificateValidator
126 new KeyValuePair<string, string>("Accept", "text/plain"));
127 Response.AssertOk();
128
129 ContentResponse Decoded = await InternetContent.DecodeAsync(Response.ContentType, Response.Encoded, Uri);
130 Decoded.AssertOk();
131
132 EncryptedStr = (string)Decoded.Decoded;
133 }
134 catch (Exception Ex)
135 {
136 ServiceRef.LogService.LogException(Ex);
137
138 await ServiceRef.UiService.DisplayAlert(
142 return;
143 }
144
145 try
146 {
147 byte[] Key = Convert.FromBase64String(KeyStr);
148 byte[] IV = Convert.FromBase64String(IVStr);
149 byte[] Encrypted = Convert.FromBase64String(EncryptedStr);
150
151 using Aes Aes = Aes.Create();
152 Aes.BlockSize = 128;
153 Aes.KeySize = 256;
154 Aes.Mode = CipherMode.CBC;
155 Aes.Padding = PaddingMode.PKCS7;
156
157 using ICryptoTransform Decryptor = Aes.CreateDecryptor(Key, IV);
158 byte[] Decrypted = Decryptor.TransformFinalBlock(Encrypted, 0, Encrypted.Length);
159 string Xml = Encoding.UTF8.GetString(Decrypted);
160
161 XmlDocument Doc = new()
162 {
163 PreserveWhitespace = true
164 };
165
166 Doc.LoadXml(Xml);
167
168 if ((Doc.DocumentElement is null) || (Doc.DocumentElement.NamespaceURI != ContractsClient.NamespaceOnboarding))
169 throw new Exception("Invalid Invitation XML");
170
171 LinkedList<XmlElement> ToProcess = new();
172 ToProcess.AddLast(Doc.DocumentElement);
173
174 bool AccountDone = false;
175 XmlElement? LegalIdDefinition = null;
176 string? Pin = null;
177
178 while (ToProcess.First is not null)
179 {
180 XmlElement E = ToProcess.First.Value;
181 ToProcess.RemoveFirst();
182 switch (E.LocalName)
183 {
184
185 case "ApiKey":
186 KeyStr = XML.Attribute(E, "key");
187 string Secret = XML.Attribute(E, "secret");
188 Domain = XML.Attribute(E, "domain");
189
190 await SelectDomain(Domain, KeyStr, Secret);
191
192 await ServiceRef.UiService.DisplayAlert(
196
198 if (Url is not null)
199 {
200 Args = new OnboardingNavigationArgs(OnboardingScenario.FullSetup, Url);
201 await ServiceRef.NavigationService.SetRootAsync(nameof(OnboardingPage), Args);
202 }
203
204 break;
205
206 case "Account":
207 string UserName = XML.Attribute(E, "userName");
208 string Password = XML.Attribute(E, "password");
209 string PasswordMethod = XML.Attribute(E, "passwordMethod");
210 Domain = XML.Attribute(E, "domain");
211
212 string DomainBak = ServiceRef.TagProfile?.Domain ?? string.Empty;
213 bool DefaultConnectivityBak = ServiceRef.TagProfile?.DefaultXmppConnectivity ?? false;
214 string ApiKeyBak = ServiceRef.TagProfile?.ApiKey ?? string.Empty;
215 string ApiSecretBak = ServiceRef.TagProfile?.ApiSecret ?? string.Empty;
216
217 await SelectDomain(Domain, string.Empty, string.Empty);
218
219 if (!await this.ConnectToAccount(UserName, Password, PasswordMethod, string.Empty, LegalIdDefinition, Pin ?? string.Empty))
220 {
221 ServiceRef.TagProfile?.SetDomain(DomainBak, DefaultConnectivityBak, ApiKeyBak, ApiSecretBak);
222 throw new Exception("Invalid account.");
223 }
224
225 AccountDone = true;
226 break;
227
228 case "LegalId":
229 LegalIdDefinition = E;
230 break;
231
232 case "Pin":
233 //Pin = XML.Attribute(E, "pin");
234 break;
235
236 case "Transfer":
237 foreach (XmlNode N in E.ChildNodes)
238 {
239 if (N is XmlElement E2)
240 {
241 ToProcess.AddLast(E2);
242 }
243 }
244 break;
245
246 default:
247 throw new Exception("Invalid Invitation XML");
248 }
249 }
250
251 if (AccountDone && LegalIdDefinition is not null)
252 {
254 }
255 else if (AccountDone)
256 {
258 }
259 else
261 }
262 catch (Exception Ex)
263 {
264 ServiceRef.LogService.LogException(Ex);
265
266 await ServiceRef.UiService.DisplayAlert(
270 }
271
272 // Processing finished; let UI hide loader.
273 WeakReferenceMessenger.Default.Send(new OnboardingLinkProcessingMessage(false));
274 }
275 private static async Task SelectDomain(string Domain, string Key, string Secret)
276 {
277 bool DefaultConnectivity;
278
279 try
280 {
281 (string HostName, int PortNumber, bool IsIpAddress) = await ServiceRef.NetworkService.LookupXmppHostnameAndPort(Domain);
282 DefaultConnectivity = HostName == Domain && PortNumber == XmppCredentials.DefaultPort;
283 }
284 catch (Exception Ex)
285 {
286 ServiceRef.LogService.LogException(Ex);
287 DefaultConnectivity = false;
288 }
289
290 ServiceRef.TagProfile.SetDomain(Domain, DefaultConnectivity, Key, Secret);
291 }
292
293 private async Task<bool> ConnectToAccount(string AccountName, string Password, string PasswordMethod, string LegalIdentityJid, XmlElement? LegalIdDefinition, string Pin)
294 {
295 try
296 {
297 async Task OnConnected(XmppClient client)
298 {
299 DateTime Now = DateTime.Now;
300 LegalIdentity? CreatedIdentity = null;
301 LegalIdentity? ApprovedIdentity = null;
302
303 bool ServiceDiscoverySucceeded;
304
306 {
307 ServiceDiscoverySucceeded = await ServiceRef.XmppService.DiscoverServices(client);
308 }
309 else
310 {
311 ServiceDiscoverySucceeded = true;
312 }
313
314 if (ServiceDiscoverySucceeded && !string.IsNullOrEmpty(ServiceRef.TagProfile.LegalJid))
315 {
316 bool DestroyContractsClient = false;
317
318 if (!client.TryGetExtension(typeof(ContractsClient), out IXmppExtension Extension) ||
319 Extension is not ContractsClient ContractsClient)
320 {
322 DestroyContractsClient = true;
323 }
324
325 try
326 {
327 if (LegalIdDefinition is not null)
328 await ContractsClient.ImportKeys(LegalIdDefinition);
329
331
332 foreach (LegalIdentity Identity in Identities)
333 {
334 try
335 {
336 if ((string.IsNullOrEmpty(LegalIdentityJid) || string.Compare(LegalIdentityJid, Identity.Id, StringComparison.OrdinalIgnoreCase) == 0) &&
337 Identity.HasClientSignature &&
338 Identity.HasClientPublicKey &&
339 Identity.From <= Now &&
340 Identity.To >= Now &&
341 (Identity.State == IdentityState.Approved || Identity.State == IdentityState.Created) &&
342 Identity.ValidateClientSignature() &&
343 await ContractsClient.HasPrivateKey(Identity))
344 {
345 if (Identity.State == IdentityState.Approved)
346 {
347 ApprovedIdentity = Identity;
348 break;
349 }
350
351 CreatedIdentity ??= Identity;
352 }
353 }
354 catch (Exception)
355 {
356 // Keys might not be available. Ignore at this point. Keys will be generated later.
357 }
358 }
359
360 LegalIdentity? SelectedIdentity = ApprovedIdentity ?? CreatedIdentity;
361
362 string SelectedId;
363
364 if (SelectedIdentity is not null)
365 {
366 await ServiceRef.TagProfile.SetAccountAndLegalIdentity(AccountName, client.PasswordHash, client.PasswordHashMethod, SelectedIdentity);
367 SelectedId = SelectedIdentity.Id;
368
370 }
371 else
372 {
374 SelectedId = string.Empty;
375 }
376
377 if (!string.IsNullOrEmpty(Pin))
378 {
379 ServiceRef.TagProfile.LocalPassword = Pin;
380 }
381
382 foreach (LegalIdentity Identity in Identities)
383 {
384 if (Identity.Id == SelectedId)
385 {
386 continue;
387 }
388
389 switch (Identity.State)
390 {
391 case IdentityState.Approved:
392 case IdentityState.Created:
394 break;
395 }
396 }
397 }
398 finally
399 {
400 if (DestroyContractsClient)
401 {
403 }
404 }
405 }
406 }
407
408 (string HostName, int PortNumber, bool IsIpAddress) = await ServiceRef.NetworkService.LookupXmppHostnameAndPort(
409 ServiceRef.TagProfile?.Domain ?? string.Empty);
410
411 (bool Succeeded, string? ErrorMessage, string[]? Alternatives) = await ServiceRef.XmppService.TryConnectAndConnectToAccount(
412 ServiceRef.TagProfile?.Domain ?? string.Empty,
413 IsIpAddress, HostName, PortNumber, AccountName, Password, PasswordMethod, Constants.LanguageCodes.Default,
414 typeof(App).Assembly, OnConnected);
415
416 if (!Succeeded)
417 {
418 await ServiceRef.UiService.DisplayAlert(
420 ErrorMessage ?? string.Empty,
422 }
423
424 return Succeeded;
425 }
426 catch (Exception Ex)
427 {
428 ServiceRef.LogService.LogException(Ex);
429
430 await ServiceRef.UiService.DisplayAlert(
434 }
435
436 return false;
437 }
438 }
439}
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
static void ValidateCertificateCallback(object? Sender, RemoteCertificateEventArgs Args)
Callback for validating SSL certificates. This method is called when a remote certificate is received...
Definition: App.xaml.cs:1135
const string Default
The default language code.
Definition: Constants.cs:105
const string Onboarding
Onboarding URI Scheme (obinfo)
Definition: Constants.cs:183
static ? string GetScheme(string Url)
Gets the predefined scheme from an IoT Code
Definition: Constants.cs:200
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string UnableToAccessInvitation
Looks up a localized string similar to Unable to access invitation information..
static string UnableToConnectTo
Looks up a localized string similar to Unable to connect to {0}.
static string InvalidInvitationCode
Looks up a localized string similar to Invalid invitation code..
static string ThisCodeCannotBeClaimedAtThisTime
Looks up a localized string similar to This code represents a Neuro-Access invitation,...
static string Ok
Looks up a localized string similar to OK.
static string InvitedToCreateAccountOnDomain
Looks up a localized string similar to You have been invited to create an account on {0}....
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
static string InvitationAccepted
Looks up a localized string similar to Invitation accepted.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:226
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:130
static INavigationService NavigationService
The navigation service for navigating between pages.
Definition: ServiceRef.cs:178
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:202
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
static void GoToRegistrationStep(RegistrationStep NewStep)
Set a new registration step
Navigation arguments for onboarding flow. Scenario determines dynamic starting step.
Contains information about a binary response to a content request.
string ContentType
Internet Content-Type of encoded object.
void AssertOk()
Asserts response is OK.
Contains information about a response to a content request.
object Decoded
Decoded object.
void AssertOk()
Asserts response is OK.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
static Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
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.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
string PasswordHashMethod
Password hash method.
Definition: XmppClient.cs:3484
bool TryGetExtension(Type Type, out IXmppExtension Extension)
Tries to get a registered extension of a specific type from the client.
Definition: XmppClient.cs:7391
string PasswordHash
Hash value of password. Depends on method used to authenticate user.
Definition: XmppClient.cs:3475
Class containing credentials for an XMPP client connection.
const int DefaultPort
Default XMPP Server port.
RegistrationStep Step
This profile's current registration step.
Definition: ITagProfile.cs:172
void SetDomain(string DomainName, bool DefaultXmppConnectivity, string Key, string Secret)
Set the domain name to connect to.
void SetXmppPasswordNeedsUpdating(bool Value)
Sets the local flag for if xmpp password needs updating.
string? ApiKey
API Key, for creating new account.
Definition: ITagProfile.cs:62
string? LegalJid
The Jabber Legal JID for this user/profile.
Definition: ITagProfile.cs:117
string? ApiSecret
API Secret, for creating new account.
Definition: ITagProfile.cs:67
bool DefaultXmppConnectivity
If connecting to the domain can be done using default parameters (host=domain, default c2s port).
Definition: ITagProfile.cs:57
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.
Definition: ITagProfile.cs:52
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.
OnboardingScenario
Distinct onboarding scenarios.
class OnboardingLinkProcessingMessage(bool isProcessing)
Signals that an onboarding link is being processed (start/stop). Used by GetStarted view to toggle a ...
Definition: Messages.cs:21
IdentityState
Lists recognized legal identity states.
Grade
Grade enumeration
Definition: Grade.cs:7