Beispiel #1
0
        public static string GetUrlLocalUri(XUri confBaseUri, string url, bool includeQuery, bool decode) {
            if(string.IsNullOrEmpty(url)) {
                return null;
            }

            if(url.StartsWithInvariantIgnoreCase(confBaseUri.ToString())) {
                
                //Remove the wiki path prefix (everything before display generally)
                url = confBaseUri.SchemeHostPort + url.Substring(confBaseUri.ToString().Length);
            }

            XUri uri = XUri.TryParse(url);
            
            if(uri == null) {
                return null;
            }
            string ret = uri.Path;
            if(decode) {
                ret = XUri.Decode(ret);
            }

            if(includeQuery && !string.IsNullOrEmpty(uri.QueryFragment)) {
                ret += uri.QueryFragment;
            }
            return ret;
        }
        // --- Constructors ---
        public RemoteInstanceManager(DekiWikiService dekiService, XUri directoryUri) : base(dekiService) {
            _directory = Plug.New(directoryUri);

            DreamMessage testMsg = _directory.GetAsync().Wait();
            if (!testMsg.IsSuccessful)
                throw new DreamInternalErrorException(string.Format("Error validating remote deki portal service at '{0}'", directoryUri.ToString()));
        }
        private string CachedWebGet(XUri uri, double? ttl) {
            string id = uri.ToString();

            // fetch message from cache or from the web
            CacheEntry result;
            bool isNew = true;
            lock(_cacheLookup) {
                _cacheLookup.TryGetValue(id, out result);
            }

            // check if we have a cached entry
            if(result != null) {
                _log.DebugFormat("cache hit for '{0}'", result.Id);
                isNew = false;
                result.ResetMemoryExpiration();
                if(result.Cache != null) {
                    _log.DebugFormat("cache data in memory '{0}'", result.Id);
                    return result.Cache;
                }

                // we have the result on disk, so let's fetch it
                DreamMessage msg = Storage.At(CACHE_DATA, result.Guid + ".bin").GetAsync().Wait();
                if(msg.IsSuccessful) {
                    _log.DebugFormat("cache data pulled from disk");
                    result.Cache = Encoding.UTF8.GetString(msg.AsBytes());
                    return result.Cache;
                }
                _log.DebugFormat("unable to fetch cache data from disk: {0}", msg.Status);
            } else {
                _log.DebugFormat("new cache item for '{0}'", id);
                result = new CacheEntry(id, ttl);
            }

            // do the web request
            Result<DreamMessage> response = new Result<DreamMessage>();
            Plug.New(uri).WithTimeout(DEFAULT_WEB_TIMEOUT).InvokeEx("GET", DreamMessage.Ok(), response);
            DreamMessage message = response.Wait();
            try {

                // check message status
                if(!message.IsSuccessful) {
                    return message.Status == DreamStatus.UnableToConnect
                        ? string.Format("(unable to fetch text document from uri [status: {0} ({1}), message: \"{2}\"])", (int)message.Status, message.Status, message.ToDocument()["message"].AsText)
                        : string.Format("(unable to fetch text document from uri [status: {0} ({1})])", (int)message.Status, message.Status);
                }

                // check message size
                Result resMemorize = message.Memorize(_insertTextLimit, new Result()).Block();
                if(resMemorize.HasException) {
                    return "(text document is too large)";
                }

                // check if response is an XML document
                result.Cache = message.AsText() ?? string.Empty;
            } finally {
                message.Close();
            }

            // start timer to clean-up cached result
            if(result.Cache != null) {
                XDoc infoDoc = new XDoc("cache-entry")
                    .Elem("guid", result.Guid)
                    .Elem("id", result.Id)
                    .Elem("expires", result.Expires);
                lock(result) {
                    Storage.At(CACHE_DATA, result.Guid + ".bin").PutAsync(new DreamMessage(DreamStatus.Ok, null, MimeType.BINARY, Encoding.UTF8.GetBytes(result.Cache))).Wait();
                    Storage.At(CACHE_INFO, result.Guid + ".xml").PutAsync(infoDoc).Wait();
                }
                if(isNew) {
                    lock(_cacheLookup) {
                        _cacheLookup[id] = result;
                    }

                    // this timer removes the cache entry from disk 
                    SetupCacheTimer(result);
                }
            }
            return result.Cache;
        }
        public Yield UserLogin(DreamContext context, DreamMessage request, Result<DreamMessage> response) {
            
            string userSuppliedIdentifier = context.GetParam("url", null);
            if (String.IsNullOrEmpty(userSuppliedIdentifier)) {
                _log.Info("No identifier was specified");
                throw new DreamBadRequestException("No identifier was specified.");
            }

            XUri returnUri = new XUri(context.GetParam("returnurl", null));
            String realm = context.GetParam("realm", null);
            if (String.IsNullOrEmpty(realm)) {
                realm = returnUri.WithoutPathQueryFragment().ToString();
            }

            IAuthenticationRequest openIdRequest;

            // dummy parameters required by DotNetOpenId 2.x; in 3.x, you can
            // just pass null to the OpenIdRelyingParty constructor.
            Uri identifierUri = new Uri(userSuppliedIdentifier);
            NameValueCollection queryCol = System.Web.HttpUtility.ParseQueryString(identifierUri.Query);
            OpenIdRelyingParty openid = new OpenIdRelyingParty(null, identifierUri, queryCol);
           
            // creating an OpenID request will authenticate that 
            // the endpoint exists and is an OpenID provider.
            _log.DebugFormat("Creating OpenID request: identifier {0}, return URL {1}, realm {2}", userSuppliedIdentifier, returnUri.ToString(), realm); 

            try {
                openIdRequest = openid.CreateRequest(
                    userSuppliedIdentifier,
                    realm,
                    returnUri.ToUri());
            } catch (OpenIdException ex) {
                _log.WarnFormat("'{0}' rejected as OpenID identifier: {1}", userSuppliedIdentifier, ex.Message);
                throw new DreamBadRequestException(string.Format("'{0}' is not a valid OpenID identifier. {1}", userSuppliedIdentifier, ex.Message));
            }

            // Ask for the e-mail address on this request.
            // Use both SREG and AX, to increase the odds of getting it.
            openIdRequest.AddExtension(new ClaimsRequest{
                Email = DemandLevel.Require,
            });

            var fetch = new FetchRequest();
            fetch.AddAttribute(new AttributeRequest(WellKnownAttributes.Contact.Email, true));
            openIdRequest.AddExtension(fetch);

            // The RedirectingResponse either contains a "Location" header for 
            // a HTTP GET, which will return in the response as 'endpoint', or
            // a HTML FORM which needs to be displayed to the user, which will
            // return in the response as 'form'.
            IResponse wr = openIdRequest.RedirectingResponse;

            XDoc result = new XDoc("openid");
            if (String.IsNullOrEmpty(wr.Headers["Location"])) {
                System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
                string formBody = enc.GetString(wr.Body);
                _log.DebugFormat("OpenID redirect by HTML FORM: {0}", formBody);  
                result.Attr("form", formBody);  
            } else {
                string redirectUrl = wr.Headers["Location"];
                _log.DebugFormat("OpenID redirect URL: {0}", redirectUrl);
                result.Attr("endpoint", redirectUrl);
            }

            response.Return(DreamMessage.Ok(result));
            yield break;
        }
 private void AddBodyRequest(string method, XUri href, XDoc body, string type) {
     _requestDoc.Start("request")
         .Attr("method", method)
         .Attr("href", href.ToString())
         .Attr("type", type)
         .Start("body")
             .Attr("type", "xml")
             .Add(body)
         .End()
     .End();
 }
 private void AddRequest(string method, XUri href, string data, string type) {
     _requestDoc.Start("request")
         .Attr("method", method)
         .Attr("href", href.ToString())
         .Attr("dataid", data)
         .Attr("type", type)
     .End();
 }
