Ejemplo n.º 1
0
        public IActionResult Index()
        {
            var config = new WebAppConfiguration
            {
                ApiUrl = _configuration["ApiUrl"],
                PolicyDocumentsPath = _configuration["PolicyDocumentsPath"],
                ApimSubscriptionKey = _configuration["ApimSubscriptionKey"]
            };

            return(View(config));
        }
 static public WebHostAdminInterfaceTestSetup Setup(
     WebAppConfiguration deployAppConfigAndInitElmState,
     Func <DateTimeOffset> persistentProcessHostDateTime = null,
     string adminRootPassword = null) =>
 Setup(
     persistentProcessHostDateTime: persistentProcessHostDateTime,
     adminRootPassword: adminRootPassword,
     deployAppConfigAndInitElmState:
     deployAppConfigAndInitElmState == null
             ?
     null
             :
     Composition.FromTree(Composition.TreeFromSetOfBlobsWithStringPath(deployAppConfigAndInitElmState.AsFiles())));
Ejemplo n.º 3
0
        static public WebHostTestSetup Setup(
            WebAppConfiguration webAppConfig,
            Func <IWebHostBuilder, IWebHostBuilder> webHostBuilderMap)
        {
            var testDirectory = Filesystem.CreateRandomDirectoryInTempDirectory();

            var setup = new WebHostTestSetup(testDirectory, webHostBuilderMap);

            var webAppConfigFilePath = setup.WebAppConfigFilePath;

            Directory.CreateDirectory(Path.GetDirectoryName(webAppConfigFilePath));

            File.WriteAllBytes(webAppConfigFilePath, ZipArchive.ZipArchiveFromEntries(webAppConfig.AsFiles()));

            return(setup);
        }
Ejemplo n.º 4
0
        public void UpdateLauncher(string launcher, string argumentPattern, string profile)
        {
            var configuration = WebAppConfigurationRepository.GetConfiguration(profile);

            if (configuration == null)
            {
                configuration = new WebAppConfiguration()
                {
                    Profile = profile,
                };
            }
            configuration.WebAppLauncher        = launcher;
            configuration.WebAppArgumentPattern = argumentPattern;

            WebAppConfigurationRepository.SaveConfiguration(configuration);
        }
Ejemplo n.º 5
0
        public WebAppConfiguration GetOrCreateConfiguration(string profile)
        {
            var configuration = WebAppConfigurationRepository.GetConfiguration(profile);

            if (configuration == null)
            {
                configuration = new WebAppConfiguration()
                {
                    Profile               = profile,
                    WebAppLauncher        = "chrome.exe",
                    WebAppArgumentPattern = "--app=\"{0}\" --profile-directory=\"Default\""
                };
                WebAppConfigurationRepository.SaveConfiguration(configuration);
            }
            return(WebAppConfigurationRepository.GetConfiguration(profile));
        }
Ejemplo n.º 6
0
        public void Init()
        {
            DataAccessService.Init();
            WebAppItemRepository.Init();
            WebAppConfigurationRepository.Init();

            var configurations = WebAppConfigurationRepository.GetConfigurations();

            if (configurations == null)
            {
                var configuration = new WebAppConfiguration()
                {
                    Profile               = "default",
                    WebAppLauncher        = "chrome.exe",
                    WebAppArgumentPattern = "--app=\"{0}\" --profile-directory=\"Default\""
                };
                WebAppConfigurationRepository.SaveConfiguration(configuration);
            }
        }
        public void SaveConfiguration(WebAppConfiguration configuration)
        {
            var affectedRows = DataAccessService
                               .GetQuery("update configuration set launcher=@launcher, pattern=@pattern where profile=@profile")
                               .WithParameter("profile", configuration.Profile)
                               .WithParameter("launcher", configuration.WebAppLauncher)
                               .WithParameter("pattern", configuration.WebAppArgumentPattern)
                               .Execute();

            if (affectedRows == 0)
            {
                DataAccessService
                .GetQuery("insert into configuration (profile, launcher, pattern) values (@profile, @launcher, @pattern)")
                .WithParameter("profile", configuration.Profile)
                .WithParameter("launcher", configuration.WebAppLauncher)
                .WithParameter("pattern", configuration.WebAppArgumentPattern)
                .Execute();
            }
        }
Ejemplo n.º 8
0
        static async Task StaticFilesMiddlewareFromWebAppConfig(
            WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next)
        {
            var matchingUrlMapToStaticFile =
                webAppConfig?.JsonStructure?.mapsFromRequestUrlToStaticFileName
                ?.FirstOrDefault(conditionalMap =>
                                 Regex.IsMatch(context.Request.GetDisplayUrl(), conditionalMap.matchingRegexPattern));

            if (matchingUrlMapToStaticFile != null)
            {
                if (!string.Equals("get", context.Request.Method, StringComparison.InvariantCultureIgnoreCase))
                {
                    context.Response.StatusCode = 405;
                    await context.Response.WriteAsync("This resource only supports the GET method.");

                    return;
                }

                var staticFilePath = matchingUrlMapToStaticFile.resultString.Split('/');

                var staticFile =
                    webAppConfig?.StaticFiles
                    ?.FirstOrDefault(staticFileNameAndContent =>
                                     staticFilePath.SequenceEqual(staticFileNameAndContent.staticFilePath));

                if (staticFile?.staticFileContent == null)
                {
                    context.Response.StatusCode = 404;
                    return;
                }

                context.Response.StatusCode = 200;
                await context.Response.Body.WriteAsync(staticFile?.staticFileContent.ToArray(), 0, staticFile.Value.staticFileContent.Count);

                return;
            }

            await next?.Invoke();
        }
