Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
QuickLoginUser.cs
1using System;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
6using Waher.Content;
13using Waher.Security;
16using Waher.Things;
17
19{
24 {
25 private readonly LegalIdentity id;
26 private readonly string petitionId;
27 private readonly Dictionary<string, object> properties;
28 private readonly Dictionary<string, object>[] attachments;
29
37 public QuickLoginUser(LegalIdentity ID, string PetitionId,
38 Dictionary<string, object> Properties, Dictionary<string, object>[] Attachments)
39 {
40 this.id = ID;
41 this.petitionId = PetitionId;
42 this.properties = Properties;
43 this.attachments = Attachments;
44 }
45
49 public ILegalIdentity LegalIdentity => this.id;
50
54 public LegalIdentity LegalId => this.id;
55
59 public string Id => this.id.Id;
60
64 public string Key => this.petitionId;
65
69 public string Provider => this.id.Provider;
70
74 public string ClientKeyName => this.id.ClientKeyName;
75
79 public DateTime Created => this.id.Created;
80
84 public DateTime? Updated => this.id.Updated == DateTime.MinValue ? (DateTime?)null : this.id.Updated;
85
89 public DateTime From => this.id.From;
90
94 public DateTime To => this.id.To;
95
99 public bool HasClientPublicKey => this.id.HasClientPublicKey;
100
104 public string ClientPubKey => this.id.HasClientPublicKey ? Convert.ToBase64String(this.id.ClientSignature) : null;
105
109 public Dictionary<string, object> Properties => this.properties;
110
114 public Dictionary<string, object>[] Attachments => this.attachments;
115
119 public string ServerSignature => Convert.ToBase64String(this.id.ServerSignature);
120
125
129 public string UserName
130 {
131 get
132 {
133 string s = this.id[PersonalInformation.JidTag];
134 if (string.IsNullOrEmpty(s))
135 s = this.id.Id;
136
137 int i = s.IndexOf('@');
138 if (i > 0 && Gateway.IsDomain(s[(i + 1)..], true))
139 return s[..i];
140
141 return s;
142 }
143 }
144
148 public string FederatedUserName
149 {
150 get
151 {
152 string s = this.id[PersonalInformation.JidTag];
153 return string.IsNullOrEmpty(s) ? this.id.Id : s;
154 }
155 }
156
160 public string FriendlyName
161 {
162 get
163 {
164 if (this.properties.ContainsKey(PersonalInformation.FirstNameTag) &&
165 this.properties.ContainsKey(PersonalInformation.LastNamesTag))
166 {
167 StringBuilder sb = null;
168
169 this.Append(ref sb, PersonalInformation.FirstNameTag, " ");
170 this.Append(ref sb, PersonalInformation.MiddleNamesTag, " ");
171 this.Append(ref sb, PersonalInformation.LastNamesTag, " ");
172
173 return sb?.ToString() ?? "N/A";
174 }
175 else if (this.properties.TryGetValue(PersonalInformation.FullNameTag, out object FullName))
176 return FullName?.ToString() ?? "N/A";
177 else
178 {
179 string s = this.Jid;
180 if (string.IsNullOrEmpty(s))
181 s = this.id?.Id;
182
183 if (string.IsNullOrEmpty(s))
184 return "N/A";
185
186 int i = s.IndexOf('@');
187 if (i < 0)
188 return s;
189
190 string LocalPart = s[..i];
191
192 if (Guid.TryParse(LocalPart, out _) && !(this.id is null))
193 {
194 string EMail = this.id[PersonalInformation.EMailTag];
195 if (!string.IsNullOrEmpty(EMail))
196 return EMail;
197
198 string PhoneNr = this.id[PersonalInformation.PhoneTag];
199 if (!string.IsNullOrEmpty(PhoneNr))
200 return PhoneNr;
201 }
202
203 string Domain = s[(i + 1)..];
204
205 if (Gateway.IsDomain(Domain, true))
206 return LocalPart;
207 else
208 return s;
209 }
210 }
211 }
212
216 public string FullAddress
217 {
218 get
219 {
220 StringBuilder sb = null;
221
222 this.Append(ref sb, PersonalInformation.AddressTag, ", ");
223 this.Append(ref sb, PersonalInformation.Address2Tag, ", ");
224 this.Append(ref sb, PersonalInformation.AreaTag, ", ");
225 this.Append(ref sb, PersonalInformation.PostalCodeTag, ", ");
226 this.Append(ref sb, PersonalInformation.CityTag, ", ");
227 this.Append(ref sb, PersonalInformation.RegionTag, ", ");
228 this.Append(ref sb, PersonalInformation.CountryTag, ", ");
229
230 if (!(sb is null))
231 return sb.ToString();
232
233 this.Append(ref sb, PersonalInformation.OrganizationNameTag, ", ");
234 this.Append(ref sb, PersonalInformation.OrganizationDepartmentTag, ", ");
235 this.Append(ref sb, PersonalInformation.OrganizationAddressTag, ", ");
236 this.Append(ref sb, PersonalInformation.OrganizationAddress2Tag, ", ");
237 this.Append(ref sb, PersonalInformation.OrganizationAreaTag, ", ");
238 this.Append(ref sb, PersonalInformation.OrganizationPostalCodeTag, ", ");
239 this.Append(ref sb, PersonalInformation.OrganizationCityTag, ", ");
240 this.Append(ref sb, PersonalInformation.OrganizationRegionTag, ", ");
241 this.Append(ref sb, PersonalInformation.OrganizationCountryTag, ", ");
242
243 return sb?.ToString() ?? "N/A";
244 }
245 }
246
250 public string Jid
251 {
252 get
253 {
254 if (this.properties.TryGetValue(PersonalInformation.JidTag, out object Obj) && Obj is string s)
255 return s;
256 else
257 return null;
258 }
259 }
260
261 private void Append(ref StringBuilder sb, string Name, string Delimiter)
262 {
263 if (this.properties.TryGetValue(Name, out object Obj))
264 User.AppendValue(ref sb, Obj, Delimiter);
265 }
266
270 public string PasswordHash => string.Empty;
271
275 public string PasswordHashType => string.Empty;
276
282 public bool HasPrivilege(string Privilege)
283 {
284 return false;
285 }
286
290 public string AvatarUrl
291 {
292 get
293 {
294 if (!(this.attachments is null))
295 {
296 foreach (Dictionary<string, object> Attachment in this.attachments)
297 {
298 if (Attachment.TryGetValue("ContentType", out object Obj) &&
299 Obj is string ContentType &&
300 ContentType.StartsWith("image/") &&
301 Attachment.TryGetValue("BackEndUrl", out Obj) &&
302 Obj is string BackEndUrl)
303 {
304 return Gateway.GetUrl(BackEndUrl);
305 }
306 }
307 }
308
309 return Gateway.GetUrl("/Images/NeuroAccess.png");
310 }
311 }
312
318 public bool IsUserAttachment(string Url, out string AttachmentId)
319 {
320 if (this.attachments is null)
321 {
322 AttachmentId = null;
323 return false;
324 }
325
326 foreach (Dictionary<string, object> Attachment in this.attachments)
327 {
328 if (Attachment.TryGetValue("Url", out object Obj) &&
329 Obj is string s &&
330 string.Compare(s, Url, true) == 0 &&
331 Attachment.TryGetValue("Id", out Obj) &&
332 Obj is string Id)
333 {
334 AttachmentId = Id;
335 return true;
336 }
337 }
338
339 AttachmentId = null;
340 return false;
341 }
342
346 public Task<RequestOrigin> GetOrigin()
347 {
348 return Task.FromResult(new RequestOrigin(this.Jid, null, null, null, this));
349 }
350
356 public object this[string Key]
357 {
358 get
359 {
360 if (this.properties.TryGetValue(Key, out object Obj))
361 return Obj;
362 else
363 return null;
364 }
365 }
366
372 public Task<IEnumerable<KeyValuePair<string, object>>> CreateClaims(bool Encrypted)
373 {
374 int IssuedAt = (int)Math.Round(DateTime.UtcNow.Subtract(JSON.UnixEpoch).TotalSeconds);
375 int Expires = IssuedAt + 3600;
376 DateTime Now = DateTime.Now;
377
378 if (this.From > Now ||
379 this.To < Now ||
380 this.State != Networking.XMPP.Contracts.IdentityState.Approved)
381 {
382 return Task.FromResult<IEnumerable<KeyValuePair<string, object>>>(null);
383 }
384
385 List<KeyValuePair<string, object>> Claims = new List<KeyValuePair<string, object>>()
386 {
387 new KeyValuePair<string, object>(JwtClaims.JwtId, Convert.ToBase64String(Gateway.NextBytes(32))),
388 new KeyValuePair<string, object>(JwtClaims.Issuer, Gateway.Domain.Value),
389 new KeyValuePair<string, object>(JwtClaims.Subject, this.Jid),
390 new KeyValuePair<string, object>(JwtClaims.SubjectIdentifier, this.Id),
391 new KeyValuePair<string, object>(JwtClaims.Name, this.UserName),
392 new KeyValuePair<string, object>(JwtClaims.IssueTime, IssuedAt),
393 new KeyValuePair<string, object>(JwtClaims.ExpirationTime, Expires),
394 new KeyValuePair<string, object>(JwtClaims.Picture, this.AvatarUrl)
395 };
396
397 AddClaim(Claims, JwtClaims.WebSite, this[PersonalInformation.AgentTag]);
398
399 if (Encrypted)
400 {
401 CaseInsensitiveString FirstName = this[PersonalInformation.FirstNameTag]?.ToString() ?? string.Empty;
402 CaseInsensitiveString MiddleNames = this[PersonalInformation.MiddleNamesTag]?.ToString() ?? string.Empty;
403 CaseInsensitiveString LastNames = this[PersonalInformation.LastNamesTag]?.ToString() ?? string.Empty;
404 CaseInsensitiveString FullName = this[PersonalInformation.FullNameTag]?.ToString() ?? string.Empty;
405
407 FullName = Networking.XMPP.Contracts.LegalIdentity.JoinNames(FirstName, MiddleNames, LastNames);
408 else
409 Networking.XMPP.Contracts.LegalIdentity.SeparateNames(FullName, out FirstName, out MiddleNames, out LastNames);
410
411 AddClaim(Claims, JwtClaims.Name, FullName);
412 AddClaim(Claims, JwtClaims.GivenName, FirstName);
413 AddClaim(Claims, JwtClaims.MiddleName, MiddleNames);
414 AddClaim(Claims, JwtClaims.FamilyName, LastNames);
415 AddClaim(Claims, JwtClaims.Address, this.FullAddress);
416
417 string BDay = null;
418 string BMonth = null;
419 string BYear = null;
420
421 foreach (KeyValuePair<string, object> P in this.properties)
422 {
423 switch (P.Key)
424 {
434 break;
435
437 AddClaim(Claims, JwtClaims.EMail, P.Value);
438 AddClaim(Claims, JwtClaims.EMailVerified, "true");
439 break;
440
442 AddClaim(Claims, JwtClaims.PhoneNumber, P.Value);
443 AddClaim(Claims, JwtClaims.PhoneNumberVerified, "true");
444 break;
445
447 AddClaim(Claims, JwtClaims.Gender, P.Value);
448 break;
449
451 AddClaim(Claims, JwtClaims.Nationalities, P.Value);
452 break;
453
455 BDay = P.Value?.ToString();
456 break;
457
459 BMonth = P.Value?.ToString();
460 break;
461
463 BYear = P.Value?.ToString();
464 break;
465
467 AddClaim(Claims, "age_above", P.Value);
468 break;
469
471 AddClaim(Claims, "org_name", P.Value);
472 break;
473
475 AddClaim(Claims, "org_nr", P.Value);
476 break;
477
479 AddClaim(Claims, "org_dept", P.Value);
480 break;
481
483 AddClaim(Claims, "org_role", P.Value);
484 break;
485
487 AddClaim(Claims, "org_addr", P.Value);
488 break;
489
491 AddClaim(Claims, "org_addr2", P.Value);
492 break;
493
494 case "ORGZIP ":
495 AddClaim(Claims, "org_zip", P.Value);
496 break;
497
499 AddClaim(Claims, "org_area", P.Value);
500 break;
501
503 AddClaim(Claims, "org_city", P.Value);
504 break;
505
507 AddClaim(Claims, "org_region", P.Value);
508 break;
509
511 AddClaim(Claims, "org_country", P.Value);
512 break;
513 }
514 }
515
516 if (!string.IsNullOrEmpty(BDay) &&
517 !string.IsNullOrEmpty(BMonth) &&
518 !string.IsNullOrEmpty(BYear) &&
519 int.TryParse(BDay, out int BDayNr) &&
520 int.TryParse(BMonth, out int BMonthNr) &&
521 int.TryParse(BYear, out int BYearNr) &&
522 BMonthNr >= 1 && BMonthNr <= 12 &&
523 BYearNr >= 1900 && BYearNr <= 2100 &&
524 BDayNr >= 1 && BDayNr <= DateTime.DaysInMonth(BYearNr, BMonthNr))
525 {
526 AddClaim(Claims, JwtClaims.BirthDate, XML.Encode(new DateTime(BYearNr, BMonthNr, BDayNr)));
527 }
528 }
529
530 return Task.FromResult<IEnumerable<KeyValuePair<string, object>>>(Claims);
531 }
532
533 private static void AddClaim(List<KeyValuePair<string, object>> Claims, string Key, object Value)
534 {
535 if (!(Value is null))
536 {
537 if (Value is string s && s == "N/A")
538 return;
539
540 Claims.Add(new KeyValuePair<string, object>(Key, Value));
541 }
542 }
543
551 public async Task<string> CreateToken(JwtFactory Factory, bool Encrypted,
552 params KeyValuePair<string, object>[] AdditionalClaims)
553 {
554 IEnumerable<KeyValuePair<string, object>> Claims = await this.CreateClaims(Encrypted);
555 Claims = JwtFactory.JoinClaims(Claims, AdditionalClaims);
556 return Factory.Create(Claims);
557 }
558
563 public string ExportXml()
564 {
565 StringBuilder Xml = new StringBuilder();
566 this.ExportXml(Xml);
567 return Xml.ToString();
568 }
569
574 public void ExportXml(StringBuilder Xml)
575 {
576 Xml.Append("<quickLoginUser xmlns='");
578 Xml.Append("' key='");
579 Xml.Append(XML.Encode(this.petitionId));
580 Xml.Append("'>");
581
582 this.id.Serialize(Xml, true, true, true, true, true, true, true);
583
584 if (!(this.properties is null))
585 {
586 Xml.Append("<properties>");
587 Waher.Events.XMPP.XmppEventSink.AppendTags(Xml, this.properties);
588 Xml.Append("</properties>");
589 }
590
591 if (!(this.attachments is null))
592 {
593 Xml.Append("<attachments>");
594
595 foreach (Dictionary<string, object> Attachment in this.attachments)
596 {
597 Xml.Append("<attachment>");
599 Xml.Append("</attachment>");
600 }
601
602 Xml.Append("</attachments>");
603 }
604
605 Xml.Append("</quickLoginUser>");
606 }
607
615 public static bool TryParse(XmlElement Xml, out QuickLoginUser User, out string Reason)
616 {
617 User = null;
618
619 if (Xml is null)
620 {
621 Reason = new ArgumentNullException(nameof(Xml)).Message;
622 return false;
623 }
624
625 if (Xml.LocalName != "quickLoginUser" || Xml.NamespaceURI != QuickLogin.NamespaceTagSignature)
626 {
627 Reason = "Not a quick-login user element.";
628 return false;
629 }
630
631 string PetitionId = XML.Attribute(Xml, "key");
632 if (string.IsNullOrEmpty(PetitionId))
633 {
634 Reason = "Missing or empty key.";
635 return false;
636 }
637
638 LegalIdentity Id = null;
639 Dictionary<string, object> Properties = null;
641
642 foreach (XmlNode N in Xml.ChildNodes)
643 {
644 if (!(N is XmlElement E))
645 continue;
646
647 switch (E.LocalName)
648 {
649 case "identity":
650 try
651 {
652 Id = Networking.XMPP.Contracts.LegalIdentity.Parse(E);
653 }
654 catch (Exception ex)
655 {
656 Reason = ex.Message;
657 return false;
658 }
659 break;
660
661 case "properties":
662 Properties = ParseTags(E);
663 break;
664
665 case "attachments":
666 foreach (XmlNode N2 in E.ChildNodes)
667 {
668 if (N2 is XmlElement E2 && E2.LocalName == "attachment")
669 Attachments.Add(ParseTags(E2));
670 }
671 break;
672 }
673 }
674
675 if (Id is null)
676 {
677 Reason = "Missing legal identity.";
678 return false;
679 }
680
681 Reason = null;
682 User = new QuickLoginUser(Id, PetitionId,
683 Properties ?? new Dictionary<string, object>(),
684 Attachments.ToArray());
685
686 return true;
687 }
688
689 private static Dictionary<string, object> ParseTags(XmlElement E)
690 {
691 Dictionary<string, object> Tags = new Dictionary<string, object>();
692
693 foreach (XmlNode N2 in E.ChildNodes)
694 {
695 if (N2 is XmlElement E2 && E2.LocalName == "tag")
696 {
697 string TagName = XML.Attribute(E2, "name");
698 string TagValue = XML.Attribute(E2, "value");
699 string TagType = XML.Attribute(E2, "type");
700
701 if (!XmppEventReceptor.TryParse(TagValue, TagType, out object TagValueParsed))
702 TagValueParsed = TagValue;
703
704 Tags[TagName] = TagValueParsed;
705 }
706 }
707
708 return Tags;
709 }
710 }
711}
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
Definition: JSON.cs:20
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
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
This class handles incoming events from the XMPP network. The default behaviour is to log incoming ev...
static bool TryParse(string Value, string Type, out object ParsedValue)
Tries to parse a simple value.
Event sink sending events to a destination over the XMPP network.
static void AppendTags(StringBuilder Xml, params KeyValuePair< string, object >[] Tags)
Appends a collection of tags to an XML string.
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:3087
static bool IsDomain(string DomainOrHost, bool IncludeAlternativeDomains)
If a domain or host name represents the gateway.
Definition: Gateway.cs:5174
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:4335
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:5079
Contains a reference to an attachment assigned to a legal object.
Definition: Attachment.cs:10
Represents a digital signature on a contract.
Contains personal information found in a legal identity.
Represents a server signature on a contract.
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
Static class containing predefined JWT claim names.
Definition: JwtClaims.cs:10
const string Issuer
Issuer of the JWT
Definition: JwtClaims.cs:14
const string GivenName
Given name(s) or first name(s)
Definition: JwtClaims.cs:54
const string EMailVerified
True if the e-mail address has been verified; otherwise false
Definition: JwtClaims.cs:99
const string WebSite
Web page or blog URL
Definition: JwtClaims.cs:89
const string IssueTime
Time at which the JWT was issued; can be used to determine age of the JWT
Definition: JwtClaims.cs:39
const string Name
Full name
Definition: JwtClaims.cs:49
const string JwtId
Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only...
Definition: JwtClaims.cs:44
const string Picture
Profile picture URL
Definition: JwtClaims.cs:84
const string EMail
Preferred e-mail address
Definition: JwtClaims.cs:94
const string BirthDate
Birthday
Definition: JwtClaims.cs:109
const string SubjectIdentifier
Subject Identifier
Definition: JwtClaims.cs:220
const string Nationalities
String array representing the End-User's nationalities.
Definition: JwtClaims.cs:184
const string PhoneNumber
Preferred telephone number
Definition: JwtClaims.cs:124
const string Gender
Gender
Definition: JwtClaims.cs:104
const string MiddleName
Middle name(s)
Definition: JwtClaims.cs:64
const string Subject
Subject of the JWT (the user)
Definition: JwtClaims.cs:19
const string PhoneNumberVerified
True if the phone number has been verified; otherwise false
Definition: JwtClaims.cs:129
const string ExpirationTime
Time after which the JWT expires
Definition: JwtClaims.cs:29
const string Address
Preferred postal address
Definition: JwtClaims.cs:134
const string FamilyName
Surname(s) or last name(s)
Definition: JwtClaims.cs:59
A factory that can create and validate JWT tokens.
Definition: JwtFactory.cs:66
static IEnumerable< KeyValuePair< string, object > > JoinClaims(IEnumerable< KeyValuePair< string, object > > Claims1, params KeyValuePair< string, object >[] Claims2)
Joins two sets of claims into one.
Definition: JwtFactory.cs:528
string Create(params KeyValuePair< string, object >[] Claims)
Creates a new JWT token.
Definition: JwtFactory.cs:379
Corresponds to a privilege in the system.
Definition: Privilege.cs:16
Corresponds to a user in the system.
Definition: User.cs:24
static void AppendValue(ref StringBuilder Output, object Value, string Delimiter)
Appends a value to a StringBuilder.
Definition: User.cs:304
Tokens available in request.
Definition: RequestOrigin.cs:9
A User that can participate in distributed operations, where the user is identified using a JWT token...
Interface for requestors that can act as an origin for distributed requests.
Definition: ImplTypes.g.cs:58
IdentityState
Lists recognized legal identity states.
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:15
Definition: App.xaml.cs:4