Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
RosterConfiguration.cs
1using System;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
6using Waher.Content;
11using Waher.Events;
18using Waher.Script;
19
21{
26 {
27 private static RosterConfiguration instance = null;
28
29 private HttpResource connectToJID;
30 private HttpResource removeContact;
31 private HttpResource unsubscribeContact;
32 private HttpResource subscribeToContact;
33 private HttpResource renameContact;
34 private HttpResource updateContactGroups;
35 private HttpResource getGroups;
36 private HttpResource acceptRequest;
37 private HttpResource declineRequest;
38
43 : base()
44 {
45 }
46
50 public static RosterConfiguration Instance => instance;
51
55 public override string Resource => "/Settings/Roster.md";
56
60 public override int Priority => 400;
61
67 public override Task<string> Title(Language Language)
68 {
69 return Language.GetStringAsync(typeof(Gateway), 11, "Roster");
70 }
71
75 public override Task ConfigureSystem()
76 {
77 this.AddHandlers();
78 return Task.CompletedTask;
79 }
80
81 private void AddHandlers()
82 {
83 if (!this.handlersAdded || Gateway.XmppClient != this.prevClient)
84 {
85 this.handlersAdded = true;
86 this.prevClient = Gateway.XmppClient;
87
88 Gateway.XmppClient.OnRosterItemAdded += this.XmppClient_OnRosterItemAdded;
89 Gateway.XmppClient.OnRosterItemRemoved += this.XmppClient_OnRosterItemRemoved;
90 Gateway.XmppClient.OnRosterItemUpdated += this.XmppClient_OnRosterItemUpdated;
91 Gateway.XmppClient.OnPresence += this.XmppClient_OnPresence;
92 Gateway.XmppClient.OnPresenceSubscribe += this.XmppClient_OnPresenceSubscribe;
93 Gateway.XmppClient.OnStateChanged += this.XmppClient_OnStateChanged;
94 }
95 }
96
97 private bool handlersAdded = false;
98 private XmppClient prevClient = null;
99
100 private async Task XmppClient_OnStateChanged(object _, XmppState NewState)
101 {
102 if (NewState == XmppState.Offline || NewState == XmppState.Error || NewState == XmppState.Connected)
103 {
104 string[] TabIDs = this.GetTabIDs();
105 if (TabIDs.Length > 0 && !(Gateway.XmppClient is null))
106 {
107 string Json = JSON.Encode(new KeyValuePair<string, object>[]
108 {
109 new KeyValuePair<string, object>("html", await this.RosterItemsHtml(Gateway.XmppClient.Roster, Gateway.XmppClient.SubscriptionRequests))
110 }, false);
111
112 Task _2 = ClientEvents.PushEvent(TabIDs, "UpdateRoster", Json, true, "User");
113 }
114
115 while (Gateway.XmppClient?.State == XmppState.Connected)
116 {
117 (string Jid, string Name, string[] Groups) = this.PopToAdd();
118
119 if (!string.IsNullOrEmpty(Jid))
120 {
121 RosterItem Item = Gateway.XmppClient.GetRosterItem(Jid);
122
123 if (Item is null)
124 await Gateway.XmppClient.AddRosterItem(new RosterItem(Jid, Name, Groups));
125 else if (NeedsUpdate(Item, Name, Groups))
126 await Gateway.XmppClient.AddRosterItem(new RosterItem(Jid, Name, Union(Item.Groups, Groups)));
127
128 continue;
129 }
130
131 Jid = this.PopToSubscribe();
132 if (!string.IsNullOrEmpty(Jid))
133 {
134 RosterItem Item = Gateway.XmppClient.GetRosterItem(Jid);
135
136 if (Item is null || Item.State == SubscriptionState.None || Item.State == SubscriptionState.From)
137 await Gateway.XmppClient.RequestPresenceSubscription(Jid);
138
139 continue;
140 }
141
142 break;
143 }
144 }
145 }
146
147 private static bool NeedsUpdate(RosterItem Item, string Name, string[] Groups)
148 {
149 if (Item.Name != Name)
150 return true;
151
152 if (Groups is null)
153 return false;
154
155 Dictionary<string, bool> Found = new Dictionary<string, bool>();
156
157 if (!(Item.Groups is null))
158 {
159 foreach (string Group in Item.Groups)
160 Found[Group] = true;
161 }
162
163 if (!(Groups is null))
164 {
165 foreach (string Group in Groups)
166 {
167 if (!Found.ContainsKey(Group))
168 return true;
169 }
170 }
171
172 return false;
173 }
174
175 private static string[] Union(string[] A1, string[] A2)
176 {
177 SortedDictionary<string, bool> Entries = new SortedDictionary<string, bool>();
178
179 if (!(A1 is null))
180 {
181 foreach (string s in A1)
182 Entries[s] = true;
183 }
184
185 if (!(A2 is null))
186 {
187 foreach (string s in A2)
188 Entries[s] = true;
189 }
190
191 string[] Result = new string[Entries.Count];
192 Entries.Keys.CopyTo(Result, 0);
193
194 return Result;
195 }
196
197 private async Task XmppClient_OnPresenceSubscribe(object Sender, PresenceEventArgs e)
198 {
199 if (string.Compare(e.FromBareJID, Gateway.XmppClient.BareJID, true) == 0)
200 return;
201
202 if (this.AcceptSubscriptionRequest(e.FromBareJID))
203 {
204 await e.Accept();
205 return;
206 }
207
208 StringBuilder Markdown = new StringBuilder();
209
210 Markdown.Append("Presence subscription request received from **");
211 Markdown.Append(MarkdownDocument.Encode(e.FromBareJID));
212 Markdown.Append("**. You can accept or decline the request from the roster configuration in the Administration portal.");
213
214 await Gateway.SendNotification(Markdown.ToString());
215
216 string[] TabIDs = this.GetTabIDs();
217 if (TabIDs.Length > 0)
218 {
219 string Json = JSON.Encode(new KeyValuePair<string, object>[]
220 {
221 new KeyValuePair<string, object>("bareJid", e.FromBareJID),
222 new KeyValuePair<string, object>("html", await this.RosterItemsHtml(Array.Empty<RosterItem>(), new PresenceEventArgs[] { e }))
223 }, false);
224
225 Task _ = ClientEvents.PushEvent(TabIDs, "UpdateRosterItem", Json, true, "User");
226 }
227 }
228
229 private async Task XmppClient_OnPresence(object Sender, PresenceEventArgs e)
230 {
231 RosterItem Item = Gateway.XmppClient?.GetRosterItem(e.FromBareJID);
232 if (!(Item is null))
233 await this.XmppClient_OnRosterItemUpdated(Sender, Item);
234 }
235
236 private async Task XmppClient_OnRosterItemUpdated(object _, RosterItem Item)
237 {
238 string[] TabIDs = this.GetTabIDs();
239 if (TabIDs.Length > 0)
240 {
241 string Json = JSON.Encode(new KeyValuePair<string, object>[]
242 {
243 new KeyValuePair<string, object>("bareJid", Item.BareJid),
244 new KeyValuePair<string, object>("html", await this.RosterItemsHtml(new RosterItem[]{ Item }, Array.Empty<PresenceEventArgs>()))
245 }, false);
246
247 await ClientEvents.PushEvent(TabIDs, "UpdateRosterItem", Json, true, "User");
248 }
249 }
250
251 private Task XmppClient_OnRosterItemRemoved(object _, RosterItem Item)
252 {
253 this.RosterItemRemoved(Item.BareJid);
254 return Task.CompletedTask;
255 }
256
257 private void RosterItemRemoved(string BareJid)
258 {
259 string[] TabIDs = this.GetTabIDs();
260 if (TabIDs.Length > 0)
261 {
262 string Json = JSON.Encode(new KeyValuePair<string, object>[]
263 {
264 new KeyValuePair<string, object>("bareJid", BareJid)
265 }, false);
266
267 Task _ = ClientEvents.PushEvent(TabIDs, "RemoveRosterItem", Json, true, "User");
268 }
269 }
270
271 private Task XmppClient_OnRosterItemAdded(object Sender, RosterItem Item)
272 {
273 return this.XmppClient_OnRosterItemUpdated(Sender, Item);
274 }
275
276 private string[] GetTabIDs()
277 {
278 if (Gateway.Configuring)
279 return ClientEvents.GetTabIDs();
280 else
281 return ClientEvents.GetTabIDsForLocation("/Settings/Roster.md");
282 }
283
284 private async Task<string> RosterItemsHtml(RosterItem[] Contacts, PresenceEventArgs[] SubscriptionRequests)
285 {
286 string FileName = Path.Combine(Gateway.RootFolder, "Settings", "RosterItems.md");
287 string Markdown = await Files.ReadAllTextAsync(FileName);
289 v["Contacts"] = Contacts;
290 v["Requests"] = SubscriptionRequests;
291
292 MarkdownSettings Settings = new MarkdownSettings(Gateway.Emoji1_24x24, true, v);
293 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(Markdown, Settings, FileName, string.Empty, string.Empty);
294 string Html = await Doc.GenerateHTML();
295
296 Html = HtmlDocument.GetBody(Html);
297
298 return Html;
299 }
300
305 public override void SetStaticInstance(ISystemConfiguration Configuration)
306 {
307 instance = Configuration as RosterConfiguration;
308 }
309
313 protected override string ConfigPrivilege => "Admin.Communication.Roster";
314
319 public override Task InitSetup(HttpServer WebServer)
320 {
321 HttpAuthenticationScheme Auth = Gateway.LoggedIn(new string[] { this.ConfigPrivilege });
322
323 this.connectToJID = WebServer.Register("/Settings/ConnectToJID", null, this.ConnectToJID, true, false, true, Auth);
324 this.removeContact = WebServer.Register("/Settings/RemoveContact", null, this.RemoveContact, true, false, true, Auth);
325 this.unsubscribeContact = WebServer.Register("/Settings/UnsubscribeContact", null, this.UnsubscribeContact, true, false, true, Auth);
326 this.subscribeToContact = WebServer.Register("/Settings/SubscribeToContact", null, this.SubscribeToContact, true, false, true, Auth);
327 this.renameContact = WebServer.Register("/Settings/RenameContact", null, this.RenameContact, true, false, true, Auth);
328 this.updateContactGroups = WebServer.Register("/Settings/UpdateContactGroups", null, this.UpdateContactGroups, true, false, true, Auth);
329 this.getGroups = WebServer.Register("/Settings/GetGroups", null, this.GetGroups, true, false, true, Auth);
330 this.acceptRequest = WebServer.Register("/Settings/AcceptRequest", null, this.AcceptRequest, true, false, true, Auth);
331 this.declineRequest = WebServer.Register("/Settings/DeclineRequest", null, this.DeclineRequest, true, false, true, Auth);
332
333 return base.InitSetup(WebServer);
334 }
335
340 public override Task UnregisterSetup(HttpServer WebServer)
341 {
342 WebServer.Unregister(this.connectToJID);
343 WebServer.Unregister(this.removeContact);
344 WebServer.Unregister(this.unsubscribeContact);
345 WebServer.Unregister(this.subscribeToContact);
346 WebServer.Unregister(this.renameContact);
347 WebServer.Unregister(this.updateContactGroups);
348 WebServer.Unregister(this.getGroups);
349 WebServer.Unregister(this.acceptRequest);
350 WebServer.Unregister(this.declineRequest);
351
352 return base.UnregisterSetup(WebServer);
353 }
354
360 public override async Task<bool> SetupConfiguration(HttpServer WebServer)
361 {
362 if (!this.Complete &&
363 Gateway.XmppClient.State == XmppState.Offline &&
364 !(Gateway.XmppClient is null))
365 {
366 await Gateway.XmppClient.Connect();
367 }
368
369 this.AddHandlers();
370
371 return await base.SetupConfiguration(WebServer);
372 }
373
374 private async Task ConnectToJID(HttpRequest Request, HttpResponse Response)
375 {
376 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
377
378 if (!Request.HasData)
379 {
380 await Response.SendResponse(new BadRequestException());
381 return;
382 }
383
384 ContentResponse Content = await Request.DecodeDataAsync();
385 if (Content.HasError || !(Content.Decoded is string JID))
386 {
387 await Response.SendResponse(new BadRequestException());
388 return;
389 }
390
391 Response.ContentType = PlainTextCodec.DefaultContentType;
392
393 string JidToValidate = JID;
394 if (!Gateway.HasDomain && JidToValidate.EndsWith("@"))
395 JidToValidate += "example.org";
396
397 if (!XmppClient.BareJidRegEx.IsMatch(JidToValidate))
398 await Response.Write("0");
399 else
400 {
401 RosterItem Item = Gateway.XmppClient.GetRosterItem(JID);
402 if (Item is null)
403 {
404 await Gateway.XmppClient.RequestPresenceSubscription(JID, await this.NickName());
405 Log.Informational("Requesting presence subscription.", JID);
406 await Response.Write("1");
407 }
408 else if (Item.State != SubscriptionState.Both && Item.State != SubscriptionState.To)
409 {
410 await Gateway.XmppClient.RequestPresenceSubscription(JID, await this.NickName());
411 Log.Informational("Requesting presence subscription.", JID);
412 await Response.Write("2");
413 }
414 else
415 await Response.Write("3");
416 }
417 }
418
419 private async Task<string> NickName()
420 {
421 SuggestionEventArgs e = new SuggestionEventArgs(string.Empty);
422 await OnGetNickNameSuggestions.Raise(this, e);
423
424 string[] Suggestions = e.ToArray();
425 string NickName = Suggestions.Length > 0 ? Suggestions[0] : (string)Gateway.Domain;
426
427 return XmppClient.EmbedNickName(NickName);
428 }
429
430 private async Task RemoveContact(HttpRequest Request, HttpResponse Response)
431 {
432 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
433
434 if (!Request.HasData)
435 {
436 await Response.SendResponse(new BadRequestException());
437 return;
438 }
439
440 ContentResponse Content = await Request.DecodeDataAsync();
441 if (Content.HasError || !(Content.Decoded is string JID))
442 {
443 await Response.SendResponse(new BadRequestException());
444 return;
445 }
446
447 Response.ContentType = PlainTextCodec.DefaultContentType;
448
449 XmppClient Client = Gateway.XmppClient;
450 RosterItem Contact = Client.GetRosterItem(JID);
451 if (Contact is null)
452 await Response.Write("0");
453 else
454 {
455 await Client.RemoveRosterItem(Contact.BareJid);
456 Log.Informational("Contact removed.", Contact.BareJid);
457 await Response.Write("1");
458 }
459 }
460
461 private async Task UnsubscribeContact(HttpRequest Request, HttpResponse Response)
462 {
463 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
464
465 if (!Request.HasData)
466 {
467 await Response.SendResponse(new BadRequestException());
468 return;
469 }
470
471 ContentResponse Content = await Request.DecodeDataAsync();
472 if (Content.HasError || !(Content.Decoded is string JID))
473 {
474 await Response.SendResponse(new BadRequestException());
475 return;
476 }
477
478 Response.ContentType = PlainTextCodec.DefaultContentType;
479
480 XmppClient Client = Gateway.XmppClient;
481 RosterItem Contact = Client.GetRosterItem(JID);
482 if (Contact is null)
483 await Response.Write("0");
484 else
485 {
486 await Client.RequestPresenceUnsubscription(Contact.BareJid);
487 Log.Informational("Unsubscribing from presence.", Contact.BareJid);
488 await Response.Write("1");
489 }
490 }
491
492 private async Task SubscribeToContact(HttpRequest Request, HttpResponse Response)
493 {
494 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
495
496 if (!Request.HasData)
497 {
498 await Response.SendResponse(new BadRequestException());
499 return;
500 }
501
502 ContentResponse Content = await Request.DecodeDataAsync();
503 if (Content.HasError || !(Content.Decoded is string JID))
504 {
505 await Response.SendResponse(new BadRequestException());
506 return;
507 }
508
509 Response.ContentType = PlainTextCodec.DefaultContentType;
510
511 XmppClient Client = Gateway.XmppClient;
512 RosterItem Contact = Client.GetRosterItem(JID);
513 if (Contact is null)
514 await Response.Write("0");
515 else
516 {
517 await Client.RequestPresenceSubscription(Contact.BareJid, await this.NickName());
518 Log.Informational("Requesting presence subscription.", Contact.BareJid);
519 await Response.Write("1");
520 }
521 }
522
523 private async Task RenameContact(HttpRequest Request, HttpResponse Response)
524 {
525 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
526
527 if (!Request.HasData)
528 {
529 await Response.SendResponse(new BadRequestException());
530 return;
531 }
532
533 ContentResponse Content = await Request.DecodeDataAsync();
534 if (Content.HasError || !(Content.Decoded is string NewName))
535 {
536 await Response.SendResponse(new BadRequestException());
537 return;
538 }
539
540 string JID = Request.Header["X-BareJID"];
541 if (JID is null || string.IsNullOrEmpty(JID))
542 {
543 await Response.SendResponse(new BadRequestException());
544 return;
545 }
546
547 Response.ContentType = PlainTextCodec.DefaultContentType;
548
549 XmppClient Client = Gateway.XmppClient;
550 RosterItem Contact = Client.GetRosterItem(JID);
551 if (Contact is null)
552 await Response.Write("0");
553 else
554 {
555 await Client.UpdateRosterItem(Contact.BareJid, NewName, Contact.Groups);
556 Log.Informational("Contact renamed.", Contact.BareJid, new KeyValuePair<string, object>("Name", NewName));
557 await Response.Write("1");
558 }
559 }
560
561 private async Task UpdateContactGroups(HttpRequest Request, HttpResponse Response)
562 {
563 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
564
565 if (!Request.HasData)
566 {
567 await Response.SendResponse(new BadRequestException());
568 return;
569 }
570
571 ContentResponse Content = await Request.DecodeDataAsync();
572 if (Content.HasError || !(Content.Decoded is string BareJid) || string.IsNullOrEmpty(BareJid))
573 {
574 await Response.SendResponse(new BadRequestException());
575 return;
576 }
577
578 XmppClient Client = Gateway.XmppClient;
579 RosterItem Contact = Client.GetRosterItem(BareJid);
580 if (Contact is null)
581 {
582 await Response.SendResponse(new NotFoundException());
583 return;
584 }
585
586 SortedDictionary<string, bool> Groups = new SortedDictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
587 string[] GroupsArray;
588 int i = 0;
589 string s;
590
591 while (!string.IsNullOrEmpty(s = System.Web.HttpUtility.UrlDecode(Request.Header["X-Group-" + (++i).ToString()])))
592 Groups[s] = true;
593
594 if (Groups.Count > 0)
595 {
596 GroupsArray = new string[Groups.Count];
597 Groups.Keys.CopyTo(GroupsArray, 0);
598 }
599 else
600 GroupsArray = Array.Empty<string>();
601
602 StringBuilder sb = new StringBuilder();
603 bool First = true;
604
605 if (!(Groups is null))
606 {
607 foreach (string Group in GroupsArray)
608 {
609 if (First)
610 First = false;
611 else
612 sb.Append(", ");
613
614 sb.Append(Group);
615 }
616 }
617
618 await Client.UpdateRosterItem(Contact.BareJid, Contact.Name, GroupsArray);
619 Log.Informational("Contact groups updated.", Contact.BareJid, new KeyValuePair<string, object>("Groups", sb.ToString()));
620
621 Response.ContentType = PlainTextCodec.DefaultContentType;
622 await Response.Write("1");
623 }
624
625 private async Task GetGroups(HttpRequest Request, HttpResponse Response)
626 {
627 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
628
629 if (!Request.HasData)
630 {
631 await Response.SendResponse(new BadRequestException());
632 return;
633 }
634
635 ContentResponse Content = await Request.DecodeDataAsync();
636
637 if (Content.HasError || !(Content.Decoded is string StartsWith) || string.IsNullOrEmpty(StartsWith))
638 {
639 await Response.SendResponse(new BadRequestException());
640 return;
641 }
642
643 SuggestionEventArgs e = new SuggestionEventArgs(StartsWith.Trim());
644
645 foreach (RosterItem Item in Gateway.XmppClient.Roster)
646 {
647 foreach (string Group in Item.Groups)
648 e.AddSuggestion(Group);
649 }
650
651 await OnGetGroupSuggestions.Raise(this, e);
652
653 StringBuilder sb = new StringBuilder();
654 string[] Groups = e.ToArray();
655 bool First = true;
656 int Nr = 0;
657
658 sb.Append("{\"groups\":[");
659
660 foreach (string Group in Groups)
661 {
662 if (First)
663 First = false;
664 else
665 sb.Append(',');
666
667 sb.Append('"');
668 sb.Append(CommonTypes.JsonStringEncode(Group));
669 sb.Append('"');
670
671 Nr++;
672 }
673
674 sb.Append("],\"count\":");
675 sb.Append(Nr.ToString());
676 sb.Append("}");
677
678 Response.ContentType = JsonCodec.DefaultContentType;
679 await Response.Write(sb.ToString());
680 }
681
685 public static event EventHandlerAsync<SuggestionEventArgs> OnGetGroupSuggestions = null;
686
690 public static event EventHandlerAsync<SuggestionEventArgs> OnGetNickNameSuggestions = null;
691
692 private async Task AcceptRequest(HttpRequest Request, HttpResponse Response)
693 {
694 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
695
696 if (!Request.HasData)
697 {
698 await Response.SendResponse(new BadRequestException());
699 return;
700 }
701
702 ContentResponse Content = await Request.DecodeDataAsync();
703 if (Content.HasError || !(Content.Decoded is string JID))
704 {
705 await Response.SendResponse(new BadRequestException());
706 return;
707 }
708
709 Response.ContentType = PlainTextCodec.DefaultContentType;
710
712
713 PresenceEventArgs SubscriptionRequest = Client.GetSubscriptionRequest(JID);
714 if (SubscriptionRequest is null)
715 await Response.Write("0");
716 else
717 {
718 await SubscriptionRequest.Accept();
719 Log.Informational("Accepting presence subscription request.", SubscriptionRequest.FromBareJID);
720
722 {
726 }
727
728 await Response.Write("1");
729 }
730 }
731
732 private async Task DeclineRequest(HttpRequest Request, HttpResponse Response)
733 {
734 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
735
736 if (!Request.HasData)
737 {
738 await Response.SendResponse(new BadRequestException());
739 return;
740 }
741
742 ContentResponse Content = await Request.DecodeDataAsync();
743 if (Content.HasError || !(Content.Decoded is string JID))
744 {
745 await Response.SendResponse(new BadRequestException());
746 return;
747 }
748
749 Response.ContentType = PlainTextCodec.DefaultContentType;
750
751 XmppClient Client = Gateway.XmppClient;
752
753 PresenceEventArgs SubscriptionRequest = Client.GetSubscriptionRequest(JID);
754 if (SubscriptionRequest is null)
755 await Response.Write("0");
756 else
757 {
758 await SubscriptionRequest.Decline();
759 Log.Informational("Declining presence subscription request.", SubscriptionRequest.FromBareJID);
760
761 this.RosterItemRemoved(JID);
762
763 await Response.Write("1");
764 }
765 }
766
771 public override Task<bool> SimplifiedConfiguration()
772 {
773 return Task.FromResult(true);
774 }
775
779 public const string GATEWAY_ROSTER_ADD = nameof(GATEWAY_ROSTER_ADD);
780
785
789 public const string GATEWAY_ROSTER_ACCEPT = nameof(GATEWAY_ROSTER_ACCEPT);
790
794 public const string GATEWAY_ROSTER_GROUPS = nameof(GATEWAY_ROSTER_GROUPS);
795
799 public const string GATEWAY_ROSTER_GRP_ = nameof(GATEWAY_ROSTER_GRP_);
800
804 public const string GATEWAY_ROSTER_NAME_ = nameof(GATEWAY_ROSTER_NAME_);
805
810 public override Task<bool> EnvironmentConfiguration()
811 {
812 Dictionary<string, string> ToAdd = GetDictionaryElementWithNames(GATEWAY_ROSTER_ADD);
813 if (!(ToAdd is null) && !this.ValidateJids(ToAdd.Keys, GATEWAY_ROSTER_ADD))
814 return Task.FromResult(false);
815
816 Dictionary<string, bool> ToSubscribe = GetDictionaryElements(GATEWAY_ROSTER_SUBSCRIBE);
817 if (!(ToSubscribe is null) && !this.ValidateJids(ToSubscribe.Keys, GATEWAY_ROSTER_SUBSCRIBE))
818 return Task.FromResult(false);
819
820 Dictionary<string, bool> ToAccept = GetDictionaryElements(GATEWAY_ROSTER_ACCEPT);
821 if (!(ToAccept is null) && !this.ValidateJids(ToAccept.Keys, GATEWAY_ROSTER_ACCEPT))
822 return Task.FromResult(false);
823
824 string[] GroupNames = GetElements(GATEWAY_ROSTER_GROUPS);
825
826 if (ToAdd is null && ToSubscribe is null && ToAccept is null && GroupNames is null)
827 return Task.FromResult(true);
828
829 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid =
830 new Dictionary<string, SortedDictionary<string, bool>>(StringComparer.InvariantCultureIgnoreCase);
831
832 foreach (string Group in GroupNames)
833 {
834 string Name = GATEWAY_ROSTER_GRP_ + Group;
835 string[] Jids = GetElements(Name);
836 if (Jids is null)
837 {
838 this.LogEnvironmentVariableMissingError(Name, string.Empty);
839 return Task.FromResult(false);
840 }
841
842 if (!this.ValidateJids(Jids, GATEWAY_ROSTER_GROUPS))
843 return Task.FromResult(false);
844
845 foreach (string Jid in Jids)
846 {
847 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> Groups))
848 {
849 Groups = new SortedDictionary<string, bool>();
850 GroupsByJid[Jid] = Groups;
851 }
852
853 Groups[Group] = true;
854 }
855 }
856
857 this.toAdd = ToAdd;
858 this.toSubscribe = ToSubscribe;
859 this.toAccept = ToAccept;
860 this.groupsByJid = GroupsByJid;
861
862 this.AddHandlers();
863
864 return Task.FromResult(true);
865 }
866
867 private Dictionary<string, string> toAdd = null;
868 private Dictionary<string, bool> toSubscribe = null;
869 private Dictionary<string, bool> toAccept = null;
870 private Dictionary<string, SortedDictionary<string, bool>> groupsByJid = null;
871
872 private bool ValidateJids(IEnumerable<string> Jids, string ParameterName)
873 {
874 foreach (string Jid in Jids)
875 {
876 if (!XmppClient.BareJidRegEx.IsMatch(Jid))
877 {
878 this.LogEnvironmentError("Not a valid Bare JID.", ParameterName, Jid);
879 return false;
880 }
881 }
882
883 return true;
884 }
885
886 private (string, string, string[]) PopToAdd()
887 {
888 Dictionary<string, string> ToAdd = this.toAdd;
889 if (ToAdd is null)
890 return (null, null, null);
891
892 string Jid = null;
893 string Name = null;
894
895 lock (ToAdd)
896 {
897 foreach (KeyValuePair<string, string> P in this.toAdd)
898 {
899 Jid = P.Key;
900 Name = P.Value;
901 this.toAdd.Remove(Jid);
902 break;
903 }
904 }
905
906 if (Jid is null)
907 {
908 this.toAdd = null;
909 return (null, null, null);
910 }
911
912 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid = this.groupsByJid;
913 if (GroupsByJid is null)
914 return (Jid, Name, null);
915
916 string[] Groups;
917
918 lock (GroupsByJid)
919 {
920 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> GroupsOrdered))
921 return (Jid, Name, null);
922
923 GroupsByJid.Remove(Jid);
924 if (GroupsByJid.Count == 0)
925 this.groupsByJid = null;
926
927 Groups = new string[GroupsOrdered.Count];
928 GroupsOrdered.Keys.CopyTo(Groups, 0);
929 }
930
931 return (Jid, Name, Groups);
932 }
933
934 private string PopToSubscribe()
935 {
936 Dictionary<string, bool> ToSubscribe = this.toSubscribe;
937 if (ToSubscribe is null)
938 return null;
939
940 lock (ToSubscribe)
941 {
942 foreach (string Jid in this.toSubscribe.Keys)
943 {
944 this.toSubscribe.Remove(Jid);
945 return Jid;
946 }
947 }
948
949 this.toSubscribe = null;
950 return null;
951 }
952
953 private bool AcceptSubscriptionRequest(string Jid)
954 {
955 Dictionary<string, bool> ToAccept = this.toAccept;
956 if (ToAccept is null)
957 return false;
958
959 lock (ToAccept)
960 {
961 if (!ToAccept.ContainsKey(Jid))
962 return false;
963
964 ToAccept.Remove(Jid);
965 if (ToAccept.Count == 0)
966 this.toAccept = null;
967 }
968
969 return true;
970 }
971
972 private static string[] GetElements(string VariableName)
973 {
974 string Value = Environment.GetEnvironmentVariable(VariableName);
975 if (string.IsNullOrEmpty(Value))
976 return null;
977 else
978 return Value.Split(',');
979 }
980
981 private static Dictionary<string, bool> GetDictionaryElements(string VariableName)
982 {
983 string[] Elements = GetElements(VariableName);
984 if (Elements is null)
985 return null;
986
987 Dictionary<string, bool> Result = new Dictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
988
989 foreach (string Element in Elements)
990 Result[Element] = true;
991
992 return Result;
993 }
994
995 private static Dictionary<string, string> GetDictionaryElementWithNames(string VariableName)
996 {
997 string[] Elements = GetElements(VariableName);
998 if (Elements is null)
999 return null;
1000
1001 Dictionary<string, string> Result = new Dictionary<string, string>();
1002
1003 foreach (string Element in Elements)
1004 {
1005 string Name = Environment.GetEnvironmentVariable(GATEWAY_ROSTER_NAME_ + Element);
1006 Result[Element] = Name ?? string.Empty;
1007 }
1008
1009 return Result;
1010 }
1011 }
1012}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static string JsonStringEncode(string s)
Encodes a string for inclusion in JSON.
Definition: CommonTypes.cs:805
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
static string GetBody(string Html)
Extracts the contents of the BODY element in a HTML string.
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
const string DefaultContentType
application/json
Definition: JsonCodec.cs:20
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
Definition: Gateway.cs:3868
static RequiredPrivileges LoggedIn(string[] Privileges)
Authentication mechanism that makes sure the call is made from a session with a valid authenticated u...
Definition: Gateway.cs:3809
static XmppClient XmppClient
XMPP Client connection of gateway.
Definition: Gateway.cs:4038
Allows the user to configure the XMPP Roster of the gateway.
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
override Task ConfigureSystem()
Is called during startup to configure the system.
const string GATEWAY_ROSTER_ADD
Optional Comma-separated list of Bare JIDs to add to the roster.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
override Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
const string GATEWAY_ROSTER_ACCEPT
Optional Comma-separated list of Bare JIDs to accept presence subscription requests from.
static EventHandlerAsync< SuggestionEventArgs > OnGetNickNameSuggestions
Event raised when list of nickname suggestions is populated.
const string GATEWAY_ROSTER_GRP_
Optional Comma-separated list of Bare JIDs in the roster to add to the group [group].
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
const string GATEWAY_ROSTER_GROUPS
Optional Comma-separated list of groups to define.
const string GATEWAY_ROSTER_SUBSCRIBE
Optional Comma-separated list of Bare JIDs to send presence subscription requests to.
RosterConfiguration()
Allows the user to configure the XMPP Roster of the gateway.
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
static RosterConfiguration Instance
Instance of configuration object.
static EventHandlerAsync< SuggestionEventArgs > OnGetGroupSuggestions
Event raised when list of group suggestions is populated.
override async Task< bool > SetupConfiguration(HttpServer WebServer)
Waits for the user to provide configuration.
const string GATEWAY_ROSTER_NAME_
Optional human-readable name of a JID in the roster.
Abstract base class for system configurations.
bool Complete
If the configuration is complete.
void LogEnvironmentVariableMissingError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value is missing.
void LogEnvironmentError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value contains an error.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP authentication schemes, as defined in RFC-7235: https://datatracker....
Represents an HTTP request.
Definition: HttpRequest.cs:22
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
bool HasData
If the request has data.
Definition: HttpRequest.cs:113
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:139
Base class for all HTTP resources.
Definition: HttpResource.cs:23
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Task Write(byte[] Data)
Returns binary data in the response.
Implements an HTTP server.
Definition: HttpServer.cs:41
static SessionVariables CreateSessionVariables()
Creates a new collection of variables, that contains access to the global set of variables.
Definition: HttpServer.cs:2130
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
Definition: HttpServer.cs:1568
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Definition: HttpServer.cs:1743
The server has not found anything matching the Request-URI. No indication is given of whether the con...
Event arguments for presence events.
string FromBareJID
Bare JID of resource sending the presence.
async Task Decline()
Declines a subscription or unsubscription request.
async Task Accept()
Accepts a subscription or unsubscription request.
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
SubscriptionState State
roup Current subscription state.
Definition: RosterItem.cs:268
string[] Groups
Any groups the roster item belongs to.
Definition: RosterItem.cs:186
string BareJid
Bare JID of the roster item.
Definition: RosterItem.cs:276
string Name
Name of the roster item.
Definition: RosterItem.cs:282
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
XmppState State
Current state of connection.
Definition: XmppClient.cs:985
KeyValuePair< string, string >[] LastSetPresenceCustomStatus
Last custom status set by the client when setting presence.
Definition: XmppClient.cs:969
Task RemoveRosterItem(string BareJID)
Removes an item from the roster.
Definition: XmppClient.cs:4680
Task UpdateRosterItem(string BareJID, string Name, params string[] Groups)
Updates an item in the roster.
Definition: XmppClient.cs:4635
XmppClient(string Host, int Port, string UserName, string Password, string Language, Assembly AppAssembly, params ISniffer[] Sniffers)
Manages an XMPP client connection over a traditional binary socket connection.
Definition: XmppClient.cs:373
Task RequestPresenceSubscription(string BareJid)
Requests subscription of presence information from a contact.
Definition: XmppClient.cs:4969
PresenceEventArgs GetSubscriptionRequest(string BareJID)
Gets a presence subscription request
Definition: XmppClient.cs:4759
static string EmbedNickName(string NickName)
Generates custom XML for embedding a nickname, as defined in XEP-0172. Can be used with RequestPresen...
Definition: XmppClient.cs:4981
static readonly Regex BareJidRegEx
Regular expression for Bare JIDs
Definition: XmppClient.cs:187
Availability LastSetPresenceAvailability
Last availability set by the client when setting presence.
Definition: XmppClient.cs:964
Task RequestPresenceUnsubscription(string BareJid)
Requests unssubscription of presence information from a contact.
Definition: XmppClient.cs:5033
Task Connect()
Connects the client.
Definition: XmppClient.cs:641
Task SetPresence()
Sets the presence of the connection. Add a CustomPresenceXml event handler to add custom presence XML...
Definition: XmppClient.cs:4825
RosterItem GetRosterItem(string BareJID)
Gets a roster item.
Definition: XmppClient.cs:4571
Contains static methods
Definition: Files.cs:14
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Definition: Files.cs:84
Contains information about a language.
Definition: Language.cs:17
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Definition: Language.cs:209
Collection of variables.
Definition: Variables.cs:25
Interface for system configurations. The gateway will scan all module for system configuration classe...
Definition: ImplTypes.g.cs:58
Availability
Resource availability.
Definition: Availability.cs:7
SubscriptionState
State of a presence subscription.
Definition: RosterItem.cs:16
XmppState
State of XMPP connection.
Definition: XmppState.cs:7