Ejemplo n.º 9
0
        private static void BuildApp(IAppBuilder app, string publicDirectoryPath)
        {
            var container = IoCConfiguration.CreateIoCContainer();

            var httpConfig = new HttpConfiguration
            {
                DependencyResolver = container.Resolve <System.Web.Http.Dependencies.IDependencyResolver>()
            };

            WebAppConfiguration.ConfigureFileSystem(app, publicDirectoryPath);

            WebAppConfiguration.ConfigureAuthentication(app);

            WebAppConfiguration.ConfigureWebApi(app, httpConfig);

            WebAppConfiguration.ConfigureFilters(httpConfig, container);

            app.UseAutofacMiddleware(container);

            // must be at the end
            app.UseWebApi(httpConfig);
        }
Ejemplo n.º 10
0
        static async Task RateLimitMiddlewareFromWebAppConfig(
            WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next)
        {
            string ClientId()
            {
                const string defaultClientId = "MapToIPv4-failed";

                try
                {
                    return(context.Connection.RemoteIpAddress?.MapToIPv4().ToString() ?? defaultClientId);
                }
                catch
                {
                    return(defaultClientId);
                }
            }

            var rateLimitFromClientId =
                context.RequestServices.GetService <ClientsRateLimitStateContainer>().RateLimitFromClientId;

            var clientRateLimitState =
                rateLimitFromClientId.GetOrAdd(
                    ClientId(), _ => BuildRateLimitContainerForClient(webAppConfig?.JsonStructure));

            if (clientRateLimitState?.AttemptPass(Configuration.GetDateTimeOffset(context).ToUnixTimeMilliseconds()) ?? true)
            {
                await next?.Invoke();

                return;
            }

            context.Response.StatusCode = 429;
            await context.Response.WriteAsync("");

            return;
        }
