internal Yield DeleteSiteLogo(DreamContext context, DreamMessage request, Result <DreamMessage> response)
 {
     DekiContext.Current.Instance.Storage.DeleteSiteFile(LOGO_LABEL);
     ConfigBL.DeleteInstanceSettingsValue(ConfigBL.UI_LOGO_UPLOADED);
     response.Return(DreamMessage.Ok());
     yield break;
 }
 public bool MimeTypeCanBeInlined(MimeType mimeType)
 {
     if (_inlineDispositionBlacklist == null)
     {
         string[]        blackliststrings = ConfigBL.GetInstanceSettingsValue("files/blacklisted-disposition-mimetypes", string.Empty).ToLowerInvariant().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
         List <MimeType> blacklist        = new List <MimeType>();
         foreach (string v in blackliststrings)
         {
             MimeType mt = MimeType.New(v);
             if (mt != null)
             {
                 blacklist.Add(mt);
             }
         }
         string[]        whiteliststrings = ConfigBL.GetInstanceSettingsValue("files/whitelisted-disposition-mimetypes", string.Empty).ToLowerInvariant().Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
         List <MimeType> whitelist        = new List <MimeType>();
         foreach (string v in whiteliststrings)
         {
             MimeType mt = MimeType.New(v);
             if (mt != null)
             {
                 whitelist.Add(mt);
             }
         }
         _inlineDispositionBlacklist = blacklist;
         _inlineDispositionWhitelist = whitelist;
     }
     foreach (MimeType bad in _inlineDispositionBlacklist)
     {
         if (mimeType.Match(bad))
         {
             return(false);
         }
     }
     foreach (MimeType good in _inlineDispositionWhitelist)
     {
         if (mimeType.Match(good))
         {
             return(true);
         }
     }
     return(false);
 }
        internal Yield PutSiteLogo(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            //Confirm image type
            if (!new MimeType("image/*").Match(request.ContentType))
            {
                throw new SiteImageMimetypeInvalidArgumentException();
            }
            try {
                //Save file to storage provider
                DekiContext.Current.Instance.Storage.PutSiteFile(LOGO_LABEL, new StreamInfo(request.AsStream(), request.ContentLength, request.ContentType));
                ConfigBL.SetInstanceSettingsValue(ConfigBL.UI_LOGO_UPLOADED, "true");
            } catch (Exception x) {
                DekiContext.Current.Instance.Log.Warn("Failed to save logo to storage provider", x);
                ConfigBL.DeleteInstanceSettingsValue(ConfigBL.UI_LOGO_UPLOADED);
                throw;
            }

            StreamInfo file = DekiContext.Current.Instance.Storage.GetSiteFile(LOGO_LABEL, false);

            if (file != null)
            {
                StreamInfo thumb = AttachmentPreviewBL.BuildThumb(file, FormatType.PNG, RatioType.UNDEFINED, DekiContext.Current.Instance.LogoWidth, DekiContext.Current.Instance.LogoHeight);
                if (thumb != null)
                {
                    DekiContext.Current.Instance.Storage.PutSiteFile(LOGO_LABEL, thumb);
                }
                else
                {
                    DekiContext.Current.Instance.Log.WarnMethodCall("PUT:site/logo", "Unable to process logo through imagemagick");
                    DekiContext.Current.ApiPlug.At("site", "logo").Delete();
                    throw new SiteUnableToProcessLogoInvalidOperationException();
                }
            }
            else
            {
                DekiContext.Current.Instance.Log.WarnMethodCall("PUT:site/logo", "Unable to retrieve saved logo");
            }
            response.Return(DreamMessage.Ok());
            yield break;
        }
        internal Yield PutSiteSettings(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            if (!MimeType.XML.Match(request.ContentType))
            {
                throw new SiteExpectedXmlContentTypeInvalidArgumentException();
            }
            var  dekiContext = DekiContext.Current;
            XDoc settings    = request.ToDocument();

            ConfigBL.SetInstanceSettings(settings);
            dekiContext.Instance.EventSink.InstanceSettingsChanged(DekiContext.Current.Now);
            yield return(Coroutine.Invoke(ConfigureMailer, settings, new Result()).CatchAndLog(_log));

            // clear out the digital signature key
            dekiContext.Instance.PrivateDigitalSignature = null;

            // clear out banned words
            dekiContext.Instance.BannedWords = null;

            response.Return(DreamMessage.Ok());
            yield break;
        }
        internal Yield GetSiteSettings(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            var givenKey = context.GetParam("apikey", null);
            var includes = context.GetParam("include", "");

            //If apikey is not given for a request, dont return hidden entries
            var validMasterKey = MasterApiKey.EqualsInvariant(givenKey);
            var retrieve       = new SiteSettingsRetrievalSettings {
                IncludeHidden        = validMasterKey,
                IncludeAnonymousUser = includes.Contains(UserBL.ANON_USERNAME),
                IncludeLicense       = (validMasterKey || PermissionsBL.IsUserAllowed(DekiContext.Current.User, Permissions.ADMIN)) && includes.Contains(ConfigBL.LICENSE),
            };

            var doc = ConfigBL.GetInstanceSettingsAsDoc(retrieve);

            // check if a custom logo was uploaded; if yes, update the config document.
            // This is being done outside of caching since URIs to the api should be computed for every request
            if (doc[ConfigBL.UI_LOGO_UPLOADED].AsBool ?? false)
            {
                doc.InsertValueAt(ConfigBL.UI_LOGO_URI, DekiContext.Current.ApiUri.At("site", "logo.png").ToString());
            }
            response.Return(DreamMessage.Ok(doc));
            yield break;
        }
