//--- Constructors --- /// <summary> /// Create a new subscription set. /// </summary> /// <param name="owner">Owner uri.</param> /// <param name="version">Version serial number.</param> /// <param name="cookie">Pub sub location access cookie.</param> /// <param name="childSubscriptions">Subscriptions.</param> public PubSubSubscriptionSet(XUri owner, long version, DreamCookie cookie, params PubSubSubscription[] childSubscriptions) { Owner = owner; Version = version; Dictionary <string, PubSubSubscription> subs = new Dictionary <string, PubSubSubscription>(); foreach (PubSubSubscription sub in childSubscriptions) { foreach (XUri channel in sub.Channels) { if (channel.Scheme == "pubsub") { // pubsub scheme is for PubSubService internal use only, so it should never be aggregated continue; } XUri[] resources = (sub.Resources == null || sub.Resources.Length == 0) ? new XUri[] { null } : sub.Resources; foreach (XUri resource in resources) { PubSubSubscription combo; string key = channel + ":" + resource; subs.TryGetValue(key, out combo); subs[key] = PubSubSubscription.MergeForChannelAndResource(channel, resource, this, cookie, sub, combo); } } } Subscriptions = new PubSubSubscription[subs.Count]; subs.Values.CopyTo(Subscriptions, 0); MaxFailures = MAX_FAILURES; }
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; }
//--- Constructors --- private PubSubSubscription(XUri channel, XUri resource, PubSubSubscriptionSet owner, DreamCookie cookie, PubSubSubscription first, PubSubSubscription second) { if(channel == null) { throw new ArgumentNullException("channel"); } Channels = new[] { channel }; Resources = resource == null ? new XUri[0] : new[] { resource }; Id = Guid.NewGuid().ToString(); Owner = owner; Destination = Owner.Owner.At("publish"); Cookie = cookie; Recipients = ArrayUtil.Union(first.Recipients, (second == null) ? new DispatcherRecipient[0] : second.Recipients); _isProxy = true; }
//--- Class Methods --- /// <summary> /// Merge two subscriptions on matchine channel and resource. /// </summary> /// <param name="channel">Common channel.</param> /// <param name="resource">Common resource.</param> /// <param name="owner">Common owner.</param> /// <param name="cookie">Subscription set cookie.</param> /// <param name="first">First subscription to merge.</param> /// <param name="second">Second subscription to merge.</param> /// <returns></returns> public static PubSubSubscription MergeForChannelAndResource(XUri channel, XUri resource, PubSubSubscriptionSet owner, DreamCookie cookie, PubSubSubscription first, PubSubSubscription second) { return new PubSubSubscription(channel, resource, owner, cookie, first, second); }
public void SubscriptionSet_combined_set_should_not_include_pubsub_channel_subscriptions() { DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); PubSubSubscription sub1 = new PubSubSubscription( new XDoc("subscription") .Elem("channel", "pubsub:///foo1") .Add(cookie.AsSetCookieDocument) .Start("recipient").Elem("uri", "http:///recipient1").End(), null); PubSubSubscriptionSet pubsubset = new PubSubSubscriptionSet(new XUri("http:///owner"), 0, cookie, sub1); Assert.AreEqual(0, pubsubset.Subscriptions.Length); PubSubSubscription sub2 = new PubSubSubscription( new XDoc("subscription") .Elem("channel", "channel:///foo1") .Add(cookie.AsSetCookieDocument) .Start("recipient").Elem("uri", "http:///recipient1").End(), null); pubsubset = new PubSubSubscriptionSet(new XUri("http:///owner"), 0, cookie, sub1, sub2); Assert.AreEqual(1, pubsubset.Subscriptions.Length); Assert.AreEqual(1, pubsubset.Subscriptions[0].Channels.Length); Assert.AreEqual("channel:///foo1", pubsubset.Subscriptions[0].Channels[0].ToString()); }
public void Subscription_auto_attaches_id() { XDoc subDoc = new XDoc("subscription") .Elem("channel", "channel:///foo/bar/*") .Start("recipient").Elem("uri", "http:///foo/bar").End(); PubSubSubscription sub = new PubSubSubscription(subDoc, null); Assert.IsFalse(string.IsNullOrEmpty(sub.Id)); }
public void SubscriptionSet_combination_splits_multichannel_subs() { XUri owner = new XUri("http:///owner"); XUri c1 = new XUri("channel:///c1"); XUri c2 = new XUri("channel:///c2"); XUri c3 = new XUri("channel:///c3"); XUri r1 = new XUri("http:///r1"); PubSubSubscription sub = new PubSubSubscription( new XDoc("subscription") .Attr("id", "123") .Elem("channel", c1) .Elem("channel", c2) .Elem("channel", c3) .Elem("uri.proxy", "http:///proxy") .Start("recipient").Attr("auth-token", "abc").Elem("uri", r1).End() .Start("recipient").Attr("auth-token", "def").Elem("uri", "http:///r2").End() , null ); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); PubSubSubscriptionSet combinedSet = new PubSubSubscriptionSet(owner, 0, cookie, sub); Assert.AreEqual(3, combinedSet.Subscriptions.Length); PubSubSubscription subx = combinedSet.Subscriptions[0]; Assert.AreEqual(owner.At("publish"), subx.Destination); Assert.AreEqual(1, subx.Channels.Length); Assert.AreEqual(c1, subx.Channels[0]); Assert.AreEqual(2, subx.Recipients.Length); Assert.AreEqual(r1, subx.Recipients[0].Uri); subx = combinedSet.Subscriptions[1]; Assert.AreEqual(owner.At("publish"), subx.Destination); Assert.AreEqual(1, subx.Channels.Length); Assert.AreEqual(c2, subx.Channels[0]); Assert.AreEqual(2, subx.Recipients.Length); Assert.AreEqual(r1, subx.Recipients[0].Uri); }
public void SubscriptionSet_combination_merges_subs_for_same_channel() { XUri owner = new XUri("http:///owner"); XUri c1 = new XUri("channel:///c1"); XUri c2 = new XUri("channel:///c2"); XUri c3 = new XUri("channel:///c3"); XDoc x1 = new XDoc("rule").Value("v1"); XDoc x2 = new XDoc("rule").Value("v2"); XDoc x3 = new XDoc("super-custom-filter").Elem("foo", "bar"); XUri r1 = new XUri("http:///r1"); XUri r2 = new XUri("http:///r2"); PubSubSubscription sub1 = new PubSubSubscription( new XDoc("subscription") .Attr("id", "123") .Elem("channel", c1) .Elem("channel", c2) .Elem("uri.proxy", "http:///proxy") .Start("recipient").Attr("auth-token", "abc").Elem("uri", r1).End() , null ); PubSubSubscription sub2 = new PubSubSubscription( new XDoc("subscription") .Attr("id", "123") .Elem("channel", c1) .Elem("channel", c3) .Elem("uri.proxy", "http:///proxy") .Start("recipient").Attr("auth-token", "abc").Elem("uri", r2).End() , null ); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); PubSubSubscriptionSet combinedSet = new PubSubSubscriptionSet(owner, 0, cookie, sub1, sub2); Assert.AreEqual(3, combinedSet.Subscriptions.Length); foreach(PubSubSubscription subx in combinedSet.Subscriptions) { switch(subx.Channels[0].ToString()) { case "channel:///c1": Assert.AreEqual(owner.At("publish"), subx.Destination); Assert.AreEqual(1, subx.Channels.Length); Assert.AreEqual(c1, subx.Channels[0]); Assert.AreEqual(2, subx.Recipients.Length); bool foundR1 = false; bool foundR2 = false; foreach(DispatcherRecipient r in subx.Recipients) { if(r.Uri == r1) { foundR1 = true; } else if(r.Uri == r2) { foundR2 = true; } } Assert.IsTrue(foundR1 && foundR2); break; case "channel:///c2": Assert.AreEqual(owner.At("publish"), subx.Destination); Assert.AreEqual(1, subx.Channels.Length); Assert.AreEqual(c2, subx.Channels[0]); Assert.AreEqual(1, subx.Recipients.Length); Assert.AreEqual(r1, subx.Recipients[0].Uri); break; case "channel:///c3": Assert.AreEqual(owner.At("publish"), subx.Destination); Assert.AreEqual(1, subx.Channels.Length); Assert.AreEqual(c3, subx.Channels[0]); Assert.AreEqual(1, subx.Recipients.Length); Assert.AreEqual(r2, subx.Recipients[0].Uri); break; default: Assert.Fail(); break; } } }
/// <summary> /// Override hook for filtering recipients for an event. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="subscription">Matching subscription.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield FilterRecipients(DispatcherEvent ev, PubSubSubscription subscription, Result<DispatcherEvent> result) { if(ev.Recipients.Length == 0) { // if the event has no recipient list, anyone can receive it result.Return(ev); yield break; } var recipients = ArrayUtil.Intersect(ev.Recipients, subscription.Recipients); result.Return(recipients.Length == 0 ? null : ev.WithRecipient(true, recipients)); yield break; }
//--- Constructors --- private PubSubSubscription(XUri channel, XUri resource, PubSubSubscriptionSet owner, DreamCookie cookie, PubSubSubscription first, PubSubSubscription second) { if (channel == null) { throw new ArgumentNullException("channel"); } Channels = new[] { channel }; Resources = resource == null ? new XUri[0] : new[] { resource }; Id = Guid.NewGuid().ToString(); Owner = owner; Destination = Owner.Owner.At("publish"); Cookie = cookie; Recipients = ArrayUtil.Union(first.Recipients, (second == null) ? new DispatcherRecipient[0] : second.Recipients); _isProxy = true; }
//--- Class Methods --- /// <summary> /// Merge two subscriptions on matchine channel and resource. /// </summary> /// <param name="channel">Common channel.</param> /// <param name="resource">Common resource.</param> /// <param name="owner">Common owner.</param> /// <param name="cookie">Subscription set cookie.</param> /// <param name="first">First subscription to merge.</param> /// <param name="second">Second subscription to merge.</param> /// <returns></returns> public static PubSubSubscription MergeForChannelAndResource(XUri channel, XUri resource, PubSubSubscriptionSet owner, DreamCookie cookie, PubSubSubscription first, PubSubSubscription second) { return(new PubSubSubscription(channel, resource, owner, cookie, first, second)); }