Ejemplo n.º 11
0
        public void Configure(
            IApplicationBuilder app,
            IWebHostEnvironment env,
            WebAppConfiguration webAppConfig,
            ProcessStore.IProcessStoreWriter processStoreWriter,
            Func <DateTimeOffset> getDateTimeOffset)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            var nextHttpRequestIndex = 0;

            var            cyclicReductionStoreLock            = new object();
            DateTimeOffset?cyclicReductionStoreLastTime        = null;
            var            cyclicReductionStoreDistanceSeconds = (int)TimeSpan.FromHours(1).TotalSeconds;

            if (webAppConfig?.JsonStructure?.letsEncryptOptions != null)
            {
                app.UseFluffySpoonLetsEncryptChallengeApprovalMiddleware();
            }

            var createVolatileHostAttempts = 0;

            var volatileHosts = new ConcurrentDictionary <string, CSharpScriptContext>();

            InterfaceToHost.Result <InterfaceToHost.TaskResult.RunInVolatileHostError, InterfaceToHost.TaskResult.RunInVolatileHostComplete>
            performProcessTaskRunInVolatileHost(
                InterfaceToHost.Task.RunInVolatileHost runInVolatileHost)
            {
                if (!volatileHosts.TryGetValue(runInVolatileHost.hostId, out var volatileHost))
                {
                    return(new InterfaceToHost.Result <InterfaceToHost.TaskResult.RunInVolatileHostError, InterfaceToHost.TaskResult.RunInVolatileHostComplete>
                    {
                        err = new InterfaceToHost.TaskResult.RunInVolatileHostError
                        {
                            hostNotFound = new object(),
                        }
                    });
                }

                var stopwatch = System.Diagnostics.Stopwatch.StartNew();

                var fromVolatileHostResult = volatileHost.RunScript(runInVolatileHost.script);

                stopwatch.Stop();

                return(new InterfaceToHost.Result <InterfaceToHost.TaskResult.RunInVolatileHostError, InterfaceToHost.TaskResult.RunInVolatileHostComplete>
                {
                    ok = new InterfaceToHost.TaskResult.RunInVolatileHostComplete
                    {
                        exceptionToString = fromVolatileHostResult.Exception?.ToString(),
                        returnValueToString = fromVolatileHostResult.ReturnValue?.ToString(),
                        durationInMilliseconds = stopwatch.ElapsedMilliseconds,
                    }
                });
            }

            InterfaceToHost.TaskResult performProcessTask(InterfaceToHost.Task task)
            {
                if (task?.createVolatileHost != null)
                {
                    var volatileHostId = System.Threading.Interlocked.Increment(ref createVolatileHostAttempts).ToString();

                    volatileHosts[volatileHostId] = new CSharpScriptContext(BlobLibrary.GetBlobWithSHA256);

                    return(new InterfaceToHost.TaskResult
                    {
                        createVolatileHostResponse = new InterfaceToHost.Result <object, InterfaceToHost.TaskResult.CreateVolatileHostComplete>
                        {
                            ok = new InterfaceToHost.TaskResult.CreateVolatileHostComplete
                            {
                                hostId = volatileHostId,
                            },
                        },
                    });
                }

                if (task?.releaseVolatileHost != null)
                {
                    volatileHosts.TryRemove(task?.releaseVolatileHost.hostId, out var volatileHost);

                    return(new InterfaceToHost.TaskResult
                    {
                        completeWithoutResult = new object(),
                    });
                }

                if (task?.runInVolatileHost != null)
                {
                    return(new InterfaceToHost.TaskResult
                    {
                        runInVolatileHostResponse = performProcessTaskRunInVolatileHost(task?.runInVolatileHost),
                    });
                }

                throw new NotImplementedException("Unexpected task structure.");
            }

            void performProcessTaskAndFeedbackEvent(InterfaceToHost.StartTask taskWithId)
            {
                var taskResult = performProcessTask(taskWithId.task);

                var interfaceEvent = new InterfaceToHost.Event
                {
                    taskComplete = new InterfaceToHost.ResultFromTaskWithId
                    {
                        taskId     = taskWithId.taskId,
                        taskResult = taskResult,
                    }
                };

                processEventAndResultingRequests(interfaceEvent);
            }

            var processRequestCompleteHttpResponse = new ConcurrentDictionary <string, InterfaceToHost.HttpResponse>();

            var persistentProcess = app.ApplicationServices.GetService <IPersistentProcess>();

            void processEventAndResultingRequests(InterfaceToHost.Event interfaceEvent)
            {
                lock (processStoreWriter)
                {
                    var serializedInterfaceEvent = Newtonsoft.Json.JsonConvert.SerializeObject(interfaceEvent, jsonSerializerSettings);

                    var(eventResponses, compositionRecord) = persistentProcess.ProcessEvents(new[] { serializedInterfaceEvent });

                    processStoreWriter.AppendSerializedCompositionRecord(compositionRecord.serializedCompositionRecord);

                    var serializedResponse = eventResponses.Single();

                    InterfaceToHost.ResponseOverSerialInterface structuredResponse = null;

                    try
                    {
                        structuredResponse =
                            Newtonsoft.Json.JsonConvert.DeserializeObject <InterfaceToHost.ResponseOverSerialInterface>(
                                serializedResponse);
                    }
                    catch (Exception parseException)
                    {
                        throw new Exception(
                                  "Failed to parse event response from app. Looks like the loaded elm app is not compatible with the interface.\nResponse from app follows:\n" + serializedResponse,
                                  parseException);
                    }

                    if (structuredResponse?.decodeEventSuccess == null)
                    {
                        throw new Exception("Hosted app failed to decode the event: " + structuredResponse.decodeEventError);
                    }

                    foreach (var requestFromProcess in structuredResponse.decodeEventSuccess)
                    {
                        if (requestFromProcess.completeHttpResponse != null)
                        {
                            processRequestCompleteHttpResponse[requestFromProcess.completeHttpResponse.httpRequestId] =
                                requestFromProcess.completeHttpResponse.response;
                        }

                        if (requestFromProcess.startTask != null)
                        {
                            System.Threading.Tasks.Task.Run(() => performProcessTaskAndFeedbackEvent(requestFromProcess.startTask));
                        }
                    }
                }
            }

            app
            .Use(async(context, next) => await Asp.MiddlewareFromWebAppConfig(webAppConfig, context, next))
            .Run(async(context) =>
            {
                var currentDateTime  = getDateTimeOffset();
                var timeMilli        = currentDateTime.ToUnixTimeMilliseconds();
                var httpRequestIndex = System.Threading.Interlocked.Increment(ref nextHttpRequestIndex);

                var httpRequestId = timeMilli.ToString() + "-" + httpRequestIndex.ToString();

                {
                    var httpEvent = AsPersistentProcessInterfaceHttpRequestEvent(context, httpRequestId, currentDateTime);

                    var httpRequestInterfaceEvent = new InterfaceToHost.Event
                    {
                        httpRequest = httpEvent,
                    };

                    processEventAndResultingRequests(httpRequestInterfaceEvent);
                }

                var waitForHttpResponseClock = System.Diagnostics.Stopwatch.StartNew();

                while (true)
                {
                    if (processRequestCompleteHttpResponse.TryRemove(httpRequestId, out var httpResponse))
                    {
                        var headerContentType =
                            httpResponse.headersToAdd
                            ?.FirstOrDefault(header => header.name?.ToLowerInvariant() == "content-type")
                            ?.values?.FirstOrDefault();

                        context.Response.StatusCode = httpResponse.statusCode;

                        foreach (var headerToAdd in (httpResponse.headersToAdd).EmptyIfNull())
                        {
                            context.Response.Headers[headerToAdd.name] =
                                new Microsoft.Extensions.Primitives.StringValues(headerToAdd.values);
                        }

                        if (headerContentType != null)
                        {
                            context.Response.ContentType = headerContentType;
                        }

                        await context.Response.WriteAsync(httpResponse?.bodyAsString ?? "");
                        break;
                    }

                    if (60 <= waitForHttpResponseClock.Elapsed.TotalSeconds)
                    {
                        throw new TimeoutException(
                            "Persistent process did not return a HTTP response within " +
                            (int)waitForHttpResponseClock.Elapsed.TotalSeconds +
                            " seconds.");
                    }

                    System.Threading.Thread.Sleep(100);
                }

                System.Threading.Thread.MemoryBarrier();
                var cyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime;

                if (!(cyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds))
                {
                    if (System.Threading.Monitor.TryEnter(cyclicReductionStoreLock))
                    {
                        try
                        {
                            var afterLockCyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime;

                            if (afterLockCyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds)
                            {
                                return;
                            }

                            var reductionRecord = persistentProcess.ReductionRecordForCurrentState();

                            lock (processStoreWriter)
                            {
                                processStoreWriter.StoreReduction(reductionRecord);
                            }

                            cyclicReductionStoreLastTime = currentDateTime;
                            System.Threading.Thread.MemoryBarrier();
                        }
                        finally
                        {
                            System.Threading.Monitor.Exit(cyclicReductionStoreLock);
                        }
                    }
                }
            });
        }