Beispiel #7
0
 public void XmlAsUri()
 {
     XUri uri = new XUri("http://foo.com/bar");
     XDoc doc = new XDoc("test").Elem("uri", uri.ToString());
     Assert.AreEqual(uri.ToString(), doc["uri"].AsText);
     Assert.AreEqual(uri, doc["uri"].AsUri);
 }
Beispiel #8
0
        private Result<DreamMessage> SubmitRequestAsync(string verb, XUri uri, IPrincipal user, DreamMessage request, Result<DreamMessage> response, Action completion)
        {
            if(string.IsNullOrEmpty(verb)) {
                if(completion != null) {
                    completion();
                }
                throw new ArgumentNullException("verb");
            }
            if(uri == null) {
                if(completion != null) {
                    completion();
                }
                throw new ArgumentNullException("uri");
            }
            if(request == null) {
                if(completion != null) {
                    completion();
                }
                throw new ArgumentNullException("request");
            }
            if(response == null) {
                if(completion != null) {
                    completion();
                }
                throw new ArgumentNullException("response");
            }

            // ensure environment is still running
            if(!IsRunning) {
                response.Return(DreamMessage.InternalError("host not running"));
                if(completion != null) {
                    completion();
                }
                return response;
            }

            try {
                Interlocked.Increment(ref _requestCounter);

                // check if we were not able to begin processing the request
                DreamMessage failed = BeginRequest(completion, uri, request);
                if(failed != null) {
                    response.Return(failed);
                    EndRequest(completion, uri, request);
                    return response;
                }

                // check if 'verb' is overwritten by a processing parameter
                verb = verb.ToUpperInvariant();
                string requestedVerb = (uri.GetParam(DreamInParam.VERB, null) ?? request.Headers.MethodOverride ?? verb).ToUpperInvariant();
                if(
                    verb.EqualsInvariant(Verb.POST) || (
                        verb.EqualsInvariant(Verb.GET) && (
                            requestedVerb.EqualsInvariant(Verb.OPTIONS) ||
                            requestedVerb.EqualsInvariant(Verb.HEAD)
                        )
                    )
                ) {
                    verb = requestedVerb;
                }

                // check if an origin was specified
                request.Headers.DreamOrigin = uri.GetParam(DreamInParam.ORIGIN, request.Headers.DreamOrigin);

                // check if a public uri is supplied
                XUri publicUri = XUri.TryParse(uri.GetParam(DreamInParam.URI, null) ?? request.Headers.DreamPublicUri);
                XUri transport = XUri.TryParse(request.Headers.DreamTransport) ?? uri.WithoutCredentialsPathQueryFragment();
                if(publicUri == null) {

                    // check if request is local
                    if(transport.Scheme.EqualsInvariantIgnoreCase("local")) {

                        // local:// uris with no public-uri specifier default to the configured public-uri
                        publicUri = _publicUri;
                    } else {

                        // check if the request was forwarded through Apache mod_proxy
                        string proxyOverride = uri.GetParam(DreamInParam.HOST, null);
                        if(string.IsNullOrEmpty(proxyOverride)) {
                            proxyOverride = request.Headers.ForwardedHost;
                        }
                        string serverPath = string.Join("/", transport.Segments);
                        if(proxyOverride != null) {

                            // request used an override, append path of public-uri
                            serverPath = string.Join("/", _publicUri.Segments);
                        }

                        // set the uri scheme based-on the incoming scheme and the override header
                        string scheme = transport.Scheme;
                        if("On".EqualsInvariantIgnoreCase(request.Headers.FrontEndHttps ?? "")) {
                            scheme = Scheme.HTTPS;
                        }
                        scheme = uri.GetParam(DreamInParam.SCHEME, scheme);

                        // set the host port
                        string hostPort = proxyOverride ?? request.Headers.Host ?? uri.HostPort;
                        publicUri = new XUri(string.Format("{0}://{1}", scheme, hostPort)).AtPath(serverPath);
                    }
                    request.Headers.DreamPublicUri = publicUri.ToString();
                }

                // set host header
                request.Headers.Host = publicUri.HostPort;

                // convert incoming uri to local://
                XUri localFeatureUri = uri.ChangePrefix(uri.WithoutPathQueryFragment(), _localMachineUri);

                // check if path begins with public uri path
                if((transport.Segments.Length > 0) && localFeatureUri.PathStartsWith(transport.Segments)) {
                    localFeatureUri = localFeatureUri.WithoutFirstSegments(transport.Segments.Length);
                }

                // check if the path is the application root and whether we have special behavior for that
                if(localFeatureUri.Path.IfNullOrEmpty("/") == "/") {
                    if(!string.IsNullOrEmpty(_rootRedirect)) {
                        localFeatureUri = localFeatureUri.AtAbsolutePath(_rootRedirect);
                    } else if(IsDebugEnv) {
                        localFeatureUri = localFeatureUri.AtAbsolutePath("/host/services");
                    }
                }

                // find the requested feature
                List<DreamFeature> features;
                lock(_features) {
                    features = _features.Find(localFeatureUri);
                }
                DreamFeature feature = null;
                if(features != null) {

                    // TODO (steveb): match the incoming mime-type to the feature's acceptable mime-types (mime-type overloading)

                    // match the request verb to the feature verb
                    foreach(DreamFeature entry in features) {
                        if((entry.Verb == "*") || entry.Verb.EqualsInvariant(verb)) {
                            feature = entry;
                            break;
                        }
                    }

                    // check if this is an OPTIONS request and there is no defined feature for it
                    if(verb.EqualsInvariant(Verb.OPTIONS) && ((feature == null) || feature.Verb.EqualsInvariant("*"))) {

                        // list all allowed methods
                        List<string> methods = new List<string>();
                        foreach(DreamFeature entry in features) {
                            if(!methods.Contains(entry.Verb)) {
                                methods.Add(entry.Verb);
                            }
                        }
                        methods.Sort(StringComparer.Ordinal.Compare);
                        DreamMessage result = DreamMessage.Ok();
                        result.Headers.Allow = string.Join(", ", methods.ToArray());
                        response.Return(result);

                        // decrease counter for external requests
                        EndRequest(completion, uri, request);
                        return response;
                    }
                }

                // check if a feature was found
                if(feature == null) {
                    DreamMessage result;

                    // check if any feature was found
                    if((features == null) || (features.Count == 0)) {
                        string msg = verb + " URI: " + uri.ToString(false) + " LOCAL: " + localFeatureUri.ToString(false) + " PUBLIC: " + publicUri + " TRANSPORT: " + transport;
                        _log.WarnMethodCall("ProcessRequest: feature not found", msg);
                        result = DreamMessage.NotFound("resource not found");
                    } else {
                        string msg = verb + " " + uri.ToString(false);
                        _log.WarnMethodCall("ProcessRequest: method not allowed", msg);
                        List<string> methods = new List<string>();
                        foreach(DreamFeature entry in features) {
                            if(!methods.Contains(entry.Verb)) {
                                methods.Add(entry.Verb);
                            }
                        }
                        methods.Sort(StringComparer.Ordinal.Compare);
                        result = DreamMessage.MethodNotAllowed(methods.ToArray(), "allowed methods are " + string.Join(", ", methods.ToArray()));
                    }
                    response.Return(result);

                    // decrease counter for external requests
                    EndRequest(completion, uri, request);
                    return response;
                }

                // add uri to aliases list
                if(_memorizeAliases) {
                    lock(_aliases) {
                        _aliases[transport] = transport;
                        _aliases[publicUri] = publicUri;
                    }
                }

                // create context
                DreamContext context = new DreamContext(this, verb, localFeatureUri, feature, publicUri, _publicUri, request, CultureInfo.InvariantCulture, GetRequestLifetimeScopeFactory(feature.Service));

                // attach request id to the context
                context.SetState(DreamHeaders.DREAM_REQUEST_ID, request.Headers.DreamRequestId);

                // add user to context
                context.User = user;

                // build linked-list of feature calls
                var chain = new Result<DreamMessage>(TimeSpan.MaxValue, TaskEnv.Current).WhenDone(result => {

                    // extract message
                    DreamMessage message;
                    if(result.HasValue) {
                        message = result.Value;
                    } else if(result.Exception is DreamAbortException) {
                        message = ((DreamAbortException)result.Exception).Response;
                    } else if(result.Exception is DreamCachedResponseException) {
                        message = ((DreamCachedResponseException)result.Exception).Response;
                    } else {
                        _log.ErrorExceptionFormat(response.Exception, "Failed Feature '{0}' Chain [{1}:{2}]: {3}",
                            feature.MainStage.Name,
                            verb,
                            localFeatureUri.Path,
                            response.Exception.Message
                        );
                        message = DreamMessage.InternalError(result.Exception);
                    }

                    // decrease counter for external requests
                    EndRequest(completion, uri, request);

                    // need to manually dispose of the context, since we're already attaching and detaching it by hand to TaskEnvs throughout the chain
                    if(response.IsCanceled) {
                        _log.DebugFormat("response for '{0}' has already returned", context.Uri.Path);
                        response.ConfirmCancel();
                        ((ITaskLifespan)context).Dispose();
                    } else {
                        ((ITaskLifespan)context).Dispose();
                        response.Return(message);
                    }
                });
                for(int i = feature.Stages.Length - 1; i >= 0; --i) {
                    var link = new DreamFeatureChain(feature.Stages[i], i == feature.MainStageIndex, context, chain, (i > 0) ? feature.Stages[i - 1].Name : "first");
                    chain = new Result<DreamMessage>(TimeSpan.MaxValue, TaskEnv.Current).WhenDone(link.Handler);
                }

                // kick-off new task
                AsyncUtil.Fork(
                    () => chain.Return(request),
                    TaskEnv.New(TimerFactory),
                    new Result(TimeSpan.MaxValue, response.Env).WhenDone(res => {
                        if(!res.HasException) {
                            return;
                        }
                        _log.ErrorExceptionFormat(res.Exception, "handler for {0}:{1} failed", context.Verb, context.Uri.ToString(false));
                        ((ITaskLifespan)context).Dispose();

                        // forward exception to recipient
                        response.Throw(res.Exception);

                        // decrease counter for external requests
                        EndRequest(completion, uri, request);
                    })
                );
            } catch(Exception e) {
                response.Throw(e);
                EndRequest(completion, uri, request);
            }
            return response;
        }
