//--- 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;
        }
Esempio n. 2
0
        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;
        }
Esempio n. 3
0
 //--- 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;
 }
Esempio n. 4
0
 //--- 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);
 }
Esempio n. 5
0
 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());
 }
Esempio n. 6
0
 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));
 }
Esempio n. 7
0
 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);
 }
Esempio n. 8
0
        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;
                }
            }
        }
Esempio n. 9
0
        /// <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));
        }