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;
2using System.Collections.Generic;
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(new RosterItem[0], 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 }, new PresenceEventArgs[0]))
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 Resources.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 throw new BadRequestException();
380
381 object Obj = await Request.DecodeDataAsync();
382 if (!(Obj is string JID))
383 throw new BadRequestException();
384
385 Response.ContentType = PlainTextCodec.DefaultContentType;
386
387 string JidToValidate = JID;
388 if (CaseInsensitiveString.IsNullOrEmpty(Gateway.Domain) && JidToValidate.EndsWith("@"))
389 JidToValidate += "example.org";
390
391 if (!XmppClient.BareJidRegEx.IsMatch(JidToValidate))
392 await Response.Write("0");
393 else
394 {
396 if (Item is null)
397 {
398 await Gateway.XmppClient.RequestPresenceSubscription(JID, await this.NickName());
399 Log.Informational("Requesting presence subscription.", JID);
400 await Response.Write("1");
401 }
402 else if (Item.State != SubscriptionState.Both && Item.State != SubscriptionState.To)
403 {
404 await Gateway.XmppClient.RequestPresenceSubscription(JID, await this.NickName());
405 Log.Informational("Requesting presence subscription.", JID);
406 await Response.Write("2");
407 }
408 else
409 await Response.Write("3");
410 }
411 }
412
413 private async Task<string> NickName()
414 {
415 SuggestionEventArgs e = new SuggestionEventArgs(string.Empty);
416 await OnGetNickNameSuggestions.Raise(this, e);
417
418 string[] Suggestions = e.ToArray();
419 string NickName = Suggestions.Length > 0 ? Suggestions[0] : (string)Gateway.Domain;
420
421 return XmppClient.EmbedNickName(NickName);
422 }
423
424 private async Task RemoveContact(HttpRequest Request, HttpResponse Response)
425 {
426 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
427
428 if (!Request.HasData)
429 throw new BadRequestException();
430
431 object Obj = await Request.DecodeDataAsync();
432 if (!(Obj is string JID))
433 throw new BadRequestException();
434
435 Response.ContentType = PlainTextCodec.DefaultContentType;
436
437 XmppClient Client = Gateway.XmppClient;
438 RosterItem Contact = Client.GetRosterItem(JID);
439 if (Contact is null)
440 await Response.Write("0");
441 else
442 {
443 await Client.RemoveRosterItem(Contact.BareJid);
444 Log.Informational("Contact removed.", Contact.BareJid);
445 await Response.Write("1");
446 }
447 }
448
449 private async Task UnsubscribeContact(HttpRequest Request, HttpResponse Response)
450 {
451 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
452
453 if (!Request.HasData)
454 throw new BadRequestException();
455
456 object Obj = await Request.DecodeDataAsync();
457 if (!(Obj is string JID))
458 throw new BadRequestException();
459
460 Response.ContentType = PlainTextCodec.DefaultContentType;
461
462 XmppClient Client = Gateway.XmppClient;
463 RosterItem Contact = Client.GetRosterItem(JID);
464 if (Contact is null)
465 await Response.Write("0");
466 else
467 {
468 await Client.RequestPresenceUnsubscription(Contact.BareJid);
469 Log.Informational("Unsubscribing from presence.", Contact.BareJid);
470 await Response.Write("1");
471 }
472 }
473
474 private async Task SubscribeToContact(HttpRequest Request, HttpResponse Response)
475 {
476 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
477
478 if (!Request.HasData)
479 throw new BadRequestException();
480
481 object Obj = await Request.DecodeDataAsync();
482 if (!(Obj is string JID))
483 throw new BadRequestException();
484
485 Response.ContentType = PlainTextCodec.DefaultContentType;
486
487 XmppClient Client = Gateway.XmppClient;
488 RosterItem Contact = Client.GetRosterItem(JID);
489 if (Contact is null)
490 await Response.Write("0");
491 else
492 {
493 await Client.RequestPresenceSubscription(Contact.BareJid, await this.NickName());
494 Log.Informational("Requesting presence subscription.", Contact.BareJid);
495 await Response.Write("1");
496 }
497 }
498
499 private async Task RenameContact(HttpRequest Request, HttpResponse Response)
500 {
501 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
502
503 if (!Request.HasData)
504 throw new BadRequestException();
505
506 object Obj = await Request.DecodeDataAsync();
507 string JID = Request.Header["X-BareJID"];
508 string NewName = Obj as string;
509 if (JID is null || string.IsNullOrEmpty(JID))
510 throw new BadRequestException();
511
512 Response.ContentType = PlainTextCodec.DefaultContentType;
513
514 XmppClient Client = Gateway.XmppClient;
515 RosterItem Contact = Client.GetRosterItem(JID);
516 if (Contact is null)
517 await Response.Write("0");
518 else
519 {
520 await Client.UpdateRosterItem(Contact.BareJid, NewName, Contact.Groups);
521 Log.Informational("Contact renamed.", Contact.BareJid, new KeyValuePair<string, object>("Name", NewName));
522 await Response.Write("1");
523 }
524 }
525
526 private async Task UpdateContactGroups(HttpRequest Request, HttpResponse Response)
527 {
528 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
529
530 if (!Request.HasData)
531 throw new BadRequestException();
532
533 object Obj = await Request.DecodeDataAsync();
534 string BareJid = Obj as string;
535 if (string.IsNullOrEmpty(BareJid))
536 throw new BadRequestException();
537
538 XmppClient Client = Gateway.XmppClient;
539 RosterItem Contact = Client.GetRosterItem(BareJid)
540 ?? throw new NotFoundException();
541
542 SortedDictionary<string, bool> Groups = new SortedDictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
543 string[] GroupsArray;
544 int i = 0;
545 string s;
546
547 while (!string.IsNullOrEmpty(s = System.Web.HttpUtility.UrlDecode(Request.Header["X-Group-" + (++i).ToString()])))
548 Groups[s] = true;
549
550 if (Groups.Count > 0)
551 {
552 GroupsArray = new string[Groups.Count];
553 Groups.Keys.CopyTo(GroupsArray, 0);
554 }
555 else
556 GroupsArray = new string[0];
557
558 StringBuilder sb = new StringBuilder();
559 bool First = true;
560
561 if (!(Groups is null))
562 {
563 foreach (string Group in GroupsArray)
564 {
565 if (First)
566 First = false;
567 else
568 sb.Append(", ");
569
570 sb.Append(Group);
571 }
572 }
573
574 await Client.UpdateRosterItem(Contact.BareJid, Contact.Name, GroupsArray);
575 Log.Informational("Contact groups updated.", Contact.BareJid, new KeyValuePair<string, object>("Groups", sb.ToString()));
576
577 Response.ContentType = PlainTextCodec.DefaultContentType;
578 await Response.Write("1");
579 }
580
581 private async Task GetGroups(HttpRequest Request, HttpResponse Response)
582 {
583 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
584
585 if (!Request.HasData)
586 throw new BadRequestException();
587
588 string StartsWith = await Request.DecodeDataAsync() as string;
589 if (string.IsNullOrEmpty(StartsWith))
590 throw new BadRequestException();
591
592 SuggestionEventArgs e = new SuggestionEventArgs(StartsWith.Trim());
593
594 foreach (RosterItem Item in Gateway.XmppClient.Roster)
595 {
596 foreach (string Group in Item.Groups)
597 e.AddSuggestion(Group);
598 }
599
600 await OnGetGroupSuggestions.Raise(this, e);
601
602 StringBuilder sb = new StringBuilder();
603 string[] Groups = e.ToArray();
604 bool First = true;
605 int Nr = 0;
606
607 sb.Append("{\"groups\":[");
608
609 foreach (string Group in Groups)
610 {
611 if (First)
612 First = false;
613 else
614 sb.Append(',');
615
616 sb.Append('"');
617 sb.Append(CommonTypes.JsonStringEncode(Group));
618 sb.Append('"');
619
620 Nr++;
621 }
622
623 sb.Append("],\"count\":");
624 sb.Append(Nr.ToString());
625 sb.Append("}");
626
627 Response.ContentType = JsonCodec.DefaultContentType;
628 await Response.Write(sb.ToString());
629 }
630
634 public static event EventHandlerAsync<SuggestionEventArgs> OnGetGroupSuggestions = null;
635
639 public static event EventHandlerAsync<SuggestionEventArgs> OnGetNickNameSuggestions = null;
640
641 private async Task AcceptRequest(HttpRequest Request, HttpResponse Response)
642 {
643 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
644
645 if (!Request.HasData)
646 throw new BadRequestException();
647
648 object Obj = await Request.DecodeDataAsync();
649 if (!(Obj is string JID))
650 throw new BadRequestException();
651
652 Response.ContentType = PlainTextCodec.DefaultContentType;
653
655
656 PresenceEventArgs SubscriptionRequest = Client.GetSubscriptionRequest(JID);
657 if (SubscriptionRequest is null)
658 await Response.Write("0");
659 else
660 {
661 await SubscriptionRequest.Accept();
662 Log.Informational("Accepting presence subscription request.", SubscriptionRequest.FromBareJID);
663
665 {
669 }
670
671 await Response.Write("1");
672 }
673 }
674
675 private async Task DeclineRequest(HttpRequest Request, HttpResponse Response)
676 {
677 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
678
679 if (!Request.HasData)
680 throw new BadRequestException();
681
682 object Obj = await Request.DecodeDataAsync();
683 if (!(Obj is string JID))
684 throw new BadRequestException();
685
686 Response.ContentType = PlainTextCodec.DefaultContentType;
687
689
690 PresenceEventArgs SubscriptionRequest = Client.GetSubscriptionRequest(JID);
691 if (SubscriptionRequest is null)
692 await Response.Write("0");
693 else
694 {
695 await SubscriptionRequest.Decline();
696 Log.Informational("Declining presence subscription request.", SubscriptionRequest.FromBareJID);
697
698 this.RosterItemRemoved(JID);
699
700 await Response.Write("1");
701 }
702 }
703
708 public override Task<bool> SimplifiedConfiguration()
709 {
710 return Task.FromResult(true);
711 }
712
716 public const string GATEWAY_ROSTER_ADD = nameof(GATEWAY_ROSTER_ADD);
717
722
726 public const string GATEWAY_ROSTER_ACCEPT = nameof(GATEWAY_ROSTER_ACCEPT);
727
731 public const string GATEWAY_ROSTER_GROUPS = nameof(GATEWAY_ROSTER_GROUPS);
732
736 public const string GATEWAY_ROSTER_GRP_ = nameof(GATEWAY_ROSTER_GRP_);
737
741 public const string GATEWAY_ROSTER_NAME_ = nameof(GATEWAY_ROSTER_NAME_);
742
747 public override Task<bool> EnvironmentConfiguration()
748 {
749 Dictionary<string, string> ToAdd = GetDictionaryElementWithNames(GATEWAY_ROSTER_ADD);
750 if (!(ToAdd is null) && !this.ValidateJids(ToAdd.Keys, GATEWAY_ROSTER_ADD))
751 return Task.FromResult(false);
752
753 Dictionary<string, bool> ToSubscribe = GetDictionaryElements(GATEWAY_ROSTER_SUBSCRIBE);
754 if (!(ToSubscribe is null) && !this.ValidateJids(ToSubscribe.Keys, GATEWAY_ROSTER_SUBSCRIBE))
755 return Task.FromResult(false);
756
757 Dictionary<string, bool> ToAccept = GetDictionaryElements(GATEWAY_ROSTER_ACCEPT);
758 if (!(ToAccept is null) && !this.ValidateJids(ToAccept.Keys, GATEWAY_ROSTER_ACCEPT))
759 return Task.FromResult(false);
760
761 string[] GroupNames = GetElements(GATEWAY_ROSTER_GROUPS);
762
763 if (ToAdd is null && ToSubscribe is null && ToAccept is null && GroupNames is null)
764 return Task.FromResult(true);
765
766 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid =
767 new Dictionary<string, SortedDictionary<string, bool>>(StringComparer.InvariantCultureIgnoreCase);
768
769 foreach (string Group in GroupNames)
770 {
771 string Name = GATEWAY_ROSTER_GRP_ + Group;
772 string[] Jids = GetElements(Name);
773 if (Jids is null)
774 {
775 this.LogEnvironmentVariableMissingError(Name, string.Empty);
776 return Task.FromResult(false);
777 }
778
779 if (!this.ValidateJids(Jids, GATEWAY_ROSTER_GROUPS))
780 return Task.FromResult(false);
781
782 foreach (string Jid in Jids)
783 {
784 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> Groups))
785 {
786 Groups = new SortedDictionary<string, bool>();
787 GroupsByJid[Jid] = Groups;
788 }
789
790 Groups[Group] = true;
791 }
792 }
793
794 this.toAdd = ToAdd;
795 this.toSubscribe = ToSubscribe;
796 this.toAccept = ToAccept;
797 this.groupsByJid = GroupsByJid;
798
799 this.AddHandlers();
800
801 return Task.FromResult(true);
802 }
803
804 private Dictionary<string, string> toAdd = null;
805 private Dictionary<string, bool> toSubscribe = null;
806 private Dictionary<string, bool> toAccept = null;
807 private Dictionary<string, SortedDictionary<string, bool>> groupsByJid = null;
808
809 private bool ValidateJids(IEnumerable<string> Jids, string ParameterName)
810 {
811 foreach (string Jid in Jids)
812 {
813 if (!XmppClient.BareJidRegEx.IsMatch(Jid))
814 {
815 this.LogEnvironmentError("Not a valid Bare JID.", ParameterName, Jid);
816 return false;
817 }
818 }
819
820 return true;
821 }
822
823 private (string, string, string[]) PopToAdd()
824 {
825 Dictionary<string, string> ToAdd = this.toAdd;
826 if (ToAdd is null)
827 return (null, null, null);
828
829 string Jid = null;
830 string Name = null;
831
832 lock (ToAdd)
833 {
834 foreach (KeyValuePair<string, string> P in this.toAdd)
835 {
836 Jid = P.Key;
837 Name = P.Value;
838 this.toAdd.Remove(Jid);
839 break;
840 }
841 }
842
843 if (Jid is null)
844 {
845 this.toAdd = null;
846 return (null, null, null);
847 }
848
849 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid = this.groupsByJid;
850 if (GroupsByJid is null)
851 return (Jid, Name, null);
852
853 string[] Groups;
854
855 lock (GroupsByJid)
856 {
857 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> GroupsOrdered))
858 return (Jid, Name, null);
859
860 GroupsByJid.Remove(Jid);
861 if (GroupsByJid.Count == 0)
862 this.groupsByJid = null;
863
864 Groups = new string[GroupsOrdered.Count];
865 GroupsOrdered.Keys.CopyTo(Groups, 0);
866 }
867
868 return (Jid, Name, Groups);
869 }
870
871 private string PopToSubscribe()
872 {
873 Dictionary<string, bool> ToSubscribe = this.toSubscribe;
874 if (ToSubscribe is null)
875 return null;
876
877 lock (ToSubscribe)
878 {
879 foreach (string Jid in this.toSubscribe.Keys)
880 {
881 this.toSubscribe.Remove(Jid);
882 return Jid;
883 }
884 }
885
886 this.toSubscribe = null;
887 return null;
888 }
889
890 private bool AcceptSubscriptionRequest(string Jid)
891 {
892 Dictionary<string, bool> ToAccept = this.toAccept;
893 if (ToAccept is null)
894 return false;
895
896 lock (ToAccept)
897 {
898 if (!ToAccept.ContainsKey(Jid))
899 return false;
900
901 ToAccept.Remove(Jid);
902 if (ToAccept.Count == 0)
903 this.toAccept = null;
904 }
905
906 return true;
907 }
908
909 private static string[] GetElements(string VariableName)
910 {
911 string Value = Environment.GetEnvironmentVariable(VariableName);
912 if (string.IsNullOrEmpty(Value))
913 return null;
914 else
915 return Value.Split(',');
916 }
917
918 private static Dictionary<string, bool> GetDictionaryElements(string VariableName)
919 {
920 string[] Elements = GetElements(VariableName);
921 if (Elements is null)
922 return null;
923
924 Dictionary<string, bool> Result = new Dictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
925
926 foreach (string Element in Elements)
927 Result[Element] = true;
928
929 return Result;
930 }
931
932 private static Dictionary<string, string> GetDictionaryElementWithNames(string VariableName)
933 {
934 string[] Elements = GetElements(VariableName);
935 if (Elements is null)
936 return null;
937
938 Dictionary<string, string> Result = new Dictionary<string, string>();
939
940 foreach (string Element in Elements)
941 {
942 string Name = Environment.GetEnvironmentVariable(GATEWAY_ROSTER_NAME_ + Element);
943 Result[Element] = Name ?? string.Empty;
944 }
945
946 return Result;
947 }
948 }
949}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static string JsonStringEncode(string s)
Encodes a string for inclusion in JSON.
Definition: CommonTypes.cs:803
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:14
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
const string DefaultContentType
application/json
Definition: JsonCodec.cs:19
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.
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Definition: Resources.cs:205
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:13
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:334
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:2354
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:3041
static RequiredUserPrivileges LoggedIn(string[] Privileges)
Authentication mechanism that makes sure the call is made from a session with a valid authenticated u...
Definition: Gateway.cs:3001
static XmppClient XmppClient
XMPP Client connection of gateway.
Definition: Gateway.cs:3187
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 string Resource
Resource to be redirected to, to perform the configuration.
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:18
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:134
bool HasData
If the request has data.
Definition: HttpRequest.cs:74
async Task< object > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:95
Base class for all HTTP resources.
Definition: HttpResource.cs:23
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:21
async Task Write(byte[] Data)
Returns binary data in the response.
Implements an HTTP server.
Definition: HttpServer.cs:36
static Variables CreateVariables()
Creates a new collection of variables, that contains access to the global set of variables.
Definition: HttpServer.cs:1604
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
Definition: HttpServer.cs:1287
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Definition: HttpServer.cs:1438
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:59
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:4631
Task UpdateRosterItem(string BareJID, string Name, params string[] Groups)
Updates an item in the roster.
Definition: XmppClient.cs:4586
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:4919
PresenceEventArgs GetSubscriptionRequest(string BareJID)
Gets a presence subscription request
Definition: XmppClient.cs:4710
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:4931
static readonly Regex BareJidRegEx
Regular expression for Bare JIDs
Definition: XmppClient.cs:188
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:4980
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:4776
RosterItem GetRosterItem(string BareJID)
Gets a roster item.
Definition: XmppClient.cs:4522
Represents a case-insensitive string.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
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...
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