Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PersistenceLayer.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
6using System.Xml;
7using Waher.Content;
9using Waher.Events;
17using Waher.Script;
24
26{
27 public delegate Task PushNotificationEventHandler(PushNotificationToken Token, object Content);
28
30 {
31 private static readonly Dictionary<CaseInsensitiveString, Dictionary<string, Dictionary<string, string>>> functionsPerTabIdPerFqnPerBareJid = new Dictionary<CaseInsensitiveString, Dictionary<string, Dictionary<string, string>>>();
32
33 private readonly Dictionary<CaseInsensitiveString, Account> accounts = new Dictionary<CaseInsensitiveString, Account>();
34 private readonly Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, RosterItem>> rosters = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, RosterItem>>();
35 private readonly Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Block>> blocks = new Dictionary<CaseInsensitiveString, Dictionary<CaseInsensitiveString, Block>>();
36 private XmppServer server = null;
37
38 public PersistenceLayer()
39 {
40 }
41
46 {
47 get => this.server;
48 set
49 {
50 if (this.server is null)
51 this.server = value;
52 else if (this.server != value)
53 throw new InvalidOperationException("Not allowed to change server reference in runtime.");
54 }
55 }
56
57 public string Domain => this.server.Domain;
59
60 #region API keys
61
67 internal async Task<ApiKey> GetApiKey(string ApiKey)
68 {
69 return await Database.FindFirstDeleteRest<ApiKey>(new FilterFieldEqualTo("Key", ApiKey));
70 }
71
77 public async Task<string> GetApiKeySecret(string ApiKey)
78 {
79 return (await this.GetApiKey(ApiKey))?.Secret;
80 }
81
82 #endregion
83
84 #region Accounts
85
86 internal async Task<Account> GetAccountEx(CaseInsensitiveString UserName)
87 {
88 lock (this.accounts)
89 {
90 if (this.accounts.TryGetValue(UserName, out Account Account))
91 return Account;
92 }
93
94 foreach (Account Account in await Database.Find<Account>(new FilterFieldEqualTo("UserName", UserName)))
95 {
96 lock (this.accounts)
97 {
98 this.accounts[Account.UserName] = Account;
99 }
100
101 return Account;
102 }
103
104 return null;
105 }
106
118 public async Task<KeyValuePair<Networking.XMPP.Server.IAccount, string[]>> CreateAccount(string ApiKey, CaseInsensitiveString UserName, string Password,
120 {
121 ApiKey ApiKeyObject = null;
122 Account Account = await this.GetAccountEx(UserName);
123 if (!(Account is null))
124 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
125
126 if (!(XmppServerModule.PubSub is null))
127 {
128 PubSubNode Node = await XmppServerModule.PubSub.GetNodeAsync(string.Empty, UserName, null, XmppAddress.Empty, null);
129 if (!(Node is null))
130 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
131 }
132
133 try
134 {
135 string s = Path.Combine(Gateway.RootFolder, UserName);
136
137 if (File.Exists(s) || Directory.Exists(s))
138 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, await GetAlternativeAccountNames(UserName));
139 }
140 catch (Exception)
141 {
142 // Ignore.
143 }
144
145 if (string.IsNullOrEmpty(ApiKey))
146 {
147 Account = new Account()
148 {
149 ApiKey = string.Empty,
150 UserName = UserName,
151 Password = Password,
152 EMail = EMail,
153 EMailVerified = null,
154 PhoneNr = PhoneNr,
155 PhoneNrVerified = null,
156 Created = DateTime.Now
157 };
158
159 await Database.Insert(Account);
160 }
161 else
162 {
163 foreach (ApiKey Key in await Database.Find<ApiKey>(new FilterFieldEqualTo("Key", ApiKey)))
164 {
165 if (Key.NrCreated - Key.NrDeleted >= Key.MaxAccounts)
166 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(null, null);
167
168 ApiKeyObject = Key;
169
170 Account = new Account()
171 {
172 ApiKey = ApiKey,
173 UserName = UserName,
174 Password = Password,
175 EMail = EMail,
176 EMailVerified = null,
177 PhoneNr = PhoneNr,
178 PhoneNrVerified = null,
179 Created = DateTime.Now
180 };
181
182 await Database.Insert(Account);
183
184 Key.NrCreated++;
185 await Database.Update(Key);
186
187 break;
188 }
189 }
190
191 if (!(Account is null) && !(XmppServerModule.Instance is null))
192 await AccountCreated(Account, ApiKeyObject, this.server.Domain, RemoteEndpoint);
193
194 return new KeyValuePair<Networking.XMPP.Server.IAccount, string[]>(Account, null);
195 }
196
197 internal static async Task<string[]> GetAlternativeAccountNames(string UserName)
198 {
199 List<string> Suggestions = new List<string>();
200 int NrDigits = 2;
201 int Max = 100;
202
203 while (Suggestions.Count < 3 && NrDigits < 9)
204 {
205 string AlternativeName = UserName + Gateway.NextInteger(Max).ToString("D" + NrDigits.ToString());
206
207 Account AlternativeAccount = await Database.FindFirstIgnoreRest<Account>(
208 new FilterFieldEqualTo("UserName", AlternativeName));
209
210 if (!(AlternativeAccount is null))
211 {
212 NrDigits++;
213 Max *= 10;
214 continue;
215 }
216
217 if (!(XmppServerModule.PubSub is null))
218 {
219 PubSubNode Node = await XmppServerModule.PubSub.GetNodeAsync(string.Empty, AlternativeName, null, XmppAddress.Empty, null);
220 if (!(Node is null))
221 {
222 NrDigits++;
223 Max *= 10;
224 continue;
225 }
226 }
227
228 try
229 {
230 string s = Path.Combine(Gateway.RootFolder, AlternativeName);
231
232 if (File.Exists(s) || Directory.Exists(s))
233 {
234 NrDigits++;
235 Max *= 10;
236 continue;
237 }
238 }
239 catch (Exception)
240 {
241 // Ignore.
242 }
243
244 Suggestions.Add(AlternativeName);
245 }
246
247 Suggestions.Sort();
248
249 return Suggestions.ToArray();
250 }
251
252 internal static async Task AccountCreated(Account Account, ApiKey ApiKeyObject,
253 string Domain, string RemoteEndpoint)
254 {
255 await Account.LoggedIn(RemoteEndpoint);
256
257 KeyValuePair<string, object>[] Tags = await LoginAuditor.Annotate(RemoteEndpoint,
258 new KeyValuePair<string, object>("ApiKey", Account.ApiKey),
259 new KeyValuePair<string, object>("Created", Account.Created),
260 new KeyValuePair<string, object>("EMail", Account.EMail?.Value),
261 new KeyValuePair<string, object>("PhoneNr", Account.PhoneNr?.Value),
262 new KeyValuePair<string, object>("Enabled", Account.Enabled),
263 new KeyValuePair<string, object>("ObjectId", Account.ObjectId),
264 new KeyValuePair<string, object>("RemoteEndpoint", RemoteEndpoint));
265
266 StringBuilder sb = new StringBuilder();
267 string WhoIsInfo = string.Empty;
268
269 sb.Append("Account created.");
271
272 Log.Informational(sb.ToString(), Account.UserName, string.Empty, "AccountCreated", EventLevel.Medium, Tags);
273
274 StringBuilder Markdown = new StringBuilder();
275 string UserNameEncoded = MarkdownDocument.Encode(Account.UserName.Value);
276 DateTime Now = DateTime.Now;
277
278 Markdown.AppendLine("XMPP Account created:");
279 Markdown.AppendLine();
280 Markdown.AppendLine("| Account Information ||");
281 Markdown.AppendLine("|:-----|:------|");
282 Markdown.Append("| User Name: | [");
283 Markdown.Append(UserNameEncoded);
284 Markdown.Append("](");
285 Markdown.Append(Gateway.GetUrl("/Account.md?UserName=" + UserNameEncoded));
286 Markdown.AppendLine(") |");
287 Markdown.Append("| JID: | [");
288 Markdown.Append(UserNameEncoded);
289 Markdown.Append('@');
290 Markdown.Append(Domain);
291 Markdown.Append("](xmpp:");
292 Markdown.Append(UserNameEncoded);
293 Markdown.Append('@');
294 Markdown.Append(Domain);
295 Markdown.Append(")");
296 Markdown.AppendLine(" |");
297
298 if (!CaseInsensitiveString.IsNullOrEmpty(Account.EMail))
299 {
300 Markdown.Append("| e-Mail: | <mailto:");
301 Markdown.Append(Account.EMail.Value);
302 Markdown.AppendLine("> |");
303 }
304
305 if (!CaseInsensitiveString.IsNullOrEmpty(Account.PhoneNr))
306 {
307 Markdown.Append("| Phone Nr: | <tel:");
308 Markdown.Append(Account.PhoneNr.Value);
309 Markdown.AppendLine("> |");
310 }
311
313
314 Markdown.Append("| Date | ");
315 Markdown.Append(MarkdownDocument.Encode(Now.ToShortDateString()));
316 Markdown.AppendLine(" |");
317 Markdown.Append("| Time | ");
318 Markdown.Append(MarkdownDocument.Encode(Now.ToLongTimeString()));
319 Markdown.AppendLine(" |");
320
321 if (!(ApiKeyObject is null))
322 {
323 Markdown.AppendLine();
324 Markdown.AppendLine("| API Key information ||");
325 Markdown.AppendLine("|:-----|:------|");
326 Markdown.Append("| API Key: | [");
327 Markdown.Append(ApiKeyObject.Key);
328 Markdown.Append("](");
329 Markdown.Append(Gateway.GetUrl("/ApiKey.md?Key=" + ApiKeyObject.Key));
330 Markdown.AppendLine(") |");
331 Markdown.Append("| Owner: | ");
332 Markdown.Append(MarkdownDocument.Encode(ApiKeyObject.Owner));
333 Markdown.AppendLine(" |");
334 Markdown.Append("| e-Mail: | <");
335 Markdown.Append(ApiKeyObject.EMail.Value);
336 Markdown.AppendLine("> |");
337 Markdown.Append("| Accounts created: | ");
338 Markdown.Append(ApiKeyObject.NrCreated.ToString());
339 Markdown.AppendLine(" |");
340 Markdown.Append("| Accounts deleted: | ");
341 Markdown.Append(ApiKeyObject.NrDeleted.ToString());
342 Markdown.AppendLine(" |");
343 Markdown.Append("| Accounts left: | ");
344 Markdown.Append((ApiKeyObject.MaxAccounts - ApiKeyObject.NrCreated + ApiKeyObject.NrDeleted).ToString());
345 Markdown.AppendLine(" |");
346 }
347
348 if (!string.IsNullOrEmpty(WhoIsInfo))
349 {
350 Markdown.AppendLine();
351 Markdown.AppendLine();
352 Markdown.AppendLine("WHOIS Information:");
353 Markdown.AppendLine();
354 Markdown.AppendLine("```");
355 Markdown.AppendLine(WhoIsInfo);
356 Markdown.AppendLine("```");
357 }
358
359 await Gateway.SendNotification(Markdown.ToString());
360 }
361
368 public async Task<bool> DeleteAccount(CaseInsensitiveString UserName, string RemoteEndpoint)
369 {
370 ApiKey ApiKeyObject = null;
371 Account Account = await this.GetAccountEx(UserName);
372 if (!(Account is null))
373 return false;
374
376
377 await Database.Delete(Account);
378 await Database.Delete(Login);
379
380 lock (this.accounts)
381 {
382 this.accounts.Remove(UserName);
383 }
384
385 if (!string.IsNullOrEmpty(Account.ApiKey))
386 {
387 foreach (ApiKey Key in await Database.Find<ApiKey>(new FilterFieldEqualTo("Key", Account.ApiKey)))
388 {
389 ApiKeyObject = Key;
390 Key.NrDeleted++;
391 await Database.Update(Key);
392
393 break;
394 }
395 }
396
397 KeyValuePair<string, object>[] Tags = await LoginAuditor.Annotate(Login.RemoteEndpoint,
398 new KeyValuePair<string, object>("ApiKey", Account.ApiKey),
399 new KeyValuePair<string, object>("Created", Account.Created),
400 new KeyValuePair<string, object>("EMail", Account.EMail?.Value),
401 new KeyValuePair<string, object>("PhoneNr", Account.PhoneNr?.Value),
402 new KeyValuePair<string, object>("Enabled", Account.Enabled),
403 new KeyValuePair<string, object>("LastLogin", Login.LastLogin),
404 new KeyValuePair<string, object>("ObjectId", Account.ObjectId),
405 new KeyValuePair<string, object>("RemoteEndpoint", Login.RemoteEndpoint));
406
407 Log.Informational("Account deleted.", Account.UserName, string.Empty, "AccountDeleted", EventLevel.Medium, Tags);
408
409 if (!(XmppServerModule.Instance is null))
410 {
411 StringBuilder Markdown = new StringBuilder();
412 string UserNameEncoded = MarkdownDocument.Encode(UserName);
413 DateTime Now = DateTime.Now;
414
415 Markdown.AppendLine("XMPP Account deleted:");
416 Markdown.AppendLine();
417 Markdown.AppendLine("| Account Information ||");
418 Markdown.AppendLine("|:-----|:------|");
419 Markdown.Append("| User Name: | ");
420 Markdown.Append(UserNameEncoded);
421 Markdown.AppendLine(" |");
422 Markdown.Append("| JID: | ");
423 Markdown.Append(UserNameEncoded);
424 Markdown.Append('@');
425 Markdown.Append(this.server.Domain);
426 Markdown.AppendLine(" |");
427
429 {
430 Markdown.Append("| e-Mail: | <mailto:");
431 Markdown.Append(Account.EMail.Value);
432 Markdown.AppendLine("> |");
433 }
434
436 {
437 Markdown.Append("| Phone Nr: | <tel:");
438 Markdown.Append(Account.PhoneNr.Value);
439 Markdown.AppendLine("> |");
440 }
441
443
444 Markdown.Append("| Date | ");
445 Markdown.Append(MarkdownDocument.Encode(Now.ToShortDateString()));
446 Markdown.AppendLine(" |");
447 Markdown.Append("| Time | ");
448 Markdown.Append(MarkdownDocument.Encode(Now.ToLongTimeString()));
449 Markdown.AppendLine(" |");
450
451 if (!(ApiKeyObject is null))
452 {
453 Markdown.AppendLine();
454 Markdown.AppendLine("| API Key information ||");
455 Markdown.AppendLine("|:-----|:------|");
456 Markdown.Append("| API Key: | [");
457 Markdown.Append(Account.ApiKey);
458 Markdown.Append("](");
459 Markdown.Append(Gateway.GetUrl("/ApiKey.md?Key=" + Account.ApiKey));
460 Markdown.AppendLine(") |");
461 Markdown.Append("| Owner: | ");
462 Markdown.Append(MarkdownDocument.Encode(ApiKeyObject.Owner));
463 Markdown.AppendLine(" |");
464 Markdown.Append("| e-Mail: | <");
465 Markdown.Append(ApiKeyObject.EMail);
466 Markdown.AppendLine("> |");
467 Markdown.Append("| Accounts created: | ");
468 Markdown.Append(ApiKeyObject.NrCreated.ToString());
469 Markdown.AppendLine(" |");
470 Markdown.Append("| Accounts deleted: | ");
471 Markdown.Append(ApiKeyObject.NrDeleted.ToString());
472 Markdown.AppendLine(" |");
473 Markdown.Append("| Accounts left: | ");
474 Markdown.Append((ApiKeyObject.MaxAccounts - ApiKeyObject.NrCreated + ApiKeyObject.NrDeleted).ToString());
475 Markdown.AppendLine(" |");
476 }
477
478 await Gateway.SendNotification(Markdown.ToString());
479 }
480
481 return true;
482 }
483
490 public async Task<bool> ChangePassword(CaseInsensitiveString UserName, string Password)
491 {
492 Account Account = await this.GetAccountEx(UserName);
493 if (Account is null)
494 return false;
495
496 if (Account.Password != Password)
497 {
498 Account.Password = Password;
499 await Database.Update(Account);
500
501 Log.Informational("Password updated.", Account.UserName, string.Empty, "PasswordUpdated", EventLevel.Medium);
502 }
503
504 return true;
505 }
506
513 public async void AccountLogin(CaseInsensitiveString UserName, string RemoteEndpoint)
514 {
515 try
516 {
517 Account Account = await this.GetAccountEx(UserName);
518 if (!(Account is null))
520 }
521 catch (Exception ex)
522 {
523 Log.Exception(ex);
524 }
525 }
526
527 #endregion
528
529 #region Roster
530
536 public async Task<IEnumerable<IRosterItem>> GetRoster(CaseInsensitiveString UserName)
537 {
538 Dictionary<CaseInsensitiveString, RosterItem> Roster;
539
540 lock (this.rosters)
541 {
542 if (this.rosters.TryGetValue(UserName, out Roster))
543 return this.ToArrayLocked(Roster);
544 }
545
546 if (await this.GetAccountEx(UserName) is null)
547 return null;
548
549 Roster = new Dictionary<CaseInsensitiveString, RosterItem>();
550
551 foreach (RosterItem Item in await Database.Find<RosterItem>(new FilterFieldEqualTo("UserName", UserName)))
552 {
553 if (Roster.ContainsKey(Item.BareJid))
554 await Database.Delete(Item);
555 else
556 Roster[Item.BareJid] = Item;
557 }
558
559 lock (this.rosters)
560 {
561 this.rosters[UserName] = Roster;
562 return this.ToArrayLocked(Roster);
563 }
564 }
565
566 private IRosterItem[] ToArrayLocked(Dictionary<CaseInsensitiveString, RosterItem> Roster)
567 {
568 IRosterItem[] Result;
569
570 int i = 0;
571 int c = Roster.Count;
572
573 Result = new IRosterItem[c];
574 foreach (RosterItem Item in Roster.Values)
575 Result[i++] = Item;
576
577 return Result;
578 }
579
586 public async Task<IRosterItem> GetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
587 {
588 lock (this.rosters)
589 {
590 if (this.rosters.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, RosterItem> Roster) &&
591 Roster.TryGetValue(Jid, out RosterItem Item))
592 {
593 return Item;
594 }
595 }
596
597 if (await this.GetRoster(UserName) is null)
598 return null;
599
600 lock (this.rosters)
601 {
602 if (this.rosters.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, RosterItem> Roster) &&
603 Roster.TryGetValue(Jid, out RosterItem Item))
604 {
605 return Item;
606 }
607 }
608
609 return null;
610 }
611
622 public async Task<IRosterItem> SetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid, string Name, SubscriptionStatus? Subscription, bool? PendingSubscription,
623 string[] Groups)
624 {
625 Dictionary<CaseInsensitiveString, RosterItem> Roster;
626 RosterItem Item;
627 bool Load = false;
628
629 lock (this.rosters)
630 {
631 if (this.rosters.TryGetValue(UserName, out Roster))
632 {
633 if (!Roster.TryGetValue(Jid, out Item))
634 Item = null;
635 }
636 else
637 {
638 Item = null;
639 Load = true;
640 }
641 }
642
643 if (Load)
644 {
645 if (await this.GetRoster(UserName) is null)
646 return null;
647
648 lock (this.rosters)
649 {
650 if (this.rosters.TryGetValue(UserName, out Roster))
651 {
652 if (!Roster.TryGetValue(Jid, out Item))
653 Item = null;
654 }
655 else
656 return null;
657 }
658 }
659
660 if (!(Item is null))
661 {
662 Item.Name = Name;
663 Item.Groups = Groups;
664
665 if (Subscription.HasValue)
666 Item.Subscription = Subscription.Value;
667
668 if (PendingSubscription.HasValue)
669 Item.PendingSubscription = PendingSubscription.Value;
670
671 await Database.Update(Item);
672
673 Log.Informational("Roster item updated.", Item.BareJid, UserName, "RosterItemUpdated", EventLevel.Minor,
674 new KeyValuePair<string, object>("Name", Item.Name),
675 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
676 new KeyValuePair<string, object>("PendingSubscription", Item.PendingSubscription),
677 new KeyValuePair<string, object>("Subscription", Item.Subscription),
678 new KeyValuePair<string, object>("NrGroups", Item.Groups is null ? 0 : Item.Groups.Length));
679
680 return Item;
681 }
682
683 lock (this.rosters)
684 {
685 Item = new RosterItem()
686 {
687 UserName = UserName,
688 BareJid = Jid,
689 Name = Name,
690 Groups = Groups,
693 };
694
695 Roster[Jid] = Item;
696
697 Log.Informational("Roster item created.", Item.BareJid, UserName, "RosterItemCreated", EventLevel.Minor,
698 new KeyValuePair<string, object>("Name", Item.Name),
699 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
700 new KeyValuePair<string, object>("PendingSubscription", Item.PendingSubscription),
701 new KeyValuePair<string, object>("Subscription", Item.Subscription),
702 new KeyValuePair<string, object>("NrGroups", Item.Groups is null ? 0 : Item.Groups.Length));
703 }
704
705 await Database.Insert(Item);
706
707 return Item;
708 }
709
716 public async Task<bool> RemoveRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
717 {
718 Dictionary<CaseInsensitiveString, RosterItem> Roster;
719 RosterItem Item = null;
720
721 lock (this.rosters)
722 {
723 if (this.rosters.TryGetValue(UserName, out Roster))
724 {
725 if (Roster.TryGetValue(Jid, out Item))
726 Roster.Remove(Jid);
727 else
728 return false;
729 }
730 }
731
732 if (Item is null)
733 {
734 if (await this.GetRoster(UserName) is null)
735 return false;
736
737 lock (this.rosters)
738 {
739 if (this.rosters.TryGetValue(UserName, out Roster))
740 {
741 if (Roster.TryGetValue(Jid, out Item))
742 Roster.Remove(Jid);
743 else
744 return false;
745 }
746 }
747 }
748
749 await Database.Delete(Item);
750
751 Log.Informational("Roster item deleted.", Item.BareJid, UserName, "RosterItemDeleted", EventLevel.Minor,
752 new KeyValuePair<string, object>("Name", Item.Name),
753 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
754 new KeyValuePair<string, object>("PendingSubscription", Item.PendingSubscription),
755 new KeyValuePair<string, object>("Subscription", Item.Subscription),
756 new KeyValuePair<string, object>("NrGroups", Item.Groups is null ? 0 : Item.Groups.Length));
757
758 return true;
759 }
760
761 #endregion
762
763 #region Blocks
764
774 public async Task<bool> AddBlock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid, BlockingReason Reason, string Text, string TextLanguage)
775 {
776 Dictionary<CaseInsensitiveString, Block> Blocks;
777 Block Item = null;
778 bool Load = false;
779 bool Found = false;
780
781 lock (this.blocks)
782 {
783 if (this.blocks.TryGetValue(UserName, out Blocks))
784 Found = Blocks.TryGetValue(BareJid, out Item);
785 else
786 Load = true;
787 }
788
789 if (Load)
790 {
791 if (await this.GetBlockList(UserName) is null)
792 return false;
793
794 lock (this.blocks)
795 {
796 if (this.blocks.TryGetValue(UserName, out Blocks))
797 Found = Blocks.TryGetValue(BareJid, out Item);
798 else
799 return false;
800 }
801 }
802
803 if (Found && !(Item is null))
804 {
805 Item.Reason = Reason;
806 Item.Text = Text;
807 Item.Language = TextLanguage;
808
809 await Database.Update(Item);
810
811 Log.Informational("Block updated.", Item.Jid, UserName, "BlockUpdated", EventLevel.Minor,
812 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
813 new KeyValuePair<string, object>("Reason", Item.Reason),
814 new KeyValuePair<string, object>("Text", Item.Text),
815 new KeyValuePair<string, object>("Language", Item.Language));
816
817 return true;
818 }
819
820 lock (this.blocks)
821 {
822 Item = new Block()
823 {
824 UserName = UserName,
825 Jid = BareJid,
826 Reason = Reason,
827 Text = Text,
828 Language = TextLanguage
829 };
830
831 Blocks[BareJid] = Item;
832 }
833
834 await Database.Insert(Item);
835
836 Log.Informational("Block added.", Item.Jid, UserName, "BlockAdded", EventLevel.Minor,
837 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
838 new KeyValuePair<string, object>("Reason", Item.Reason),
839 new KeyValuePair<string, object>("Text", Item.Text),
840 new KeyValuePair<string, object>("Language", Item.Language));
841
842 return true;
843 }
844
851 public async Task<bool> Unblock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid)
852 {
853 Dictionary<CaseInsensitiveString, Block> Blocks;
854 Block Item = null;
855
856 lock (this.blocks)
857 {
858 if (this.blocks.TryGetValue(UserName, out Blocks))
859 {
860 if (Blocks.TryGetValue(BareJid, out Item) && !(Item is null))
861 Blocks[BareJid] = null;
862 else
863 return false;
864 }
865 }
866
867 if (Item is null)
868 {
869 if (await this.GetBlockList(UserName) is null)
870 return false;
871
872 lock (this.blocks)
873 {
874 if (this.blocks.TryGetValue(UserName, out Blocks))
875 {
876 if (Blocks.TryGetValue(BareJid, out Item) && !(Item is null))
877 Blocks[BareJid] = null;
878 else
879 return false;
880 }
881 }
882 }
883
884 await Database.Delete(Item);
885
886 Log.Informational("Block removed.", Item.Jid, UserName, "BlockRemoved", EventLevel.Minor,
887 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
888 new KeyValuePair<string, object>("Reason", Item.Reason),
889 new KeyValuePair<string, object>("Text", Item.Text),
890 new KeyValuePair<string, object>("Language", Item.Language));
891
892 return true;
893 }
894
900 public async Task<bool> ClearBlocks(CaseInsensitiveString UserName)
901 {
902 Dictionary<CaseInsensitiveString, Block> Blocks;
903
904 lock (this.blocks)
905 {
906 if (this.blocks.TryGetValue(UserName, out Blocks))
907 this.blocks.Remove(UserName);
908 }
909
910 if (Blocks is null)
911 {
912 if (await this.GetBlockList(UserName) is null)
913 return false;
914
915 lock (this.blocks)
916 {
917 if (this.blocks.TryGetValue(UserName, out Blocks))
918 {
919 if (this.blocks.TryGetValue(UserName, out Blocks))
920 this.blocks.Remove(UserName);
921 else
922 return false;
923 }
924 }
925 }
926
927 foreach (Block Item in Blocks.Values)
928 {
929 await Database.Delete(Item);
930
931 Log.Informational("Block removed.", Item.Jid, UserName, "BlockRemoved", EventLevel.Minor,
932 new KeyValuePair<string, object>("ObjectId", Item.ObjectId),
933 new KeyValuePair<string, object>("Reason", Item.Reason),
934 new KeyValuePair<string, object>("Text", Item.Text),
935 new KeyValuePair<string, object>("Language", Item.Language));
936 }
937
938 return true;
939 }
940
946 public async Task<IEnumerable<CaseInsensitiveString>> GetBlockList(CaseInsensitiveString UserName)
947 {
948 Dictionary<CaseInsensitiveString, Block> Blocks;
949
950 lock (this.blocks)
951 {
952 if (this.blocks.TryGetValue(UserName, out Blocks))
953 return this.ToArrayLocked(Blocks);
954 }
955
956 if (await this.GetAccountEx(UserName) is null)
957 return null;
958
959 Blocks = new Dictionary<CaseInsensitiveString, Block>();
960
961 foreach (Block Item in await Database.Find<Block>(new FilterFieldEqualTo("UserName", UserName)))
962 Blocks[Item.Jid] = Item;
963
964 lock (this.blocks)
965 {
966 this.blocks[UserName] = Blocks;
967 return this.ToArrayLocked(Blocks);
968 }
969 }
970
971 private CaseInsensitiveString[] ToArrayLocked(Dictionary<CaseInsensitiveString, Block> Blocks)
972 {
973 List<CaseInsensitiveString> Result = new List<CaseInsensitiveString>();
974
975 foreach (Block Item in Blocks.Values)
976 {
977 if (!(Item is null))
978 Result.Add(Item.Jid);
979 }
980
981 return Result.ToArray();
982 }
983
984 private async Task<Block> GetBlock(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
985 {
986 lock (this.blocks)
987 {
988 if (this.blocks.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, Block> Blocks) &&
989 Blocks.TryGetValue(Jid, out Block Block))
990 {
991 return Block;
992 }
993 }
994
995 if (await this.GetBlockList(UserName) is null)
996 return null;
997
998 lock (this.blocks)
999 {
1000 if (this.blocks.TryGetValue(UserName, out Dictionary<CaseInsensitiveString, Block> Blocks))
1001 {
1002 if (Blocks.TryGetValue(Jid, out Block Block))
1003 return Block;
1004 else
1005 Blocks[Jid] = null;
1006 }
1007 }
1008
1009 return null;
1010 }
1011
1018 public async Task<bool> IsBlocked(CaseInsensitiveString FromBareJid, CaseInsensitiveString ToBareJid)
1019 {
1020 int i = ToBareJid.IndexOf('@');
1021 if (i < 0)
1022 return false;
1023
1024 CaseInsensitiveString Domain = ToBareJid.Substring(i + 1);
1025 if (!this.server.IsServerDomain(Domain, true))
1026 return false;
1027
1028 CaseInsensitiveString ToUserName = ToBareJid.Substring(0, i);
1029 Block Block = await this.GetBlock(ToUserName, FromBareJid);
1030
1031 return !(Block is null);
1032 }
1033
1034 #endregion
1035
1036 #region Avatars
1037
1043 public async Task<Tuple<string, byte[]>> GetAvatar(CaseInsensitiveString UserName)
1044 {
1045 foreach (Avatar Avatar in await Database.Find<Avatar>(new FilterFieldEqualTo("UserName", UserName)))
1046 return new Tuple<string, byte[]>(Avatar.ContentType, Avatar.Data);
1047
1048 return null;
1049 }
1050
1058 public async Task<bool> SetAvatar(CaseInsensitiveString UserName, string ContentType, byte[] Data)
1059 {
1060 Account Account = await this.GetAccountEx(UserName);
1061 if (Account is null)
1062 return false;
1063
1064 foreach (Avatar Avatar in await Database.Find<Avatar>(new FilterFieldEqualTo("UserName", UserName)))
1065 {
1066 Avatar.ContentType = ContentType;
1067 Avatar.Data = Data;
1068
1069 await Database.Update(Avatar);
1070
1071 Log.Informational("Avatar updated.", UserName, string.Empty, "AvatarUpdated", EventLevel.Minor,
1072 new KeyValuePair<string, object>("ObjectId", Avatar.ObjectId),
1073 new KeyValuePair<string, object>("ContentType", Avatar.ContentType),
1074 new KeyValuePair<string, object>("Bytes", Avatar.Data.Length));
1075
1076 return true;
1077 }
1078
1079 Avatar Avatar2 = new Avatar()
1080 {
1081 UserName = UserName,
1082 ContentType = ContentType,
1083 Data = Data
1084 };
1085
1086 await Database.Insert(Avatar2);
1087
1088 Log.Informational("Avatar created.", UserName, string.Empty, "AvatarCreated", EventLevel.Minor,
1089 new KeyValuePair<string, object>("ObjectId", Avatar2.ObjectId),
1090 new KeyValuePair<string, object>("ContentType", Avatar2.ContentType),
1091 new KeyValuePair<string, object>("Bytes", Avatar2.Data.Length));
1092
1093 return true;
1094 }
1095
1096 #endregion
1097
1098 #region vCard
1099
1105 public async Task<string> GetVCard(CaseInsensitiveString UserName)
1106 {
1107 foreach (VCard VCard in await Database.Find<VCard>(new FilterFieldEqualTo("UserName", UserName)))
1108 return VCard.VCardXml;
1109
1110 return null;
1111 }
1112
1119 public async Task<bool> SetVCard(CaseInsensitiveString UserName, string VCard)
1120 {
1121 Account Account = await this.GetAccountEx(UserName);
1122 if (Account is null)
1123 return false;
1124
1125 foreach (VCard VCard2 in await Database.Find<VCard>(new FilterFieldEqualTo("UserName", UserName)))
1126 {
1127 VCard2.VCardXml = VCard;
1128 await Database.Update(VCard2);
1129
1130 Log.Informational("vCard updated.", UserName, string.Empty, "vCardUpdated", EventLevel.Minor,
1131 new KeyValuePair<string, object>("ObjectId", VCard2.ObjectId));
1132
1133 return true;
1134 }
1135
1136 VCard VCard3 = new VCard()
1137 {
1138 UserName = UserName,
1139 VCardXml = VCard
1140 };
1141
1142 await Database.Insert(VCard3);
1143
1144 Log.Informational("vCard created.", UserName, string.Empty, "vCardCreated", EventLevel.Minor,
1145 new KeyValuePair<string, object>("ObjectId", VCard3.ObjectId));
1146
1147 return true;
1148 }
1149
1150 #endregion
1151
1152 #region Settings
1153
1160 public Task<bool> IsPermitted(CaseInsensitiveString BareJid, string Setting)
1161 {
1162 return RuntimeSettings.GetAsync(BareJid + " " + Setting, true);
1163 }
1164
1169 public async Task<byte[]> GetDialbackSecret()
1170 {
1171 string s = await RuntimeSettings.GetAsync("Dialback Secret", string.Empty);
1172
1173 if (string.IsNullOrEmpty(s))
1174 {
1175 byte[] Data = Gateway.NextBytes(32);
1176
1177 s = Convert.ToBase64String(Data, Base64FormattingOptions.None);
1178
1179 await RuntimeSettings.SetAsync("Dialback Secret", s);
1180 }
1181
1182 return Convert.FromBase64String(s);
1183 }
1184
1185 #endregion
1186
1187 #region Offline messages
1188
1209 internal static bool RegisterEventsTab(CaseInsensitiveString BareJid,
1210 string MessageType, string LocalName, string Namespace, string Function,
1211 string TabId)
1212 {
1213 string Fqn = EventHandlerKey(MessageType, LocalName, Namespace);
1214 bool Unregistering = string.IsNullOrEmpty(Function);
1215
1216 lock (functionsPerTabIdPerFqnPerBareJid)
1217 {
1218 if (!functionsPerTabIdPerFqnPerBareJid.TryGetValue(BareJid, out Dictionary<string, Dictionary<string, string>> FunctionsPerTabIdPerFqn))
1219 {
1220 if (Unregistering)
1221 return false;
1222
1223 FunctionsPerTabIdPerFqn = new Dictionary<string, Dictionary<string, string>>();
1224 functionsPerTabIdPerFqnPerBareJid[BareJid] = FunctionsPerTabIdPerFqn;
1225 }
1226
1227 if (!FunctionsPerTabIdPerFqn.TryGetValue(Fqn, out Dictionary<string, string> FunctionsPerTabId))
1228 {
1229 if (Unregistering)
1230 return false;
1231
1232 FunctionsPerTabId = new Dictionary<string, string>();
1233 FunctionsPerTabIdPerFqn[Fqn] = FunctionsPerTabId;
1234 }
1235
1236 if (Unregistering)
1237 {
1238 if (!FunctionsPerTabId.Remove(TabId))
1239 return false;
1240
1241 if (FunctionsPerTabId.Count > 0)
1242 return true;
1243
1244 if (!FunctionsPerTabIdPerFqn.Remove(Fqn))
1245 return false;
1246
1247 if (FunctionsPerTabIdPerFqn.Count > 0)
1248 return true;
1249
1250 return functionsPerTabIdPerFqnPerBareJid.Remove(BareJid);
1251 }
1252 else if (FunctionsPerTabId.TryGetValue(TabId, out string s) && s == Function)
1253 return false;
1254 else
1255 {
1256 FunctionsPerTabId[TabId] = Function;
1257 return true;
1258 }
1259 }
1260 }
1261
1262 private static string EventHandlerKey(string MessageType, string LocalName, string Namespace)
1263 {
1264 StringBuilder sb = new StringBuilder();
1265
1266 sb.Append(Namespace);
1267 sb.Append('#');
1268 sb.Append(LocalName);
1269 sb.Append('?');
1270 sb.Append(MessageType);
1271
1272 return sb.ToString();
1273 }
1274
1284 internal static bool UnregisterEventsTab(CaseInsensitiveString BareJid,
1285 string MessageType, string LocalName, string Namespace, string TabId)
1286 {
1287 return RegisterEventsTab(BareJid, MessageType, LocalName, Namespace, string.Empty, TabId);
1288 }
1289
1297 internal static ClientPushRecord[] GetEventTabs(CaseInsensitiveString BareJid,
1298 string Type, string XmlContent, XmlElement StanzaElement)
1299 {
1300 lock (functionsPerTabIdPerFqnPerBareJid)
1301 {
1302 XmlElement FirstContent = null;
1303
1304 if (!functionsPerTabIdPerFqnPerBareJid.TryGetValue(BareJid, out Dictionary<string, Dictionary<string, string>> FunctionsPerTabIdPerFqn))
1305 return null;
1306
1307 if (StanzaElement is null)
1308 {
1309 try
1310 {
1311 StringBuilder Xml = new StringBuilder();
1312
1313 Xml.Append("<message xmlns=\"");
1314 Xml.Append(ClientConnection.C2SNamespace);
1315 Xml.Append('>');
1316 Xml.Append(XmlContent);
1317 Xml.Append("</message>");
1318
1319 XmlDocument Doc = new XmlDocument();
1320 Doc.LoadXml(XmlContent);
1321 StanzaElement = Doc.DocumentElement;
1322 }
1323 catch (Exception)
1324 {
1325 return null;
1326 }
1327 }
1328
1329 Dictionary<string, string> FunctionsPerTabId;
1330 List<ClientPushRecord> Result = null;
1331 string Key;
1332
1333 foreach (XmlNode N in StanzaElement.ChildNodes)
1334 {
1335 if (N is XmlElement E)
1336 {
1337 if (FirstContent is null)
1338 FirstContent = E;
1339
1340 Key = EventHandlerKey(Type, E.LocalName, E.NamespaceURI);
1341 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1342 {
1343 if (Result is null)
1344 Result = new List<ClientPushRecord>();
1345
1346 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1347 {
1348 Result.Add(new ClientPushRecord()
1349 {
1350 Type = Type,
1351 LocalName = E.LocalName,
1352 Namespace = E.NamespaceURI,
1353 Function = P.Value,
1354 TabId = P.Key,
1355 Data = E
1356 });
1357 }
1358 }
1359
1360 Key = EventHandlerKey(Type, string.Empty, E.NamespaceURI);
1361 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1362 {
1363 if (Result is null)
1364 Result = new List<ClientPushRecord>();
1365
1366 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1367 {
1368 Result.Add(new ClientPushRecord()
1369 {
1370 Type = Type,
1371 LocalName = string.Empty,
1372 Namespace = E.NamespaceURI,
1373 Function = P.Key,
1374 TabId = P.Value,
1375 Data = E
1376 });
1377 }
1378 }
1379
1380 Key = EventHandlerKey(string.Empty, string.Empty, E.NamespaceURI);
1381 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1382 {
1383 if (Result is null)
1384 Result = new List<ClientPushRecord>();
1385
1386 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1387 {
1388 Result.Add(new ClientPushRecord()
1389 {
1390 Type = string.Empty,
1391 LocalName = string.Empty,
1392 Namespace = E.NamespaceURI,
1393 Function = P.Key,
1394 TabId = P.Value,
1395 Data = E
1396 });
1397 }
1398 }
1399 }
1400 }
1401
1402 if (!(FirstContent is null))
1403 {
1404 Key = EventHandlerKey(string.Empty, string.Empty, string.Empty);
1405 if (FunctionsPerTabIdPerFqn.TryGetValue(Key, out FunctionsPerTabId))
1406 {
1407 if (Result is null)
1408 Result = new List<ClientPushRecord>();
1409
1410 foreach (KeyValuePair<string, string> P in FunctionsPerTabId)
1411 {
1412 Result.Add(new ClientPushRecord()
1413 {
1414 Type = string.Empty,
1415 LocalName = string.Empty,
1416 Namespace = string.Empty,
1417 Function = P.Key,
1418 TabId = P.Value,
1419 Data = FirstContent
1420 });
1421 }
1422 }
1423 }
1424
1425 return Result?.ToArray();
1426 }
1427 }
1428
1429 internal class ClientPushRecord
1430 {
1431 public string Type;
1432 public string LocalName;
1433 public string Namespace;
1434 public string Function;
1435 public string TabId;
1436 public XmlElement Data;
1437 }
1438
1445 public async Task<IEnumerable<IOfflineMessage>> GetOfflineMessages(CaseInsensitiveString ToUserName, int Max)
1446 {
1447 return await Database.Find<OfflineMessage>(0, Max, new FilterFieldEqualTo("ToUserName", ToUserName), "Timestamp");
1448 }
1449
1454 public async Task DeleteOfflineMessages(IEnumerable<IOfflineMessage> Messages)
1455 {
1456 await Database.Delete(Messages);
1457 }
1458
1464 public async Task<int> DeleteOfflineMessages(CaseInsensitiveString ToUserName)
1465 {
1466 int Count = 0;
1467
1468 foreach (OfflineMessage Message in await Database.FindDelete<OfflineMessage>(new FilterFieldEqualTo("ToUserName", ToUserName)))
1469 Count++;
1470
1471 return Count;
1472 }
1473
1484 public async Task<bool> StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
1485 {
1486 CaseInsensitiveString BareJid = To.BareJid;
1487 ClientPushRecord[] ClientPushRecords = GetEventTabs(BareJid, Type, ContentXml, null);
1488
1489 if (!(ClientPushRecords is null) && await PushMessageToClients(BareJid, ClientPushRecords))
1490 return true;
1491
1492 OfflineMessage Msg = new OfflineMessage()
1493 {
1494 ToUserName = To.Account,
1495 Type = Type,
1496 Id = Id,
1497 To = BareJid,
1498 From = From.Address,
1499 Language = Language,
1500 ContentXml = ContentXml,
1501 Timestamp = DateTime.UtcNow
1502 };
1503
1504 await Database.Insert(Msg);
1505
1507 if (!(Token is null))
1508 {
1509 PushNotificationRule Rule = await XmppServer.TryGetPushNotificationRule(BareJid, Type, string.Empty, string.Empty);
1510 if (!(Rule is null))
1511 {
1512 XmlElement StanzaElement = this.RecreateStanza(ContentXml);
1513 if (!(StanzaElement is null))
1514 await this.ProcessOfflineRule(Rule, StanzaElement, Token, Type, Id, To.Address.Value, From.Address.Value);
1515 }
1516 else if (!string.IsNullOrEmpty(ContentXml))
1517 {
1518 XmlElement StanzaElement = this.RecreateStanza(ContentXml);
1519 if (!(StanzaElement is null))
1520 await this.ProcessOfflineStanza(BareJid, Type, StanzaElement, Token, Id, To.Address.Value, From.Address.Value);
1521 }
1522 }
1523
1524 return true;
1525 }
1526
1527 private static async Task<bool> PushMessageToClients(CaseInsensitiveString BareJid,
1528 ClientPushRecord[] ClientPushRecords)
1529 {
1530 if (ClientPushRecords is null)
1531 return false;
1532
1533 bool Sent = false;
1534
1535 foreach (ClientPushRecord Rec in ClientPushRecords)
1536 {
1537 if (await ClientEvents.PushEvent(new string[] { Rec.TabId }, Rec.Function, JSON.Encode(Rec.Data, false), true) > 0)
1538 Sent = true;
1539 else
1540 UnregisterEventsTab(BareJid, Rec.Type, Rec.LocalName, Rec.Namespace, Rec.TabId);
1541 }
1542
1543 return Sent;
1544 }
1545
1546 private XmlElement RecreateStanza(string ContentXml)
1547 {
1548 if (string.IsNullOrEmpty(ContentXml))
1549 return null;
1550
1551 try
1552 {
1553 XmlDocument Doc = new XmlDocument();
1554 Doc.LoadXml("<message xmlns='jabber:client'>" + ContentXml + "</message>");
1555 return Doc.DocumentElement;
1556 }
1557 catch (Exception ex)
1558 {
1559 Log.Exception(ex);
1560 return null;
1561 }
1562 }
1563
1564 private async Task ProcessOfflineStanza(CaseInsensitiveString BareJid, string Type, XmlElement StanzaElement,
1565 PushNotificationToken Token, string Id, string To, string From)
1566 {
1567 foreach (XmlNode N in StanzaElement.ChildNodes)
1568 {
1569 if (N is XmlElement E)
1570 {
1571 PushNotificationRule Rule = await XmppServer.TryGetPushNotificationRule(BareJid, Type, E.LocalName, E.NamespaceURI);
1572 if (!(Rule is null))
1573 {
1574 await this.ProcessOfflineRule(Rule, StanzaElement, Token, Type, Id, To, From);
1575 return;
1576 }
1577 }
1578 }
1579 }
1580
1581 private bool NoXPath(ScriptNode Node, out ScriptNode NewNode, object State)
1582 {
1583 NewNode = null;
1584 return !(Node is Script.Persistence.Functions.XPath);
1585 }
1586
1587 private async Task ProcessOfflineRule(PushNotificationRule Rule, XmlElement StanzaElement, PushNotificationToken Token,
1588 string Type, string Id, string To, string From)
1589 {
1590 try
1591 {
1592 if (!string.IsNullOrEmpty(Type) && !StanzaElement.HasAttribute("type"))
1593 StanzaElement.SetAttribute("type", Type);
1594
1595 if (!string.IsNullOrEmpty(Id) && !StanzaElement.HasAttribute("id"))
1596 StanzaElement.SetAttribute("id", Id);
1597
1598 if (!string.IsNullOrEmpty(To) && !StanzaElement.HasAttribute("to"))
1599 StanzaElement.SetAttribute("to", To);
1600
1601 if (!string.IsNullOrEmpty(From) && !StanzaElement.HasAttribute("from"))
1602 StanzaElement.SetAttribute("from", From);
1603
1604 Variables v = new Variables();
1605 Expression ContentExpression = Rule.ContentExpression;
1606 Expression PatternMatchingExpression = Rule.PatternMatchingExpression;
1607 bool HasXPath = !((ContentExpression?.ForAll(this.NoXPath, null, SearchMethod.TreeOrder) ?? true) &&
1608 (PatternMatchingExpression?.ForAll(this.NoXPath, null, SearchMethod.TreeOrder) ?? true));
1609
1610 if (!string.IsNullOrEmpty(Rule.MessageVariable))
1611 v[Rule.MessageVariable] = StanzaElement;
1612
1613 if (!(PatternMatchingExpression is null))
1614 {
1615 Dictionary<string, IElement> AlreadyFound = new Dictionary<string, IElement>();
1616
1617 if (PatternMatchingExpression.Root.PatternMatch(new ObjectValue(StanzaElement), AlreadyFound) != PatternMatchResult.Match)
1618 return;
1619
1620 foreach (KeyValuePair<string, IElement> P in AlreadyFound)
1621 v[P.Key] = P.Value;
1622 }
1623
1624 object Content;
1625
1626 if (ContentExpression is null)
1627 Content = StanzaElement;
1628 else
1629 Content = await ContentExpression.EvaluateAsync(v);
1630
1631 PushNotificationEventHandler h = this.OnPushNotification;
1632 if (!(h is null))
1633 await h(Token, Content);
1634
1635 PushNotificationConfiguration.PushNotification(Token, Content);
1636 }
1637 catch (Exception ex)
1638 {
1639 Log.Exception(ex);
1640 }
1641 }
1642
1646 public event PushNotificationEventHandler OnPushNotification;
1647
1658 public async Task<bool> StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza)
1659 {
1660 CaseInsensitiveString BareJid = To.BareJid;
1661 ClientPushRecord[] ClientPushRecords = GetEventTabs(BareJid, Type, Stanza.Content, Stanza.StanzaElement);
1662
1663 if (!(ClientPushRecords is null) && await PushMessageToClients(BareJid, ClientPushRecords))
1664 return true;
1665
1666 OfflineMessage Msg = new OfflineMessage()
1667 {
1668 ToUserName = To.Account,
1669 Type = Type,
1670 Id = Id,
1671 To = BareJid,
1672 From = From.Address,
1673 Language = Language,
1674 ContentXml = Stanza.Content,
1675 Timestamp = DateTime.UtcNow
1676 };
1677
1678 await Database.Insert(Msg);
1679
1681 if (!(Token is null))
1682 {
1683 PushNotificationRule Rule = await XmppServer.TryGetPushNotificationRule(BareJid, Type, string.Empty, string.Empty);
1684 if (!(Rule is null))
1685 await this.ProcessOfflineRule(Rule, Stanza.StanzaElement, Token, Type, Id, To.Address.Value, From.Address.Value);
1686 else if (Stanza.HasContent)
1687 await this.ProcessOfflineStanza(BareJid, Type, Stanza.StanzaElement, Token, Id, To.Address.Value, From.Address.Value);
1688 }
1689
1690 return true;
1691 }
1692
1698 public async Task<int> DeleteOfflineMessages(DateTime OlderThan)
1699 {
1700 int Nr = 0;
1701
1702 foreach (OfflineMessage Msg in await Database.FindDelete<OfflineMessage>(new FilterFieldLesserOrEqualTo("Timestamp", OlderThan)))
1703 Nr++;
1704
1705 return Nr;
1706 }
1707
1713 async Task<Networking.SASL.IAccount> ISaslPersistenceLayer.GetAccount(CaseInsensitiveString UserName)
1714 {
1715 return await this.GetAccountEx(UserName);
1716 }
1717
1723 async Task<Networking.XMPP.Server.IAccount> IXmppServerPersistenceLayer.GetAccount(CaseInsensitiveString UserName)
1724 {
1725 return await this.GetAccountEx(UserName);
1726 }
1727
1733 {
1734 lock (this.accounts)
1735 {
1736 this.accounts.Remove(UserName);
1737 }
1738
1739 return Task.CompletedTask;
1740 }
1741
1742 public byte[] GetRandomNumbers(int NrBytes)
1743 {
1744 return XmppServer.GetRandomNumbers(NrBytes);
1745 }
1746
1747 #endregion
1748
1749 }
1750}
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
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 ...
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
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
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
Definition: ClientEvents.cs:51
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static LoginAuditor LoginAuditor
Current Login Auditor. Should be used by modules accepting user logins, to protect the system from un...
Definition: Gateway.cs:3033
static byte[] NextBytes(int NrBytes)
Generates an array of random bytes.
Definition: Gateway.cs:3534
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:3826
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:4167
static int NextInteger(int Max)
Returns a non-negative random integer that is less than the specified maximum.
Definition: Gateway.cs:3510
static string RootFolder
Web root folder.
Definition: Gateway.cs:2379
Authentication done by the LOGIN authentication mechanism. https://tools.ietf.org/html/draft-murchiso...
Definition: Login.cs:15
Abstract base class for XMPP client connections
Expression PatternMatchingExpression
Parsed pattern-matching expression
string MessageVariable
Variable to put the Message XML in, before patternmatching or content script is executed.
Contains information about a stanza.
Definition: Stanza.cs:10
string Content
Literal XML content.
Definition: Stanza.cs:54
XmlElement StanzaElement
Stanza element.
Definition: Stanza.cs:114
bool HasContent
If the stanza has content.
Definition: Stanza.cs:81
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
static readonly XmppAddress Empty
Empty address.
Definition: XmppAddress.cs:31
CaseInsensitiveString Account
Account
Definition: XmppAddress.cs:124
static byte[] GetRandomNumbers(int NrBytes)
Generates a set of random numbers.
Definition: XmppServer.cs:672
static async Task< PushNotificationToken > TryGetPushNotificationToken(CaseInsensitiveString BareJid)
Tries to get a push notification token for a client, if one exists.
Definition: XmppServer.cs:6507
static async Task< PushNotificationRule > TryGetPushNotificationRule(CaseInsensitiveString BareJid, string MessageType, string LocalName, string Namespace)
Tries to get a push notification token for a client, if one exists.
Definition: XmppServer.cs:6803
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:879
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:717
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field lesser or equal to a given value.
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Class managing a script expression.
Definition: Expression.cs:39
ScriptNode Root
Root script node.
Definition: Expression.cs:4299
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4275
bool ForAll(ScriptNodeEventHandler Callback, object State, bool DepthFirst)
Calls the callback method for all script nodes defined for the expression.
Definition: Expression.cs:5196
Base class for all funcions.
Definition: Function.cs:9
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
virtual PatternMatchResult PatternMatch(IElement CheckAgainst, Dictionary< string, IElement > AlreadyFound)
Performs a pattern match operation.
Definition: ScriptNode.cs:169
Collection of variables.
Definition: Variables.cs:25
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async Task< string > AppendWhoIsInfo(StringBuilder Markdown, string RemoteEndpoint)
Appends WHOIS information to a Markdown document.
static async Task< KeyValuePair< string, object >[]> Annotate(string RemoteEndpoint, params KeyValuePair< string, object >[] Tags)
Annotates a remote endpoint.
Login state information relating to a remote endpoint
Contains information about a broker account.
Definition: Account.cs:28
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:129
CaseInsensitiveString UserName
User Name of account
Definition: Account.cs:99
bool Enabled
If account is enabled
Definition: Account.cs:287
async Task< AccountLogin > GetAccountLogin()
Gets the object with the associated account login status information.
Definition: Account.cs:370
string Password
Password of account
Definition: Account.cs:109
CaseInsensitiveString PhoneNr
Phone number associated with account.
Definition: Account.cs:138
async Task LoggedIn(string RemoteEndpoint)
Registers a log-in event on the account.
Definition: Account.cs:356
DateTime Created
When account was created associated with account holder.
Definition: Account.cs:259
string ApiKey
Reference to API Key used to create object.
Definition: Account.cs:90
async Task< bool > ClearBlocks(CaseInsensitiveString UserName)
Remove all blocks for an account.
Task AccountUpdated(CaseInsensitiveString UserName)
Called when account has been updated.
async Task< bool > ChangePassword(CaseInsensitiveString UserName, string Password)
Changes the password of an account.
async Task< bool > SetAvatar(CaseInsensitiveString UserName, string ContentType, byte[] Data)
Sets the Avatar of an account.
async Task< byte[]> GetDialbackSecret()
Gets the Dialback secret, as defined in XEP-0185.
Task< bool > IsPermitted(CaseInsensitiveString BareJid, string Setting)
Checks if a feature is permitted for a Bare JID.
async Task< int > DeleteOfflineMessages(DateTime OlderThan)
Deletes offline messages that are older than a specific timestamp.
async Task< IRosterItem > GetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
Gets a roster item for an account.
async Task< bool > AddBlock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid, BlockingReason Reason, string Text, string TextLanguage)
Blocks an account.
async Task< int > DeleteOfflineMessages(CaseInsensitiveString ToUserName)
Deletes offline messages for a given account.
async void AccountLogin(CaseInsensitiveString UserName, string RemoteEndpoint)
Successful login to account registered.
async Task< IEnumerable< CaseInsensitiveString > > GetBlockList(CaseInsensitiveString UserName)
Gets the entire block list for an account.
async Task< string > GetVCard(CaseInsensitiveString UserName)
Gets the vCard of an account.
async Task< IRosterItem > SetRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid, string Name, SubscriptionStatus? Subscription, bool? PendingSubscription, string[] Groups)
Sets a roster item in a users roster.
PushNotificationEventHandler OnPushNotification
Event raised when a push notification is performed.
async Task< bool > StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza)
Tries to save an offline message.
async Task DeleteOfflineMessages(IEnumerable< IOfflineMessage > Messages)
Deletes offline messages.
async Task< KeyValuePair< Networking.XMPP.Server.IAccount, string[]> > CreateAccount(string ApiKey, CaseInsensitiveString UserName, string Password, CaseInsensitiveString EMail, CaseInsensitiveString PhoneNr, string RemoteEndpoint)
Creates an account.
async Task< bool > Unblock(CaseInsensitiveString UserName, CaseInsensitiveString BareJid)
Unblocks an account.
async Task< bool > StoreOfflineMessage(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Tries to save an offline message.
async Task< IEnumerable< IRosterItem > > GetRoster(CaseInsensitiveString UserName)
Gets the roster of an account.
async Task< bool > DeleteAccount(CaseInsensitiveString UserName, string RemoteEndpoint)
Deletes an account.
async Task< IEnumerable< IOfflineMessage > > GetOfflineMessages(CaseInsensitiveString ToUserName, int Max)
Gets the oldest offline messages stored for a given bare JID, up to a maximum count.
async Task< Tuple< string, byte[]> > GetAvatar(CaseInsensitiveString UserName)
Gets the Avatar of an account.
async Task< bool > SetVCard(CaseInsensitiveString UserName, string VCard)
Sets the vCard of an account.
async Task< bool > IsBlocked(CaseInsensitiveString FromBareJid, CaseInsensitiveString ToBareJid)
Checks if a sender is blocked by a receiver.
byte[] GetRandomNumbers(int NrBytes)
Generates a set of random numbers.
async Task< string > GetApiKeySecret(string ApiKey)
Gets the secret for a given API key.
async Task< bool > RemoveRosterItem(CaseInsensitiveString UserName, CaseInsensitiveString Jid)
Removes a roster item.
SubscriptionStatus Subscription
Subscription status.
Definition: RosterItem.cs:60
CaseInsensitiveString BareJid
Bare JID.
Definition: RosterItem.cs:39
bool PendingSubscription
If a presence subscription awaits.
Definition: RosterItem.cs:67
string[] Groups
Groups assigned to roster item.
Definition: RosterItem.cs:53
async Task< PubSubNode > GetNodeAsync(CaseInsensitiveString Service, CaseInsensitiveString NodeName, NodeAccessModel? AutoCreateAccess, XmppAddress From, CaseInsensitiveString Domain)
Gets a pubsub node.
Defines a node on which items can be published.
Definition: PubSubNode.cs:19
Provides the user configuration options regarding use of Push Notification to reach offline clients.
Service Module hosting the XMPP broker and its components.
static async Task AppendRemoteEndpointToTable(StringBuilder Markdown, string RemoteEndpoint)
Appends annotated information about a remote endpoint to a Markdown table.
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
Task< IAccount > GetAccount(CaseInsensitiveString UserName)
Method to call to fetch account information.
Interface for roster items.
Definition: IRosterItem.cs:43
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
new Task< IAccount > GetAccount(CaseInsensitiveString UserName)
Method to call to fetch account information.
EventLevel
Event level.
Definition: EventLevel.cs:7
SubscriptionStatus
Roster item subscription status enumeration.
Definition: IRosterItem.cs:10
BlockingReason
Reason for blocking an account.
MessageType
Type of message received.
Definition: MessageType.cs:7
PendingSubscription
Pending subscription states.
Definition: RosterItem.cs:54
PatternMatchResult
Status result of a pattern matching operation.
Definition: ScriptNode.cs:17
SearchMethod
Method to traverse the expression structure
Definition: ScriptNode.cs:38