Ejemplo n.º 12
0
        public void Configure(
            IApplicationBuilder app,
            IWebHostEnvironment env,
            IHostApplicationLifetime appLifetime,
            Func <DateTimeOffset> getDateTimeOffset)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            var configuration = app.ApplicationServices.GetService <IConfiguration>();

            var rootPassword      = configuration.GetValue <string>(Configuration.AdminRootPasswordSettingKey);
            var publicWebHostUrls = configuration.GetValue <string>(Configuration.PublicWebHostUrlsSettingKey)?.Split(new[] { ',', ';' });

            var processStoreFileStore = app.ApplicationServices.GetService <FileStoreForProcessStore>().fileStore;

            object publicAppLock = new object();

            PublicHostConfiguration publicAppHost = null;

            void stopPublicApp()
            {
                lock (publicAppLock)
                {
                    if (publicAppHost != null)
                    {
                        publicAppHost?.webHost?.StopAsync(TimeSpan.FromSeconds(10)).Wait();
                        publicAppHost?.webHost?.Dispose();
                        publicAppHost?.processVolatileRepresentation?.Dispose();
                        publicAppHost = null;
                    }
                }
            }

            appLifetime.ApplicationStopping.Register(() =>
            {
                stopPublicApp();
            });

            var processStoreWriter =
                new ProcessStoreSupportingMigrations.ProcessStoreWriterInFileStore(processStoreFileStore);

            void startPublicApp()
            {
                lock (publicAppLock)
                {
                    stopPublicApp();

                    var newPublicAppConfig = new PublicHostConfiguration {
                    };

                    logger.LogInformation("Begin to build the process volatile representation.");

                    var processVolatileRepresentation =
                        PersistentProcess.PersistentProcessVolatileRepresentation.Restore(
                            new ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore(processStoreFileStore),
                            logger: logEntry => logger.LogInformation(logEntry));

                    logger.LogInformation("Completed building the process volatile representation.");

                    var            cyclicReductionStoreLock            = new object();
                    DateTimeOffset?cyclicReductionStoreLastTime        = null;
                    var            cyclicReductionStoreDistanceSeconds = (int)TimeSpan.FromMinutes(10).TotalSeconds;

                    void maintainStoreReductions()
                    {
                        var currentDateTime = getDateTimeOffset();

                        System.Threading.Thread.MemoryBarrier();
                        var cyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime;

                        if (!(cyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds))
                        {
                            if (System.Threading.Monitor.TryEnter(cyclicReductionStoreLock))
                            {
                                try
                                {
                                    var afterLockCyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime;

                                    if (afterLockCyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds)
                                    {
                                        return;
                                    }

                                    lock (processStoreFileStore)
                                    {
                                        var reductionRecord = processVolatileRepresentation.StoreReductionRecordForCurrentState(processStoreWriter);
                                    }

                                    cyclicReductionStoreLastTime = currentDateTime;
                                    System.Threading.Thread.MemoryBarrier();
                                }
                                finally
                                {
                                    System.Threading.Monitor.Exit(cyclicReductionStoreLock);
                                }
                            }
                        }
                    }

                    var webHost =
                        processVolatileRepresentation?.lastAppConfig?.appConfigComponent == null
                        ?
                        null
                        :
                        buildWebHost();

                    IWebHost buildWebHost()
                    {
                        var appConfigTree = Composition.ParseAsTree(
                            processVolatileRepresentation.lastAppConfig.Value.appConfigComponent).Ok;

                        var appConfigFilesNamesAndContents =
                            appConfigTree.EnumerateBlobsTransitive()
                            .Select(blobPathAndContent => (
                                        fileName: (IImmutableList <string>)blobPathAndContent.path.Select(name => System.Text.Encoding.UTF8.GetString(name.ToArray())).ToImmutableList(),
                                        fileContent: blobPathAndContent.blobContent))
                            .ToImmutableList();

                        return
                            (Microsoft.AspNetCore.WebHost.CreateDefaultBuilder()
                             .ConfigureLogging((hostingContext, logging) =>
                        {
                            logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                            logging.AddConsole();
                            logging.AddDebug();
                        })
                             .ConfigureKestrel(kestrelOptions =>
                        {
                            kestrelOptions.ConfigureHttpsDefaults(httpsOptions =>
                            {
                                httpsOptions.ServerCertificateSelector = (c, s) => FluffySpoon.AspNet.LetsEncrypt.LetsEncryptRenewalService.Certificate;
                            });
                        })
                             .UseUrls(publicWebHostUrls ?? PublicWebHostUrlsDefault)
                             .UseStartup <StartupPublicApp>()
                             .WithSettingDateTimeOffsetDelegate(getDateTimeOffset)
                             .ConfigureServices(services =>
                        {
                            services.AddSingleton <WebAppAndElmAppConfig>(
                                new WebAppAndElmAppConfig
                            {
                                WebAppConfiguration = WebAppConfiguration.FromFiles(appConfigFilesNamesAndContents),
                                ProcessEventInElmApp = serializedEvent =>
                                {
                                    lock (processStoreWriter)
                                    {
                                        lock (publicAppLock)
                                        {
                                            var elmEventResponse =
                                                processVolatileRepresentation.ProcessElmAppEvent(
                                                    processStoreWriter, serializedEvent);

                                            maintainStoreReductions();

                                            return elmEventResponse;
                                        }
                                    }
                                }
                            });
                        })
                             .Build());
                    }

                    newPublicAppConfig.processVolatileRepresentation = processVolatileRepresentation;
                    newPublicAppConfig.webHost = webHost;

                    webHost?.StartAsync(appLifetime.ApplicationStopping).Wait();
                    publicAppHost = newPublicAppConfig;
                }
            }

            startPublicApp();

            app.Run(async(context) =>
            {
                var syncIOFeature = context.Features.Get <Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature>();
                if (syncIOFeature != null)
                {
                    syncIOFeature.AllowSynchronousIO = true;
                }

                {
                    var expectedAuthorization = Configuration.BasicAuthenticationForAdminRoot(rootPassword);

                    context.Request.Headers.TryGetValue("Authorization", out var requestAuthorizationHeaderValue);

                    AuthenticationHeaderValue.TryParse(
                        requestAuthorizationHeaderValue.FirstOrDefault(), out var requestAuthorization);

                    if (!(0 < rootPassword?.Length))
                    {
                        context.Response.StatusCode = 403;
                        await context.Response.WriteAsync("Forbidden");
                        return;
                    }

                    var buffer = new byte[400];

                    var decodedRequestAuthorizationParameter =
                        Convert.TryFromBase64String(requestAuthorization?.Parameter ?? "", buffer, out var bytesWritten) ?
                        Encoding.UTF8.GetString(buffer, 0, bytesWritten) : null;

                    if (!(string.Equals(expectedAuthorization, decodedRequestAuthorizationParameter) &&
                          string.Equals("basic", requestAuthorization?.Scheme, StringComparison.OrdinalIgnoreCase)))
                    {
                        context.Response.StatusCode = 401;
                        context.Response.Headers.Add(
                            "WWW-Authenticate",
                            @"Basic realm=""" + context.Request.Host + @""", charset=""UTF-8""");
                        await context.Response.WriteAsync("Unauthorized");
                        return;
                    }
                }

                var requestPathIsDeployAppConfigAndInitElmAppState =
                    context.Request.Path.Equals(new PathString(PathApiDeployAppConfigAndInitElmAppState));

                if (context.Request.Path.Equals(new PathString(PathApiGetDeployedAppConfig)))
                {
                    if (!string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase))
                    {
                        context.Response.StatusCode = 405;
                        await context.Response.WriteAsync("Method not supported.");
                        return;
                    }

                    var appConfig = publicAppHost?.processVolatileRepresentation?.lastAppConfig?.appConfigComponent;

                    if (appConfig == null)
                    {
                        context.Response.StatusCode = 404;
                        await context.Response.WriteAsync("I did not find an app config in the history. Looks like no app was deployed so far.");
                        return;
                    }

                    var appConfigHashBase16 = CommonConversion.StringBase16FromByteArray(Composition.GetHash(appConfig));

                    var appConfigTree = Composition.ParseAsTree(appConfig).Ok;

                    var appConfigFilesNamesAndContents =
                        appConfigTree.EnumerateBlobsTransitive()
                        .Select(blobPathAndContent => (
                                    fileName: (IImmutableList <string>)blobPathAndContent.path.Select(name => Encoding.UTF8.GetString(name.ToArray())).ToImmutableList(),
                                    fileContent: blobPathAndContent.blobContent))
                        .ToImmutableList();

                    var appConfigZipArchive =
                        ZipArchive.ZipArchiveFromEntries(
                            ElmApp.ToFlatDictionaryWithPathComparer(appConfigFilesNamesAndContents));

                    context.Response.StatusCode            = 200;
                    context.Response.Headers.ContentLength = appConfigZipArchive.LongLength;
                    context.Response.Headers.Add("Content-Disposition", new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = appConfigHashBase16 + ".zip"
                    }.ToString());
                    context.Response.Headers.Add("Content-Type", new MediaTypeHeaderValue("application/zip").ToString());

                    await context.Response.Body.WriteAsync(appConfigZipArchive);
                    return;
                }

                if (requestPathIsDeployAppConfigAndInitElmAppState ||
                    context.Request.Path.Equals(new PathString(PathApiDeployAppConfigAndMigrateElmAppState)))
                {
                    if (!string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase))
                    {
                        context.Response.StatusCode = 405;
                        await context.Response.WriteAsync("Method not supported.");
                        return;
                    }

                    var memoryStream = new MemoryStream();
                    context.Request.Body.CopyTo(memoryStream);

                    var webAppConfigZipArchive = memoryStream.ToArray();

                    {
                        try
                        {
                            var filesFromZipArchive = ZipArchive.EntriesFromZipArchive(webAppConfigZipArchive).ToImmutableList();

                            if (filesFromZipArchive.Count < 1)
                            {
                                throw new Exception("Contains no files.");
                            }
                        }
                        catch (Exception e)
                        {
                            context.Response.StatusCode = 400;
                            await context.Response.WriteAsync("Malformed web app config zip-archive:\n" + e);
                            return;
                        }
                    }

                    var appConfigTree =
                        Composition.TreeFromSetOfBlobsWithCommonFilePath(
                            ZipArchive.EntriesFromZipArchive(webAppConfigZipArchive));

                    var appConfigComponent = Composition.FromTree(appConfigTree);

                    processStoreWriter.StoreComponent(appConfigComponent);

                    var appConfigValueInFile =
                        new ProcessStoreSupportingMigrations.ValueInFileStructure
                    {
                        HashBase16 = CommonConversion.StringBase16FromByteArray(Composition.GetHash(appConfigComponent))
                    };

                    var compositionLogEvent =
                        requestPathIsDeployAppConfigAndInitElmAppState
                            ?
                        new ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent
                    {
                        DeployAppConfigAndInitElmAppState = appConfigValueInFile,
                    }
                            :
                    new ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent
                    {
                        DeployAppConfigAndMigrateElmAppState = appConfigValueInFile,
                    };

                    await attemptContinueWithCompositionEventAndSendHttpResponse(compositionLogEvent);
                    return;
                }

                if (context.Request.Path.StartsWithSegments(new PathString(PathApiRevertProcessTo),
                                                            out var revertToRemainingPath))
                {
                    if (!string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase))
                    {
                        context.Response.StatusCode = 405;
                        await context.Response.WriteAsync("Method not supported.");
                        return;
                    }

                    var processVersionId = revertToRemainingPath.ToString().Trim('/');

                    var processVersionComponent =
                        new ProcessStoreReaderInFileStore(processStoreFileStore).LoadComponent(processVersionId);

                    if (processVersionComponent == null)
                    {
                        context.Response.StatusCode = 404;
                        await context.Response.WriteAsync("Did not find process version '" + processVersionId + "'.");
                        return;
                    }

                    await attemptContinueWithCompositionEventAndSendHttpResponse(new CompositionLogRecordInFile.CompositionEvent
                    {
                        RevertProcessTo = new ValueInFileStructure {
                            HashBase16 = processVersionId
                        },
                    });
                    return;
                }

                if (context.Request.Path.Equals(new PathString(PathApiElmAppState)))
                {
                    if (publicAppHost == null)
                    {
                        context.Response.StatusCode = 400;
                        await context.Response.WriteAsync("Not possible because there is no app (state).");
                        return;
                    }

                    if (string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase))
                    {
                        var processVolatileRepresentation = publicAppHost?.processVolatileRepresentation;

                        var components = new List <Composition.Component>();

                        var storeWriter = new DelegatingProcessStoreWriter
                        {
                            StoreComponentDelegate              = components.Add,
                            StoreProvisionalReductionDelegate   = _ => { },
                            SetCompositionLogHeadRecordDelegate = _ => throw new Exception("Unexpected use of interface."),
                        };

                        var reductionRecord =
                            processVolatileRepresentation?.StoreReductionRecordForCurrentState(storeWriter);

                        if (reductionRecord == null)
                        {
                            context.Response.StatusCode = 500;
                            await context.Response.WriteAsync("Not possible because there is no Elm app deployed at the moment.");
                            return;
                        }

                        var elmAppStateReductionHashBase16 = reductionRecord.elmAppState?.HashBase16;

                        var elmAppStateReductionComponent =
                            components.First(c => CommonConversion.StringBase16FromByteArray(Composition.GetHash(c)) == elmAppStateReductionHashBase16);

                        var elmAppStateReductionString =
                            Encoding.UTF8.GetString(elmAppStateReductionComponent.BlobContent.ToArray());

                        context.Response.StatusCode  = 200;
                        context.Response.ContentType = "application/json";
                        await context.Response.WriteAsync(elmAppStateReductionString);
                        return;
                    }
                    else
                    {
                        if (string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase))
                        {
                            var elmAppStateToSet = new StreamReader(context.Request.Body, System.Text.Encoding.UTF8).ReadToEndAsync().Result;

                            var elmAppStateComponent = Composition.Component.Blob(Encoding.UTF8.GetBytes(elmAppStateToSet));

                            var appConfigValueInFile =
                                new ProcessStoreSupportingMigrations.ValueInFileStructure
                            {
                                HashBase16 = CommonConversion.StringBase16FromByteArray(Composition.GetHash(elmAppStateComponent))
                            };

                            processStoreWriter.StoreComponent(elmAppStateComponent);

                            await attemptContinueWithCompositionEventAndSendHttpResponse(
                                new ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent
                            {
                                SetElmAppState = appConfigValueInFile
                            });
                            return;
                        }
                        else
                        {
                            context.Response.StatusCode = 405;
                            await context.Response.WriteAsync("Method not supported.");
                            return;
                        }
                    }
                }


                if (context.Request.Path.Equals(new PathString(PathApiReplaceProcessHistory)))
                {
                    var memoryStream = new MemoryStream();
                    context.Request.Body.CopyTo(memoryStream);

                    var webAppConfigZipArchive = memoryStream.ToArray();

                    var replacementFiles =
                        ZipArchive.EntriesFromZipArchive(webAppConfigZipArchive)
                        .Select(filePathAndContent =>
                                (path: filePathAndContent.name.Split(new[] { '/', '\\' }).ToImmutableList()
                                 , content: filePathAndContent.content))
                        .ToImmutableList();

                    lock (publicAppLock)
                    {
                        lock (processStoreFileStore)
                        {
                            stopPublicApp();

                            foreach (var filePath in processStoreFileStore.ListFilesInDirectory(ImmutableList <string> .Empty).ToImmutableList())
                            {
                                processStoreFileStore.DeleteFile(filePath);
                            }

                            foreach (var replacementFile in replacementFiles)
                            {
                                processStoreFileStore.SetFileContent(replacementFile.path, replacementFile.content);
                            }

                            startPublicApp();
                        }
                    }

                    context.Response.StatusCode = 200;
                    await context.Response.WriteAsync("Successfully replaced the process history.");
                    return;
                }

                if (context.Request.Path.StartsWithSegments(
                        new PathString(PathApiProcessHistoryFileStoreGetFileContent), out var remainingPathString))
                {
                    if (!string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase))
                    {
                        context.Response.StatusCode = 405;
                        await context.Response.WriteAsync("Method not supported.");
                        return;
                    }

                    var filePathInStore =
                        remainingPathString.ToString().Trim('/').Split('/').ToImmutableList();

                    var fileContent = processStoreFileStore.GetFileContent(filePathInStore);

                    if (fileContent == null)
                    {
                        context.Response.StatusCode = 404;
                        await context.Response.WriteAsync("No file at '" + string.Join("/", filePathInStore) + "'.");
                        return;
                    }

                    context.Response.StatusCode  = 200;
                    context.Response.ContentType = "application/octet-stream";
                    await context.Response.Body.WriteAsync(fileContent);
                    return;
                }

                (int statusCode, string responseBodyString)attemptContinueWithCompositionEvent(
                    ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent compositionLogEvent)
                {
                    lock (processStoreFileStore)
                    {
                        lock (publicAppLock)
                        {
                            publicAppHost?.processVolatileRepresentation?.StoreReductionRecordForCurrentState(processStoreWriter);

                            var(projectedFiles, projectedFileReader) = IProcessStoreReader.ProjectFileStoreReaderForAppendedCompositionLogEvent(
                                originalFileStore: processStoreFileStore,
                                compositionLogEvent: compositionLogEvent);

                            using (var projectedProcess =
                                       PersistentProcess.PersistentProcessVolatileRepresentation.Restore(
                                           new ProcessStoreReaderInFileStore(projectedFileReader),
                                           _ => { }))
                            {
                                if (compositionLogEvent.DeployAppConfigAndMigrateElmAppState != null ||
                                    compositionLogEvent.SetElmAppState != null)
                                {
                                    if (projectedProcess.lastSetElmAppStateResult?.Ok == null)
                                    {
                                        return(statusCode: 400, responseBodyString: "Failed to migrate Elm app state for this deployment: " + projectedProcess.lastSetElmAppStateResult?.Err);
                                    }
                                }
                            }

                            foreach (var projectedFilePathAndContent in projectedFiles)
                            {
                                processStoreFileStore.SetFileContent(
                                    projectedFilePathAndContent.filePath, projectedFilePathAndContent.fileContent);
                            }

                            startPublicApp();

                            return(statusCode: 200, responseBodyString: "Successfully deployed this configuration and started the web server.");
                        }
                    }
                }

                async System.Threading.Tasks.Task attemptContinueWithCompositionEventAndSendHttpResponse(
                    ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent compositionLogEvent)
                {
                    var(statusCode, responseBodyString) = attemptContinueWithCompositionEvent(compositionLogEvent);

                    context.Response.StatusCode = statusCode;
                    await context.Response.WriteAsync(responseBodyString);
                    return;
                }

                if (context.Request.Path.Equals(PathString.Empty) || context.Request.Path.Equals(new PathString("/")))
                {
                    context.Response.StatusCode = 200;
                    await context.Response.WriteAsync(
                        "Welcome to Elm-fullstack version " + Program.AppVersionId + ".\n" +
                        "To learn about this admin interface, see http://elm-fullstack.org/");
                    return;
                }

                context.Response.StatusCode = 404;
                await context.Response.WriteAsync("Not Found");
                return;
            });
        }
