protected override Yield FilterRecipients(DispatcherEvent ev, PubSubSubscription subscription, Result <DispatcherEvent> result) { var recipients2 = new List <DispatcherRecipient>(); uint? pageid = null; string wikiId = null; if (ev.HasDocument) { var changeDoc = ev.AsDocument(); pageid = changeDoc["pageid"].AsUInt; wikiId = changeDoc["@wikiid"].AsText; } var userIds = new Dictionary <int, DispatcherRecipient>(); foreach (var recipient in subscription.Recipients) { var authtoken = recipient.Doc["@authtoken"].AsText; if (string.IsNullOrEmpty(authtoken)) { // if the recipient has no authtoken, but has a userid, collect the Id so we can authorize it against the page int?userId = recipient.Doc["@userid"].AsInt; if (userId.HasValue) { userIds.Add(userId.Value, recipient); } } else if (authtoken == _authtoken) { // master authtoken means the recipient doesn't need page level authorization (such as lucene) recipients2.Add(recipient); } else if (!string.IsNullOrEmpty(wikiId)) { var key = authtoken + ":" + wikiId; if (!_validatedKeys.Contains(key)) { // no valid key found, need to check with API to validate XDoc settings = null; yield return(_deki.At("site", "settings") .With("apikey", _authtoken) .WithHeader("X-Deki-Site", "id=" + wikiId) .Get(new Result <DreamMessage>()) .Set(x => settings = x.IsSuccessful ? x.ToDocument() : null)); if (settings == null || !authtoken.EqualsInvariant(settings["security/api-key"].AsText)) { continue; } _validatedKeys.Add(key); } // instance authtoken means the recipient doesn't need page level authorization (such as lucene) recipients2.Add(recipient); } } if (userIds.Count > 0 && (ev.Channel.Segments.Length <= 2 || !ev.Channel.Segments[2].EqualsInvariantIgnoreCase("delete"))) { // check all userId's against the page to prune set to authorized users var users = new XDoc("users"); foreach (int userid in userIds.Keys) { users.Start("user").Attr("id", userid).End(); } if (pageid.HasValue) { Result <DreamMessage> userAuthResult; yield return(userAuthResult = _deki.At("pages", pageid.Value.ToString(), "allowed") .With("permissions", "read,subscribe") .With("filterdisabled", true) .WithHeader("X-Deki-Site", "id=" + wikiId) .PostAsync(users)); DreamMessage userAuth = userAuthResult.Value; if (userAuth.IsSuccessful) { int authorized = 0; foreach (XDoc userid in userAuth.ToDocument()["user/@id"]) { DispatcherRecipient recipient; if (!userIds.TryGetValue(userid.AsInt.GetValueOrDefault(), out recipient)) { continue; } authorized++; recipients2.Add(recipient); } if (authorized != userIds.Count) { _log.DebugFormat("requested auth on {0} users, received auth on {1} for page {2}", userIds.Count, authorized, pageid.Value); } } else { _log.WarnFormat("unable to retrieve user auth for page '{0}': {1}", pageid, userAuth.Status); } } } result.Return(recipients2.Count == 0 ? null : ev.WithRecipient(true, recipients2.ToArray())); yield break; }
protected override Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result <Dictionary <XUri, List <PubSubSubscription> > > result) { if (!ev.Channel.Segments[1].EqualsInvariantIgnoreCase("pages")) { // not a page DispatcherEvent or a page delete DispatcherEvent, use default matcher Result <Dictionary <XUri, List <PubSubSubscription> > > baseResult; yield return(baseResult = Coroutine.Invoke(base.GetListenersByChannelResourceMatch, ev, new Result <Dictionary <XUri, List <PubSubSubscription> > >())); result.Return(baseResult); yield break; } var matches = new List <PubSubSubscription>(); if (ev.Channel.Segments.Length <= 2 || !ev.Channel.Segments[2].EqualsInvariantIgnoreCase("delete")) { // dispatch to all PubSubSubscriptions that listen for this DispatcherEvent and its contents XDoc evDoc = ev.AsDocument(); uint? pageid = evDoc["pageid"].AsUInt; string wikiId = evDoc["@wikiid"].AsText; bool first = true; _log.DebugFormat("trying dispatch based on channel & page PubSubSubscriptions for page '{0}' from wiki '{1}'", pageid, wikiId); // fetch parent page id's for this page so that we can resolve infinite depth PubSubSubscriptions Result <DreamMessage> pageHierarchyResult; yield return(pageHierarchyResult = _deki.At("pages", pageid.ToString()).WithHeader("X-Deki-Site", "id=" + wikiId).GetAsync()); DreamMessage pageHierarchy = pageHierarchyResult.Value; if (pageHierarchy.IsSuccessful) { XDoc pageDoc = pageHierarchy.ToDocument(); while (pageid.HasValue) { List <Tuplet <PubSubSubscription, bool> > subs; _subscriptionsByPage.TryGetValue(pageid.Value, out subs); if (subs != null) { // only the first pageId (the one from the event) triggers on non-infinite depth subs foreach (var sub in subs) { if ((sub.Item2 || first) && !matches.Contains(sub.Item1)) { matches.Add(sub.Item1); } } } // get parent id and then set pageDoc to the parent's subdoc, so we can descend the ancesstor tree further pageid = pageDoc["page.parent/@id"].AsUInt; pageDoc = pageDoc["page.parent"]; first = false; } } else { _log.WarnFormat("unable to retrieve page doc for page '{0}': {1}", pageid, pageHierarchy.Status); } } ICollection <PubSubSubscription> listeningSubs; lock (_channelMap) { // get all the PubSubSubscriptions that are wild card matches (which is basically those that didn't // have any resources in their PubSubSubscription) and add them to the above matches foreach (var sub in _resourceMap.GetMatches(new XUri("http://dummy/dummy"))) { if (!matches.Contains(sub)) { matches.Add(sub); } } listeningSubs = _channelMap.GetMatches(ev.Channel, matches); } var listeners = new Dictionary <XUri, List <PubSubSubscription> >(); foreach (var sub in listeningSubs) { List <PubSubSubscription> subs; if (!listeners.TryGetValue(sub.Destination, out subs)) { subs = new List <PubSubSubscription>(); listeners.Add(sub.Destination, subs); subs.Add(sub); } else if (!subs.Contains(sub)) { subs.Add(sub); } } result.Return(listeners); yield break; }