Beispiel #9
0
        private void StopService(XUri uri)
        {
            string path = uri.Path.ToLowerInvariant();

            // remove service from services table
            ServiceEntry service;
            lock(_services) {
                if(_services.TryGetValue(path, out service)) {
                    _services.Remove(path);
                }
            }
            if(_log.IsDebugEnabled) {
                string sid = "<UNKNOWN>";
                string type = "<UNKNOWN>";
                if(service != null) {
                    sid = service.SID.ToString();
                    if(service.Service != null) {
                        type = service.Service.GetType().ToString();
                    }
                }
                _log.DebugMethodCall("stop", path, sid, type);
            }

            // deactivate service
            DreamMessage deleteResponse = Plug.New(uri).At("@config").Delete(new Result<DreamMessage>(TimeSpan.MaxValue)).Wait();
            if(!deleteResponse.IsSuccessful) {
                _log.InfoMethodCall("StopService: Delete failed", uri.ToString(false), deleteResponse.Status);
            }

            // deactivate features
            lock(_features) {
                _features.Remove(uri);
            }

            // check for private-storage service
            if((service != null) && !service.Blueprint["setup/private-storage"].IsEmpty) {
                StopService(Self.At("private-storage", EncodedServicePath(service.Uri)));
            }

            // check for any lingering child services
            List<ServiceEntry> entries;
            lock(_services) {
                entries = _services.Values.Where(entry => uri == entry.Owner).ToList();
            }
            foreach(ServiceEntry entry in entries) {
                _log.WarnMethodCall("StopService: child service was not shutdown properly", entry.Service.Self.Uri.ToString(false));
                StopService(entry.Service.Self);
            }
        }
        //--- Methods ---
        private Yield FetchResult(string name, XUri input, Hashtable args, Result<XDoc> response) {

            // build uri
            XUri uri = new XUri("http://www.dapper.net/RunDapp?v=1").With("dappName", name);
            if(input != null) {
                uri = uri.With("applyToUrl", input.ToString());
            }
            if(args != null) {
                foreach(DictionaryEntry entry in args) {
                    uri = uri.With(VARIABLE_PREFIX + SysUtil.ChangeType<string>(entry.Key), SysUtil.ChangeType<string>(entry.Value));
                }
            }

            // check if we have a cached result
            XDoc result;
            string key = uri.ToString();
            lock(_cache) {
                if(_cache.TryGetValue(key, out result)) {
                    response.Return(result);
                    yield break;
                }
            }

            // fetch result
            Result<DreamMessage> res;
            yield return res = Plug.New(uri).GetAsync();
            if(!res.Value.IsSuccessful) {
                throw new DreamInternalErrorException(string.Format("Unable to process Dapp: ", input));
            }
            if(!res.Value.HasDocument) {
                throw new DreamInternalErrorException(string.Format("Dapp response is not XML: ", input));
            }

            // add result to cache and start a clean-up timer
            lock(_cache) {
                _cache[key] = res.Value.ToDocument();
            }
            TaskTimer.New(TimeSpan.FromSeconds(CACHE_TTL), RemoveCachedEntry, key, TaskEnv.None);
            response.Return(res.Value.ToDocument());
            yield break;
        }
        private Yield Process(XUri image, string options, Result<XUri> result) {

            // TODO (steveb):
            //  1) check if the fetched image has caching information
            //  2) if it does, check if it changed since last operation
            //  3) generate MD5 from image (not from uri)
            //  4) respond with new uri

            if(image == null) {
                throw new DreamBadRequestException("invalid image uri");
            }

            // check if we have a cached response
            string key = new Guid(StringUtil.ComputeHash(image.ToString() + " " + options)).ToString() + ".png";
            Plug location = Storage.At(key);
            Result<DreamMessage> res;
            yield return res = location.InvokeAsync("HEAD", DreamMessage.Ok());
            if(res.Value.IsSuccessful) {
                result.Return(Self.At("images", key));
                yield break;
            }

            // check if image-magick is properly configured
            if(string.IsNullOrEmpty(ImageMagick) || !File.Exists(ImageMagick)) {
                throw new DreamBadRequestException("imagemagick misconfigured or missing");
            }

            // fetch image
            res = new Result<DreamMessage>();
            Plug.New(image).InvokeEx("GET", DreamMessage.Ok(), res);
            yield return res;
            if(!res.Value.IsSuccessful) {
                res.Value.Close();
                throw new DreamAbortException(DreamMessage.NotFound("could not retrieve image"));
            }

            // invoke image conversion
            Result<Tuplet<int, Stream, Stream>> exitRes = new Result<Tuplet<int, Stream, Stream>>(TimeSpan.FromSeconds(30));
            using(Stream input = res.Value.AsStream()) {
                yield return Async.ExecuteProcess(ImageMagick, string.Format("{0} {1}:-", options, "png"), input, exitRes);
            }

            // check outcome
            int status = exitRes.Value.Item1;
            Stream output = exitRes.Value.Item2;
            Stream error = exitRes.Value.Item3;
            if(status != 0) {
                string message;
                using(StreamReader reader = new StreamReader(error, Encoding.ASCII)) {
                    message = reader.ReadToEnd();
                }
                throw new DreamAbortException(DreamMessage.InternalError(string.Format("operation failed with status {0}:\n{1}", status, message)));
            }

            // create result
            yield return res = location.With("ttl", TimeSpan.FromDays(7).TotalSeconds).PutAsync(DreamMessage.Ok(MimeType.PNG, output.Length, output));
            if(!res.Value.IsSuccessful) {
                throw new DreamAbortException(new DreamMessage(res.Value.Status, null, res.Value.ContentType, res.Value.AsBytes()));
            }
            result.Return(Self.At("images", key));
        }
        private XDoc RewriteRedirectsAndUris(XDoc searchResults) {
            DekiContext context = DekiContext.Current;

            // setup proper URI for file attachments and users
            foreach(XDoc node in searchResults["document"]) {
                try {
                    if(node["type"].AsText.EqualsInvariant("wiki")) {
                        node["uri"].ReplaceValue(Utils.AsPublicUiUri(Title.FromUriPath(node["path"].AsText)));
                    } else if(node["type"].AsText.EqualsInvariant("comment")) {
                        XUri commentUri = node["uri"].AsUri;
                        XUri publicCommentUri = new XUri(Utils.AsPublicUiUri(Title.FromUriPath(node["path"].AsText))).WithFragment(commentUri.Fragment);
                        node["uri"].ReplaceValue(publicCommentUri.ToString());
                    } else if(!node["id.file"].IsEmpty) {
                        node["uri"].ReplaceValue(context.ApiUri.At("files", node["id.file"].AsText, Title.AsApiParam(node["title"].AsText)));
                    } else if(!node["id.user"].IsEmpty) {
                        node["uri"].ReplaceValue(new XUri(Utils.AsPublicUiUri(Title.FromUIUsername(node["username"].AsText))));
                    }
                } catch(Exception e) {
                    _log.Warn(string.Format("Unable to generate UI uri for API Uri '{0}'", node["uri"].AsText), e);
                }
            }
            return searchResults;
        }
        public void Authtoken_recipients_always_get_what_they_want() {
            XUri mockDeki = new XUri("http://mock/deki");
            int dekiCalled = 0;
            MockPlug.Register(mockDeki, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                _log.DebugFormat("deki called at {0}", u);
                dekiCalled++;
                r2.Return(DreamMessage.Ok());
            });
            XUri mockAuthorized = new XUri("http://mock/authorized");
            int authorizedCalled = 0;
            XDoc received = null;
            var authorizedResetEvent = new ManualResetEvent(false);
            MockPlug.Register(mockAuthorized, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                authorizedCalled++;
                received = r.ToDocument();
                authorizedResetEvent.Set();
                r2.Return(DreamMessage.Ok());
            });
            XUri mockNotAuthorized = new XUri("http://mock/notauthorized");
            int notAuthorizedCalled = 0;
            MockPlug.Register(mockNotAuthorized, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                notAuthorizedCalled++;
                r2.Return(DreamMessage.Ok());
            });
            DreamServiceInfo serviceInfo = DreamTestHelper.CreateService(
                _hostInfo,
                "sid://mindtouch.com/dream/2008/10/pubsub",
                "authorization",
                new XDoc("config")
                    .Elem("authtoken", "abc")
                    .Elem("uri.deki", mockDeki)
                    .Start("components")
                        .Attr("context", "service")
                        .Start("component")
                            .Attr("implementation", typeof(DekiDispatcher).AssemblyQualifiedName)
                            .Attr("type", typeof(IPubSubDispatcher).AssemblyQualifiedName)
                        .End()
                    .End()
            );
            XDoc subForAuthorized = new XDoc("subscription-set")
                .Elem("uri.owner", mockAuthorized)
                .Start("subscription")
                    .Attr("id", "1")
                    .Elem("uri.resource", "http://mock/resource/*")
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient").Attr("authtoken", "abc").Elem("uri", mockAuthorized).End()
                .End();
            DreamMessage result = serviceInfo.WithInternalKey().AtLocalHost.At("subscribers").PostAsync(subForAuthorized).Wait();
            Assert.IsTrue(result.IsSuccessful);

            XDoc subForNotAuthorized = new XDoc("subscription-set")
                .Elem("uri.owner", mockNotAuthorized)
                .Start("subscription")
                    .Attr("id", "1")
                    .Elem("uri.resource", "http://mock/resource/*")
                    .Elem("channel", "channel:///foo/*")
                    .Start("recipient").Elem("uri", mockNotAuthorized).End()
                .End();
            result = serviceInfo.WithInternalKey().AtLocalHost.At("subscribers").PostAsync(subForNotAuthorized).Wait();
            Assert.IsTrue(result.IsSuccessful);
            XDoc msg = new XDoc("foop");
            result = serviceInfo.WithInternalKey()
                .AtLocalHost
                .At("publish")
                .WithHeader(DreamHeaders.DREAM_EVENT_CHANNEL, "channel:///foo/bar")
                .WithHeader(DreamHeaders.DREAM_EVENT_ORIGIN, mockDeki.ToString())
                .WithHeader(DreamHeaders.DREAM_EVENT_RESOURCE, "http://mock/resource/bar")
                .PostAsync(msg).Wait();
            Assert.IsTrue(result.IsSuccessful);

            // Meh. Testing multithreaded code is wonky. This 1000ms sleep is required, otherwise the event below may not fire
            Thread.Sleep(1000);
            Assert.IsTrue(authorizedResetEvent.WaitOne(2000, true));
            Assert.AreEqual(0, dekiCalled);
            Assert.AreEqual(0, notAuthorizedCalled);
            Assert.AreEqual(1, authorizedCalled);
            Assert.AreEqual(msg, received);
        }
        public void Uses_deki_to_prune_recipients() {
            XUri mockDeki = new XUri("http://mock/deki");
            int dekiCalled = 0;
            int dekipage42authCalled = 0;
            bool dekiArgsGood = false;
            int dekipage43authCalled = 0;
            MockPlug.Register(mockDeki, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                _log.DebugFormat("mockDeki called at: {0}", u);
                dekiCalled++;
                dekiArgsGood = false;
                List<int> users = new List<int>();
                foreach(XDoc user in r.ToDocument()["user/@id"]) {
                    users.Add(user.AsInt.Value);
                }
                if(users.Count == 4 && users.Contains(1) && users.Contains(2) && users.Contains(3) && users.Contains(4)) {
                    dekiArgsGood = true;
                }
                DreamMessage msg = DreamMessage.Ok();
                if(u.WithoutQuery() == mockDeki.At("pages", "42", "allowed")) {
                    dekipage42authCalled++;
                    msg = DreamMessage.Ok(new XDoc("users")
                        .Start("user").Attr("id", 1).End()
                        .Start("user").Attr("id", 2).End());
                } else if(u.WithoutQuery() == mockDeki.At("pages", "43", "allowed")) {
                    dekipage43authCalled++;
                    msg = DreamMessage.Ok(new XDoc("users")
                        .Start("user").Attr("id", 3).End()
                        .Start("user").Attr("id", 4).End());
                }
                r2.Return(msg);
            });
            XUri mockRecipient = new XUri("http://mock/r1");
            int mockRecipientCalled = 0;
            XDoc received = null;
            List<string> recipients = new List<string>();
            AutoResetEvent are = new AutoResetEvent(false);
            MockPlug.Register(mockRecipient, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                _log.DebugFormat("mockRecipient called at: {0}", u);
                mockRecipientCalled++;
                received = r.ToDocument();
                recipients.Clear();
                recipients.AddRange(r.Headers.DreamEventRecipients);
                are.Set();
                r2.Return(DreamMessage.Ok());
            });

            DreamServiceInfo serviceInfo = DreamTestHelper.CreateService(
                _hostInfo,
                "sid://mindtouch.com/dream/2008/10/pubsub",
                "whitelist",
                new XDoc("config")
                .Elem("uri.deki", mockDeki)
                .Start("components")
                    .Attr("context", "service")
                    .Start("component")
                        .Attr("implementation", typeof(DekiDispatcher).AssemblyQualifiedName)
                        .Attr("type", typeof(IPubSubDispatcher).AssemblyQualifiedName)
                    .End()
                .End()
            );
            XDoc sub = new XDoc("subscription-set")
                .Elem("uri.owner", mockRecipient)
                .Start("subscription")
                    .Attr("id", "1")
                    .Elem("uri.resource", "http://mock/resource/x")
                    .Elem("channel", "channel:///foo/*")
                    .Elem("uri.proxy", mockRecipient)
                    .Start("recipient").Attr("userid", "1").Elem("uri", "http://recipient/a").End()
                    .Start("recipient").Attr("userid", "2").Elem("uri", "http://recipient/b").End()
                    .Start("recipient").Attr("userid", "3").Elem("uri", "http://recipient/c").End()
                    .Start("recipient").Attr("userid", "4").Elem("uri", "http://recipient/d").End()
                .End()
                .Start("subscription")
                    .Attr("id", "2")
                    .Elem("uri.resource", "http://mock/resource/y")
                    .Elem("channel", "channel:///foo/*")
                    .Elem("uri.proxy", mockRecipient)
                    .Start("recipient").Attr("userid", "1").Elem("uri", "http://recipient/a").End()
                    .Start("recipient").Attr("userid", "2").Elem("uri", "http://recipient/b").End()
                    .Start("recipient").Attr("userid", "3").Elem("uri", "http://recipient/c").End()
                    .Start("recipient").Attr("userid", "4").Elem("uri", "http://recipient/d").End()
                .End();
            DreamMessage result = serviceInfo.WithInternalKey().AtLocalHost.At("subscribers").PostAsync(sub).Wait();
            Assert.IsTrue(result.IsSuccessful);
            XDoc ev = new XDoc("event").Elem("pageid", 42);
            result = serviceInfo.WithInternalKey()
                 .AtLocalHost
                 .At("publish")
                 .WithHeader(DreamHeaders.DREAM_EVENT_CHANNEL, "channel:///foo/bar")
                 .WithHeader(DreamHeaders.DREAM_EVENT_ORIGIN, mockDeki.ToString())
                 .WithHeader(DreamHeaders.DREAM_EVENT_RESOURCE, "http://mock/resource/x")
                 .PostAsync(ev).Wait();
            Assert.IsTrue(result.IsSuccessful);
            Assert.IsTrue(are.WaitOne(500, true));
            Assert.AreEqual(1, dekiCalled);
            Assert.AreEqual(1, dekipage42authCalled);
            Assert.IsTrue(dekiArgsGood);
            Assert.AreEqual(ev, received);
            Assert.AreEqual(2, recipients.Count);
            Assert.Contains("http://recipient/a", recipients);
            Assert.Contains("http://recipient/b", recipients);
            ev = new XDoc("event").Elem("pageid", 43);
            result = serviceInfo.WithInternalKey()
                 .AtLocalHost
                 .At("publish")
                 .WithHeader(DreamHeaders.DREAM_EVENT_CHANNEL, "channel:///foo/bar")
                 .WithHeader(DreamHeaders.DREAM_EVENT_ORIGIN, mockDeki.ToString())
                 .WithHeader(DreamHeaders.DREAM_EVENT_RESOURCE, "http://mock/resource/y")
                 .PostAsync(ev).Wait();
            Assert.IsTrue(result.IsSuccessful);
            Assert.IsTrue(are.WaitOne(5000, true));
            Assert.AreEqual(2, dekiCalled);
            Assert.AreEqual(1, dekipage42authCalled);
            Assert.IsTrue(dekiArgsGood);
            Assert.AreEqual(ev, received);
            Assert.AreEqual(2, recipients.Count);
            Assert.Contains("http://recipient/c", recipients);
            Assert.Contains("http://recipient/d", recipients);
        }
        public void Subscribe_and_see_page_change_dispatched() {
            XDoc set = null, sub = null;

            // make sure we have a pagesubservice subscription to start with
            Assert.IsTrue(Wait.For(() => {
                set = Utils.Settings.HostInfo.LocalHost.At("deki", "pubsub", "subscribers").With("apikey", Utils.Settings.HostInfo.ApiKey).Get(new Result<XDoc>()).Wait();
                return set["subscription[channel='event://*/deki/pages/update']"]
                    .Where(x => x["recipient/uri"].AsText.EndsWithInvariant("deki/pagesubservice/notify")).Any();
            }, 10.Seconds()), set.ToPrettyString());

            _log.Debug("start: Subscribe_and_see_page_change_dispatched");
            var emailResetEvent = new ManualResetEvent(false);

            // subscribe to dekipubsub, so we can verify page creation event has passed
            XUri coreSubscriber = new XUri("http://mock/dekisubscriber");
            var createdPages = new HashSet<string>();
            var modifiedPages = new HashSet<string>();
            MockPlug.Register(coreSubscriber, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                var doc = r.ToDocument();
                var channel = doc["channel"].AsUri;
                var pId = doc["pageid"].AsText;
                _log.DebugFormat("dekisubscriber called called with channel '{0}' for page {1}", channel, pId);
                if(channel.LastSegment == "create") {
                    lock(createdPages) {
                        createdPages.Add(pId);
                    }
                } else if(channel.LastSegment == "update") {
                    lock(modifiedPages) {
                        modifiedPages.Add(pId);
                    }
                } else {
                    _log.Info("wrong channel!");
                }
                r2.Return(DreamMessage.Ok());
            });
            XDoc subscriptionSet = new XDoc("subscription-set")
                .Elem("uri.owner", coreSubscriber)
                .Start("subscription")
                    .Elem("channel", "event://*/deki/pages/*")
                    .Start("recipient")
                        .Attr("authtoken", Utils.Settings.HostInfo.ApiKey)
                        .Elem("uri", coreSubscriber)
                    .End()
                .End();

            var subscriptionResult = Utils.Settings.HostInfo.LocalHost.At("deki", "pubsub", "subscribers").With("apikey", Utils.Settings.HostInfo.ApiKey).PostAsync(subscriptionSet).Wait();
            Assert.IsTrue(subscriptionResult.IsSuccessful);
            _log.DebugFormat("check for subscription in combined set");
            Assert.IsTrue(Wait.For(() => {
                set = Utils.Settings.HostInfo.LocalHost.At("deki", "pubsub", "subscribers").With("apikey", Utils.Settings.HostInfo.ApiKey).Get(new Result<XDoc>()).Wait();
                sub = set["subscription[channel='event://*/deki/pages/*']"];
                return sub["recipient/uri"].Contents == coreSubscriber.ToString();
            }, 10.Seconds()));
            int emailerCalled = 0;
            XDoc emailDoc = null;
            DreamMessage response;
            string pageId;
            string pagePath;

            // create a page
            _log.DebugFormat("creating page");
            response = PageUtils.CreateRandomPage(_adminPlug, out pageId, out pagePath);
            Assert.IsTrue(response.IsSuccessful);

            //give the page creation event a chance to bubble through
            _log.DebugFormat("checking for page {0} creation event", pageId);
            Assert.IsTrue(Wait.For(() => {
                Thread.Sleep(100);
                lock(createdPages) {
                    return createdPages.Contains(pageId);
                }
            }, 10.Seconds()));
            _log.DebugFormat("got create event");

            _log.DebugFormat("post page subscription for page {0}", pageId);
            response = _pageSub.At("pages", pageId).With("depth", "0").WithHeader("X-Deki-Site", "id=default").PostAsFormAsync().Wait();
            Assert.IsTrue(response.IsSuccessful);

            XUri emailerEndpoint = Utils.Settings.HostInfo.Host.LocalMachineUri.At("deki", "mailer");
            MockPlug.Register(emailerEndpoint, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) {
                var doc = r.ToDocument();
                var to = doc["to"].AsText;
                _log.DebugFormat("emailer called for: {0}", to);
                var fragment = _userName + "@";
                if(to.StartsWith(fragment) || to.Contains(fragment)) {
                    emailDoc = doc;
                    emailerCalled++;
                    emailResetEvent.Set();
                }
                r2.Return(DreamMessage.Ok());
            });

            _log.Debug("mod page");
            modifiedPages.Clear();

            response = PageUtils.SavePage(Utils.BuildPlugForAdmin(), pagePath, "foo");
            Assert.IsTrue(response.IsSuccessful);

            _log.DebugFormat("checking for page {0} update event", pageId);
            Assert.IsTrue(Wait.For(() => {
                Thread.Sleep(100);
                lock(modifiedPages) {
                    return modifiedPages.Contains(pageId);
                }
            }, 10.Seconds()));
            _log.DebugFormat("got update event");

            _log.Debug("waiting on email post");
            Assert.IsTrue(Wait.For(() => emailResetEvent.WaitOne(100, true), 10.Seconds()));
            _log.Debug("email fired");
            Assert.IsFalse(emailDoc.IsEmpty);
            Assert.AreEqual(pageId, emailDoc["pages/pageid"].AsText);
            Thread.Sleep(200);
            Assert.AreEqual(1, emailerCalled);
        }
Beispiel #16
0
        public virtual DekiScriptLiteral Invoke(Location location, XUri uri, DekiScriptLiteral args, DekiScriptEnv env) {
            var sw = Stopwatch.StartNew();
            DekiScriptInvocationTargetDescriptor descriptor;
            var target = _functions.TryGetValue(uri, out descriptor) ? descriptor.Target : FindTarget(uri);
            try {

                // invoke function directly
                return target.Invoke(this, args);
            } catch(Exception e) {
                throw UnwrapAsyncException(uri, e).Rethrow();
            } finally {
                sw.Stop();
                bool property = (uri.LastSegment ?? string.Empty).StartsWithInvariant("$");
                env.AddFunctionProfile(location, (descriptor != null) ? (property ? "$" : "") + descriptor.Name : uri.ToString(), sw.Elapsed);
            }
        }