Ejemplo n.º 13
0
 static public async Task MiddlewareFromWebAppConfig(
     WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next) =>
 await StaticFilesMiddlewareFromWebAppConfig(webAppConfig, context,
                                             () => RateLimitMiddlewareFromWebAppConfig(webAppConfig, context,
                                                                                       () => AdminPersistentProcessMiddlewareFromWebAppConfig(webAppConfig, context, next)));
Ejemplo n.º 14
0
 static public WebHostTestSetup Setup(
     WebAppConfiguration webAppConfig,
     Func <DateTimeOffset> persistentProcessHostDateTime = null) =>
 Setup(
     webAppConfig,
     builder => builder.WithSettingDateTimeOffsetDelegate(persistentProcessHostDateTime ?? (() => DateTimeOffset.UtcNow)));
Ejemplo n.º 15
0
 public WebApiServer(WebAppConfiguration config)
     : base(config)
 {
     _config = config;
 }
Ejemplo n.º 16
0
        static async Task AdminPersistentProcessMiddlewareFromWebAppConfig(
            WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next)
        {
            if (context.Request.Path.StartsWithSegments(
                    new PathString(Configuration.AdminPath),
                    out var requestPathInAdmin))
            {
                var configuration = context.RequestServices.GetService <IConfiguration>();

                var rootPassword = configuration.GetValue <string>(Configuration.AdminRootPasswordSettingKey);

                var expectedAuthorization = Configuration.BasicAuthenticationForAdminRoot(rootPassword);

                context.Request.Headers.TryGetValue("Authorization", out var requestAuthorizationHeaderValue);

                AuthenticationHeaderValue.TryParse(
                    requestAuthorizationHeaderValue.FirstOrDefault(), out var requestAuthorization);

                if (!(0 < rootPassword?.Length))
                {
                    context.Response.StatusCode = 403;
                    await context.Response.WriteAsync("Forbidden");

                    return;
                }

                var buffer = new byte[400];

                var decodedRequestAuthorizationParameter =
                    Convert.TryFromBase64String(requestAuthorization?.Parameter ?? "", buffer, out var bytesWritten) ?
                    Encoding.UTF8.GetString(buffer, 0, bytesWritten) : null;

                if (!(string.Equals(expectedAuthorization, decodedRequestAuthorizationParameter) &&
                      string.Equals("basic", requestAuthorization?.Scheme, StringComparison.OrdinalIgnoreCase)))
                {
                    context.Response.StatusCode = 401;
                    context.Response.Headers.Add(
                        "WWW-Authenticate",
                        @"Basic realm=""" + context.Request.Host + Configuration.AdminPath + @""", charset=""UTF-8""");
                    await context.Response.WriteAsync("Unauthorized");

                    return;
                }

                if (string.Equals(requestPathInAdmin, Configuration.ApiPersistentProcessStatePath))
                {
                    if (string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase))
                    {
                        var stateToSet = new StreamReader(context.Request.Body, System.Text.Encoding.UTF8).ReadToEndAsync().Result;

                        var processStoreWriter = context.RequestServices.GetService <ProcessStore.IProcessStoreWriter>();

                        var persistentProcess = context.RequestServices.GetService <IPersistentProcess>();

                        var compositionRecord = persistentProcess.SetState(stateToSet);

                        processStoreWriter.AppendSerializedCompositionRecord(compositionRecord.serializedCompositionRecord);

                        context.Response.StatusCode = 200;
                        await context.Response.WriteAsync("Successfully set process state.");

                        return;
                    }

                    if (string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase))
                    {
                        var persistentProcess = context.RequestServices.GetService <IPersistentProcess>();

                        var reducedValueLiteralString =
                            persistentProcess.ReductionRecordForCurrentState().ReducedValueLiteralString;

                        context.Response.StatusCode = 200;
                        await context.Response.WriteAsync(reducedValueLiteralString);

                        return;
                    }

                    context.Response.StatusCode = 405;
                    await context.Response.WriteAsync("Method not supported.");

                    return;
                }

                context.Response.StatusCode = 404;
                await context.Response.WriteAsync("Not Found");

                return;
            }

            await next?.Invoke();
        }