Exemple #6
0
        protected Yield PrologueDekiContext(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            // check if we need to skip this feature
            if (context.Feature.PathSegments.Length > 1 && context.Feature.PathSegments[context.Feature.ServiceUri.Segments.Length].StartsWith("@"))
            {
                response.Return(request);
                yield break;
            }
            if (context.Feature.Signature.StartsWithInvariantIgnoreCase("host") && !context.Feature.Signature.EqualsInvariantIgnoreCase("host/stop"))
            {
                // all host features except host/stop drop out before dekicontext so that there is no instance data
                response.Return(request);
                yield break;
            }
            var startInstanceIfNotRunning = true;

            if (context.Feature.Signature.StartsWithInvariantIgnoreCase("host/"))
            {
                startInstanceIfNotRunning = false;
            }

            // check if service has initialized
            if (!_isInitialized)
            {
                throw new DreamInternalErrorException("service not initialized");
            }

            //Build the dekicontext out of current request details and info from this wiki instance's details
            DekiInstance instance = _instanceManager.GetWikiInstance(request, startInstanceIfNotRunning);

            if (instance == null && !startInstanceIfNotRunning)
            {
                _log.Debug("no instance found, and on a code path that functions without one");
                response.Return(request);
                yield break;
            }

            // TODO (arnec): need to be able to get DreamContext.Current.StartTime injected into a feature signature
            var hostheader  = request.Headers.Host ?? string.Empty;
            var dekiContext = new DekiContext(this, instance, hostheader, context.StartTime, ResourceManager);

            // Note (arnec): By attaching DekiContext to the current DreamContext we guarantee that it is disposed at the end of the request
            DreamContext.Current.SetState(dekiContext);

            // check if instance has already been initialized
            if (instance != null)
            {
                if (instance.Status == DekiInstanceStatus.CREATED)
                {
                    bool created;
                    try {
                        lock (instance) {
                            created = (instance.Status == DekiInstanceStatus.CREATED);
                            if (created)
                            {
                                // initialize instance
                                instance.Startup(dekiContext);
                            }
                        }

                        // BUGBUGBUG (steveb): we startup the services AFTER the lock, because of race conditions, but this needs to be fixed
                        if (created)
                        {
                            instance.StartServices();
                        }
                    } catch (Exception e) {
                        created = false;
                        instance.StatusDescription = "Initialization exception: " + e.GetCoroutineStackTrace();
                        instance.Log.Error("Error initializing instance", e);
                    }
                    if (created)
                    {
                        // Note (arnec) this has to happen down here, since yield cannot exist inside a try/catch
                        // send instance settings to mailer
                        yield return(Coroutine.Invoke(ConfigureMailer, ConfigBL.GetInstanceSettingsAsDoc(false), new Result()).CatchAndLog(_log));

                        // check whether we have an index
                        XDoc lucenestate = null;
                        yield return(LuceneIndex.At("initstate").With("wikiid", instance.Id).Get(new Result <XDoc>()).Set(x => lucenestate = x));

                        // Note (arnec): defaulting to true, to avoid accidental re-index on false positive
                        if (!(lucenestate["@exists"].AsBool ?? true))
                        {
                            _log.DebugFormat("instance '{0}' doesn't have an index yet, forcing a rebuild", instance.Id);
                            yield return(Self.At("site", "search", "rebuild").With("apikey", MasterApiKey).Post(new Result <DreamMessage>()));
                        }
                    }
                }
                if (instance.Status != DekiInstanceStatus.ABANDONED)
                {
                    try {
                        // force a state check to verify that license is good
                        var state = dekiContext.LicenseManager.LicenseState;
                        _log.DebugFormat("instance '{0}' license state: {1}", instance.Id, state);
                    } catch (MindTouchRemoteLicenseFailedException) {
                        _instanceManager.ShutdownCurrentInstance();
                    }
                }
                instance.CheckInstanceIsReady();
                if (instance.Status == DekiInstanceStatus.ABANDONED)
                {
                    //If instance was abandoned (failed to initialize), error out.
                    throw new DreamInternalErrorException(string.Format("wiki '{0}' has failed to initialize or did not start up properly: {1}", instance.Id, instance.StatusDescription));
                }
                if (instance.Status == DekiInstanceStatus.STOPPING)
                {
                    throw new DreamInternalErrorException(string.Format("wiki '{0}' is currently shutting down", instance.Id));
                }
                if (instance.Status == DekiInstanceStatus.STOPPED)
                {
                    throw new DreamInternalErrorException(string.Format("wiki '{0}' has just shut down and may be restarted with a new request", instance.Id));
                }

                // intialize culture/language + user
                if (context.Culture.IsNeutralCulture || context.Culture.Equals(System.Globalization.CultureInfo.InvariantCulture))
                {
                    try {
                        context.Culture = new System.Globalization.CultureInfo(instance.SiteLanguage);
                    } catch {
                        // in case the site language is invalid, default to US English
                        context.Culture = new System.Globalization.CultureInfo("en-US");
                    }
                }
                if (!context.Feature.Signature.EqualsInvariantIgnoreCase("users/authenticate"))
                {
                    bool allowAnon = context.Uri.GetParam("authenticate", "false").EqualsInvariantIgnoreCase("false");
                    bool altPassword;
                    SetContextAndAuthenticate(request, 0, false, allowAnon, false, out altPassword);
                }

                // TODO (steveb): we should update the culture based on the user's preferences
            }

            // continue processing
            response.Return(request);
            yield break;
        }
        /// <summary>
        /// Calls methods to perform initial init of a wiki instance including db updates, etc
        /// </summary>
        public void Startup(DekiContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (_status != DekiInstanceStatus.CREATED)
            {
                throw new InvalidOperationException("bad state");
            }
            _status = DekiInstanceStatus.INITIALIZING;

            // run startup code
            try {
                // have to initialize the event sink first since because license checking might trigger a license transition event
                _eventSink = new DekiChangeSink(Id, DekiContext.Current.ApiUri, DekiContext.Current.Deki.PubSub.At("publish").WithCookieJar(DekiContext.Current.Deki.Cookies));

                // create the IDekiDataSessionFactory for this instance
                Type typeMySql   = Type.GetType("MindTouch.Deki.Data.MySql.MySqlDekiDataSessionFactory, mindtouch.deki.data.mysql", true);
                Type typeCaching = null;
                try {
                    typeCaching = Type.GetType("MindTouch.Deki.Data.Caching.CachingDekiDataSessionFactory, mindtouch.data.caching", false);
                } catch (Exception x) {
                    Log.Warn("The caching library was found but could not be loaded. Check that its version matches the version of your MindTouch API", x);
                }

                // FUN FACT: DekiInstanceSettings is a proxy for the static calls in ConfigBL, which expects to access DbUtils.CurrentSession,
                // which has to be initialized by the session factory.. Yes, the very session factory that takes DekiInstanceSettings as part
                // of its initialization call. I call it the M.C.Escher Design Pattern.
                var sessionFactory = (IDekiDataSessionFactory)typeMySql.GetConstructor(Type.EmptyTypes).Invoke(null);
                sessionFactory = new LoggingDekiDataSessionFactory(sessionFactory);
                sessionFactory = new DekiDataSessionProfilerFactory(sessionFactory);
                if (typeCaching != null)
                {
                    sessionFactory = (IDekiDataSessionFactory)typeCaching.GetConstructor(new[] { typeof(IDekiDataSessionFactory) }).Invoke(new object[] { sessionFactory });
                }
                _sessionFactory = sessionFactory;
                _sessionFactory.Initialize(Config ?? _deki.Config, _licenseController.LicenseDoc, new DekiInstanceSettings());
                var state = context.LicenseManager.LicenseState;
                if (state == LicenseStateType.UNDEFINED)
                {
                    throw new MindTouchInvalidLicenseStateException();
                }

                // set deki license token
                _token = context.LicenseManager.LicenseDocument["licensee/deki"].AsText;
                if (string.IsNullOrEmpty(_token))
                {
                    // compute deki license token
                    var    tokenKey = _apiKey ?? _deki.MasterApiKey;
                    ulong  folded_productkey_md5 = 0;
                    byte[] md5_bytes             = StringUtil.ComputeHash(tokenKey, Encoding.UTF8);
                    for (int i = 0; i < md5_bytes.Length; ++i)
                    {
                        folded_productkey_md5 ^= (ulong)md5_bytes[i] << (i & 7) * 8;
                    }
                    _token = folded_productkey_md5.ToString("X");
                }

                // check if a storage config section was provided (default storage is filesystem provider)
                XDoc storageConfig;
                switch (ConfigBL.GetInstanceSettingsValue("storage/@type", ConfigBL.GetInstanceSettingsValue("storage/type", "default")))
                {
                case "default":
                    string defaultAttachPath = Path.Combine(_deki.DekiPath, "attachments");
                    storageConfig = new XDoc("config")
                                    .Elem("path", defaultAttachPath)
                                    .Elem("cache-path", Path.Combine(defaultAttachPath, ".cache"));
                    _storage = new FSStorage(storageConfig);
                    break;

                case "fs":
                    string fsPath = ConfigBL.GetInstanceSettingsValue("storage/fs/path", null);

                    //Replace a $1 with the wiki name
                    fsPath        = string.Format(PhpUtil.ConvertToFormatString(fsPath ?? string.Empty), Id);
                    storageConfig = new XDoc("config")
                                    .Elem("path", fsPath)
                                    .Elem("cache-path", ConfigBL.GetInstanceSettingsValue("storage/fs/cache-path", null));
                    _storage = new FSStorage(storageConfig);
                    break;

                case "s3":
                    storageConfig = new XDoc("config")
                                    .Elem("publickey", ConfigBL.GetInstanceSettingsValue("storage/s3/publickey", null))
                                    .Elem("privatekey", ConfigBL.GetInstanceSettingsValue("storage/s3/privatekey", null))
                                    .Elem("bucket", ConfigBL.GetInstanceSettingsValue("storage/s3/bucket", null))
                                    .Elem("prefix", string.Format(PhpUtil.ConvertToFormatString(ConfigBL.GetInstanceSettingsValue("storage/s3/prefix", string.Empty)), DekiContext.Current.Instance.Id))
                                    .Elem("timeout", ConfigBL.GetInstanceSettingsValue("storage/s3/timeout", null))
                                    .Elem("allowredirects", ConfigBL.GetInstanceSettingsValue("storage/s3/allowredirects", null))
                                    .Elem("redirecttimeout", ConfigBL.GetInstanceSettingsValue("storage/s3/redirecttimeout", null));
                    _storage = new S3Storage(storageConfig, _loggerRepository.Get <S3Storage>());
                    break;

                default:
                    throw new ArgumentException("Storage provider unknown or not defined (key: storage/type)", "storage/type");
                }

                HomePageId = DbUtils.CurrentSession.Pages_HomePageId;
                _eventSink.InstanceStarting(DekiContext.Current.Now);
            } catch {
                // we failed to initialize
                _status = DekiInstanceStatus.ABANDONED;
                throw;
            }

            // set state to initializing
            _status = DekiInstanceStatus.INITIALIZING;
        }