Ejemplo n.º 17
0
        BuildConfigurationZipArchive(
            Action <string> verboseLogWriteLine)
        {
            var currentDirectory = Environment.CurrentDirectory;

            Console.WriteLine("The currentDirectory is '" + currentDirectory + "'.");

            var elmAppFilesBeforeLowering =
                ElmApp.ToFlatDictionaryWithPathComparer(
                    ElmApp.FilesFilteredForElmApp(
                        Filesystem.GetAllFilesFromDirectory(Path.Combine(currentDirectory, ElmAppSubdirectoryName)))
                    .Select(filePathAndContent =>
                            ((IImmutableList <string>)filePathAndContent.filePath.Split(new[] { '/', '\\' }).ToImmutableList(),
                             filePathAndContent.fileContent)));

            Console.WriteLine("I found " + elmAppFilesBeforeLowering.Count + " files to build the Elm app.");

            var elmAppContainsFrontend = elmAppFilesBeforeLowering.ContainsKey(FrontendElmAppRootFilePath);

            Console.WriteLine("This Elm app contains " + (elmAppContainsFrontend ? "a" : "no") + " frontend at '" + string.Join("/", FrontendElmAppRootFilePath) + "'.");

            var loweredElmAppFiles = ElmApp.AsCompletelyLoweredElmApp(
                elmAppFilesBeforeLowering,
                ElmAppInterfaceConfig.Default,
                verboseLogWriteLine);

            var compileConfigFile = new Func <byte[]>(() =>
            {
                WebAppConfigurationJsonStructure jsonStructure = null;

                var jsonFileSearchPath = Path.Combine(currentDirectory, "elm-fullstack.json");

                if (File.Exists(jsonFileSearchPath))
                {
                    Console.WriteLine("I found a file at '" + jsonFileSearchPath + "'. I use this to build the configuration.");

                    var jsonFile = File.ReadAllBytes(jsonFileSearchPath);

                    jsonStructure = JsonConvert.DeserializeObject <WebAppConfigurationJsonStructure>(Encoding.UTF8.GetString(jsonFile));
                }
                else
                {
                    Console.WriteLine("I did not find a file at '" + jsonFileSearchPath + "'. I build the configuration without the 'elm-fullstack.json'.");
                }

                byte[] frontendWebFile = null;

                if (elmAppContainsFrontend)
                {
                    var frontendWebHtml = ProcessFromElm019Code.CompileElmToHtml(
                        loweredElmAppFiles,
                        FrontendElmAppRootFilePath,
                        elmMakeCommandAppendix: jsonStructure?.frontendWebElmMakeCommandAppendix);

                    frontendWebFile = Encoding.UTF8.GetBytes(frontendWebHtml);
                }

                var staticFilesFromFrontendWeb =
                    frontendWebFile == null ?
                    Array.Empty <(IImmutableList <string> name, IImmutableList <byte> content)>() :
                    new[] { (name: (IImmutableList <string>)ImmutableList.Create(FrontendWebStaticFileName), (IImmutableList <byte>)frontendWebFile.ToImmutableList()) };

                var staticFilesSourceDirectory = Path.Combine(currentDirectory, StaticFilesSubdirectoryName);

                var staticFilesFromDirectory =
                    Directory.Exists(staticFilesSourceDirectory)
                    ?
                    Filesystem.GetAllFilesFromDirectory(staticFilesSourceDirectory)
                    .Select(nameAndContent => (name: (IImmutableList <string>)nameAndContent.name.Split(new[] { '/', '\\' }).ToImmutableList(), content: nameAndContent.content))
                    .ToImmutableList()
                    :
                    ImmutableList <(IImmutableList <string> name, IImmutableList <byte> content)> .Empty;

                Console.WriteLine("I found " + staticFilesFromDirectory.Count + " static files to include.");

                var staticFiles =
                    staticFilesFromDirectory.AddRange(staticFilesFromFrontendWeb);

                var webAppConfig =
                    new WebAppConfiguration()
                    .WithElmApp(loweredElmAppFiles)
                    .WithStaticFiles(staticFiles)
                    .WithJsonStructure(jsonStructure);

                var webAppConfigFiles =
                    webAppConfig.AsFiles();

                var webAppConfigFile = ZipArchive.ZipArchiveFromEntries(webAppConfigFiles);

                return(webAppConfigFile);
            });