Skip to content

Commit

Permalink
petrsvihlik#192 create WopiValidator sample and move all to sample/
Browse files Browse the repository at this point in the history
  • Loading branch information
Leon Segal committed Mar 6, 2025
1 parent c792420 commit c7f7e4b
Show file tree
Hide file tree
Showing 205 changed files with 90,175 additions and 46 deletions.
35 changes: 2 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,10 @@ If you need a version that's targeting an older version of .NET, check out the r

If you get errors saying that Microsoft.CobaltCore.*.nupkg can't be found, then just remove the reference or see the chapter [Cobalt](#Cobalt) below.

Configuration
Samples
-----------

### WopiHost
[WopiHost\appSettings.json](https://github.com/petrsvihlik/WopiHost/blob/master/WopiHost/appsettings.json)

| Parameter | Sample value | Description |
| :--- | :--- | :--- |
|`Wopi:StorageProviderAssemblyName`| [`"WopiHost.FileSystemProvider"`](https://github.com/petrsvihlik/WopiHost/tree/master/WopiHost.FileSystemProvider) | Name of assembly containing implementation of `WopiHost.Abstractions` interfaces |
|`Wopi:StorageProvider:RootPath` | [`".\\wopi-docs"`](https://github.com/petrsvihlik/WopiHost/tree/master/WopiHost/wwwroot/wopi-docs) | Provider-specific setting used by `WopiHost.FileSystemProvider` (which is an implementation of `IWopiStorageProvider` working with System.IO) |
|`Wopi:UseCobalt`| `true`| Whether or not to use [MS-FSSHTTP](https://learn.microsoft.com/openspecs/sharepoint_protocols/ms-fsshttp/) for file synchronization. More details at [Cobalt](#cobalt)|

### WopiHost.Web
[WopiHost.Web\appSettings.json](https://github.com/petrsvihlik/WopiHost/blob/master/WopiHost.Web/appsettings.json)

| Parameter | Sample value | Description |
| :--- | :--- | :--- |
| `Wopi:HostUrl` | `"http://wopihost:5000"` | URL pointing to a WopiHost instance (above). It's used by the URL generator. |
| `Wopi:ClientUrl` | ` "http://owaserver"` | Base URL of your WOPI client - typically, [Office Online Server](#compatible-wopi-clients) - used by the discovery module to load WOPI client URL templates |
| `Wopi:StorageProvider:RootPath` | [`"..\\..\\WopiHost\\wwwroot\\wopi-docs"`](https://github.com/petrsvihlik/WopiHost/tree/master/WopiHost/wwwroot/wopi-docs) | Provider-specific setting used by `WopiHost.FileSystemProvider` (which is an implementation of `IWopiStorageProvider` working with System.IO) |
| `Wopi:Discovery:NetZone` | `"InternalHttp"` | Determines the target zone configuration of your [OOS Deployment](https://learn.microsoft.com/officeonlineserver/deploy-office-online-server). Values correspond with the [`NetZoneEnum`](https://github.com/petrsvihlik/WopiHost/blob/master/WopiHost.Discovery/NetZoneEnum.cs). |


Additionally, you can use the [secret storage](https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-2.2&tabs=windows) to configure both of the apps.

Running the application
-----------------------
Once you've successfully built the app you can:

- run it directly from the Visual Studio using [IIS Express or self-hosted](/img/debug.png?raw=true).
- make sure you run both `WopiHost` and `WopiHost.Web`. You can set them both as [startup projects](/img/multiple_projects.png?raw=true)
- run it from the `cmd`
- navigate to the WopiHost folder and run `dotnet run`
- run it in IIS (tested in IIS 8.5)
- TODO
See [Samples](https://github.com/petrsvihlik/WopiHost/blob/master/sample/README.md) for all samples.

Compatible WOPI Clients
-------
Expand Down
39 changes: 37 additions & 2 deletions WOPI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B692B58D
test\Directory.Packages.props = test\Directory.Packages.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost", "src\WopiHost\WopiHost.csproj", "{21B81530-CC57-46F1-9524-08F984187DE2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost", "sample\WopiHost\WopiHost.csproj", "{21B81530-CC57-46F1-9524-08F984187DE2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost.Discovery", "src\WopiHost.Discovery\WopiHost.Discovery.csproj", "{84B896D2-1A87-4671-B583-92A6AF3645F0}"
EndProject
Expand All @@ -40,7 +40,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost.Url", "src\WopiHos
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost.FileSystemProvider.Tests", "test\WopiHost.FileSystemProvider.Tests\WopiHost.FileSystemProvider.Tests.csproj", "{650A5A85-5956-491E-9312-5E25A27D1108}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost.Web", "src\WopiHost.Web\WopiHost.Web.csproj", "{5C301182-6FB2-40C5-97ED-58B07A910BFF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost.Web", "sample\WopiHost.Web\WopiHost.Web.csproj", "{5C301182-6FB2-40C5-97ED-58B07A910BFF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WopiHost.Discovery.Tests", "test\WopiHost.Discovery.Tests\WopiHost.Discovery.Tests.csproj", "{4089410A-8A12-455C-862B-A4A3834C3100}"
EndProject
Expand All @@ -56,6 +56,28 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WopiHost.MemoryLockProvider
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WopiHost.MemoryLockProvider.Tests", "test\WopiHost.MemoryLockProvider.Tests\WopiHost.MemoryLockProvider.Tests.csproj", "{A863AD59-1075-C7DD-A3C9-B0859DF692EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WopiHost.Validator", "sample\WopiHost.Validator\WopiHost.Validator.csproj", "{97442113-B773-4F71-8B60-EF5599AC135D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
ProjectSection(SolutionItems) = preProject
sample\README.md = sample\README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wopi-docs", "wopi-docs", "{EA1A15F6-C3AF-4AAF-9A8B-4B7E053D6CEC}"
ProjectSection(SolutionItems) = preProject
sample\wopi-docs\test.docx = sample\wopi-docs\test.docx
sample\wopi-docs\test.html = sample\wopi-docs\test.html
sample\wopi-docs\test.pptx = sample\wopi-docs\test.pptx
sample\wopi-docs\test.wopitest = sample\wopi-docs\test.wopitest
sample\wopi-docs\test.xlsx = sample\wopi-docs\test.xlsx
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "subfolder", "subfolder", "{1CCBC8E4-5688-4F95-ADE8-31BF49064828}"
ProjectSection(SolutionItems) = preProject
sample\wopi-docs\subfolder\test2.docx = sample\wopi-docs\subfolder\test2.docx
sample\wopi-docs\subfolder\TextFile.txt = sample\wopi-docs\subfolder\TextFile.txt
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -176,17 +198,30 @@ Global
{A863AD59-1075-C7DD-A3C9-B0859DF692EE}.Release|Any CPU.Build.0 = Release|Any CPU
{A863AD59-1075-C7DD-A3C9-B0859DF692EE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A863AD59-1075-C7DD-A3C9-B0859DF692EE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Release|Any CPU.Build.0 = Release|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{97442113-B773-4F71-8B60-EF5599AC135D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F66706B6-D7DD-48FB-963C-870B3D611500} = {785E1533-48CE-4B5E-8C59-D6F1FDA8C45C}
{21B81530-CC57-46F1-9524-08F984187DE2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{650A5A85-5956-491E-9312-5E25A27D1108} = {B692B58D-1720-49A8-9CB8-5562894618F6}
{5C301182-6FB2-40C5-97ED-58B07A910BFF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{4089410A-8A12-455C-862B-A4A3834C3100} = {B692B58D-1720-49A8-9CB8-5562894618F6}
{9FFB3975-2DE6-4944-92C7-7062C39BB8FA} = {B692B58D-1720-49A8-9CB8-5562894618F6}
{9CBB4AD4-EAE4-4D85-A70C-ECC465DA3D86} = {B692B58D-1720-49A8-9CB8-5562894618F6}
{A863AD59-1075-C7DD-A3C9-B0859DF692EE} = {B692B58D-1720-49A8-9CB8-5562894618F6}
{97442113-B773-4F71-8B60-EF5599AC135D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{EA1A15F6-C3AF-4AAF-9A8B-4B7E053D6CEC} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{1CCBC8E4-5688-4F95-ADE8-31BF49064828} = {EA1A15F6-C3AF-4AAF-9A8B-4B7E053D6CEC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {91B6EE44-4505-4272-9D80-E4C73B09BF25}
Expand Down
69 changes: 69 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# ![Logo](../img/logo48.png) WopiHost Samples

Introduction
==========
This folder includes samples for hosting the WopiHost and the Host pages.

wopi-docs
---

Sample documents used by our Host pages - referenced by the FileSystemProvider as configured in the samples.

WopiHost and WopiHost.Web
---

Two companion samples - the WopiHost is the actual Wopi Server host and the WopiHost.Web provides a sample Host page to actually view/edit the sample documents in the Wopi client (as configured in WopiHost.Web/Wopi:ClientUrl).

#### Configuration: WopiHost

[WopiHost\appSettings.json](https://github.com/petrsvihlik/WopiHost/blob/master/sample/WopiHost/appsettings.json)

| Parameter | Sample value | Description |
| :--- | :--- | :--- |
|`Wopi:StorageProviderAssemblyName`| [`"WopiHost.FileSystemProvider"`](https://github.com/petrsvihlik/WopiHost/tree/master/WopiHost.FileSystemProvider) | Name of assembly containing implementation of the `IWopiStorageProvider` and `IWopiSecurityHandler` interfaces |
|`Wopi:StorageProvider:RootPath` | [`".\\wopi-docs"`](https://github.com/petrsvihlik/WopiHost/tree/master/sample/wopi-docs) | Provider-specific setting used by `WopiHost.FileSystemProvider` (which is an implementation of `IWopiStorageProvider` working with System.IO) |
|`Wopi:LockProviderAssemblyName`| [`"WopiHost.LockProvider"`](https://github.com/petrsvihlik/WopiHost/tree/master/WopiHost.MemoryLockProvider) | Name of assembly containing implementation of the `IWopiLockProvider` interface |
|`Wopi:UseCobalt`| `false`| Whether or not to use [MS-FSSHTTP](https://learn.microsoft.com/openspecs/sharepoint_protocols/ms-fsshttp/) for file synchronization. More details at [Cobalt](#cobalt)|

#### Configuration: WopiHost.Web
[WopiHost.Web\appSettings.json](https://github.com/petrsvihlik/WopiHost/blob/master/sample/WopiHost.Web/appsettings.json)

| Parameter | Sample value | Description |
| :--- | :--- | :--- |
| `Wopi:HostUrl` | `"http://wopihost:52788"` | URL pointing to a WopiHost instance (above). It's used by the URL generator. |
| `Wopi:ClientUrl` | ` "http://owaserver"` | Base URL of your WOPI client - typically, [Office Online Server](#compatible-wopi-clients) - used by the discovery module to load WOPI client URL templates |
| `Wopi:StorageProvider:RootPath` | [`".\\wopi-docs"`](https://github.com/petrsvihlik/WopiHost/tree/master/sample/wopi-docs) | Provider-specific setting used by `WopiHost.FileSystemProvider` (which is an implementation of `IWopiStorageProvider` working with System.IO) |
| `Wopi:Discovery:NetZone` | `"InternalHttp"` | Determines the target zone configuration of your [OOS Deployment](https://learn.microsoft.com/officeonlineserver/deploy-office-online-server). Values correspond with the [`NetZoneEnum`](https://github.com/petrsvihlik/WopiHost/blob/master/WopiHost.Discovery/NetZoneEnum.cs). |


Additionally, you can use the [secret storage](https://learn.microsoft.com/aspnet/core/security/app-secrets?view=aspnetcore-2.2&tabs=windows) to configure both of the apps.

WopiHost.Validator
---

Single hosting for both the Wopi Server and the Host page server by a single .NET HTTP server.
This has been preconfigured to handle custom Wopi Events and is used to validate the Wopi Server implementation using the WOPI-Validator.

#### Configuration: WopiHost.Validator

This is a combination of the WopiHost and WopiHost.Web configurations, with the additional `Wopi:UserId` (default: Anonymous) setting to specify the hard-coded token to be used by the WOPI-Validator.

#### Running the WOPI-Validator

After cloning and building the [WOPI-Validator repository](https://github.com/Microsoft/wopi-validator-core) use the following command to run the `Office Online` suite of validations:

```
dotnet run --project src\WopiValidator\WopiValidator.csproj --framework net8.0 -s -e OfficeOnline -w http://localhost:28752/wopi/files/Llx0ZXN0LndvcGl0ZXN0 -t Anonymous -l 0
```


Running the samples
---
Once you've successfully built the app you can:

- run it directly from the Visual Studio using [IIS Express or self-hosted](/img/debug.png?raw=true).
- make sure you run both `WopiHost` and `WopiHost.Web`. You can set them both as [startup projects](/img/multiple_projects.png?raw=true)
- run it from the `cmd`
- navigate to the WopiHost folder and run `dotnet run`
- run it in IIS (tested in IIS 8.5)
- TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.Extensions.Options;
using Serilog;
using Serilog.Events;
using WopiHost.Abstractions;
using WopiHost.Core.Extensions;
using WopiHost.Core.Models;
using WopiHost.Discovery;
using WopiHost.FileSystemProvider;
using WopiHost.Validator.Models;

namespace WopiHost.Validator.Infrastructure;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddWopiLogging(this IServiceCollection services)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Debug()
.CreateLogger();

services.AddSerilog(Log.Logger);
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
});
return services;
}
public static IServiceCollection AddWopiServer(this IServiceCollection services, IConfiguration configuration)
{
// ------ add Wopi Server .Core services

// Configuration
services.Configure<WopiHostOptions>(configuration.GetSection(WopiConfigurationSections.WOPI_ROOT));
// Add file provider
services.AddSingleton<IWopiStorageProvider, WopiFileSystemProvider>();
// Add lock provider
services.AddSingleton<IWopiLockProvider, MemoryLockProvider.MemoryLockProvider>();
services.AddSingleton<IWopiSecurityHandler, WopiSecurityHandler>();
// Add WOPI
services.AddWopi(o =>
{
o.OnCheckFileInfo = WopiEvents.OnGetWopiCheckFileInfo;
});
return services;
}

public static IServiceCollection AddWopiHostPages(this IServiceCollection services, IConfiguration configuration)
{
// ------- add Wopi Host services
services.Configure<DiscoveryOptions>(configuration.GetSection(WopiConfigurationSections.DISCOVERY_OPTIONS));
services.Configure<WopiOptions>(configuration.GetSection(WopiConfigurationSections.WOPI_ROOT));

services.AddHttpClient<IDiscoveryFileProvider, HttpDiscoveryFileProvider>((sp, client) =>
{
var wopiOptions = sp.GetRequiredService<IOptions<WopiOptions>>();
client.BaseAddress = wopiOptions.Value.ClientUrl;
});

services.AddSingleton<IDiscoverer, WopiDiscoverer>();
return services;
}

}
47 changes: 47 additions & 0 deletions sample/WopiHost.Validator/Infrastructure/WopiEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using WopiHost.Abstractions;
using WopiHost.Core.Models;

namespace WopiHost.Validator.Infrastructure;

public static class WopiEvents
{
/// <summary>
/// Custom handling of CheckFileInfo results for WOPI-Validator
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static Task<WopiCheckFileInfo> OnGetWopiCheckFileInfo(WopiCheckFileInfoContext context)
{
var wopiCheckFileInfo = context.CheckFileInfo;
wopiCheckFileInfo.AllowAdditionalMicrosoftServices = true;
wopiCheckFileInfo.AllowErrorReportPrompt = true;

// ##183 required for WOPI-Validator
if (wopiCheckFileInfo.BaseFileName == "test.wopitest")
{
wopiCheckFileInfo.CloseUrl = new("https://example.com/close");
wopiCheckFileInfo.DownloadUrl = new("https://example.com/download");
wopiCheckFileInfo.FileSharingUrl = new("https://example.com/share");
wopiCheckFileInfo.FileUrl = new("https://example.com/file");
wopiCheckFileInfo.FileVersionUrl = new("https://example.com/version");
wopiCheckFileInfo.HostEditUrl = new("https://example.com/edit");
wopiCheckFileInfo.HostEmbeddedViewUrl = new("https://example.com/embedded");
wopiCheckFileInfo.HostEmbeddedEditUrl = new("https://example.com/embeddededit");
wopiCheckFileInfo.HostRestUrl = new("https://example.com/rest");
wopiCheckFileInfo.HostViewUrl = new("https://example.com/view");
wopiCheckFileInfo.SignInUrl = new("https://example.com/signin");
wopiCheckFileInfo.SignoutUrl = new("https://example.com/signout");

wopiCheckFileInfo.ClientUrl = new("https://example.com/client");
wopiCheckFileInfo.FileEmbedCommandUrl = new("https://example.com/embed");

// https://learn.microsoft.com/microsoft-365/cloud-storage-partner-program/rest/files/checkfileinfo/checkfileinfo-other#breadcrumb-properties
wopiCheckFileInfo.BreadcrumbBrandName = "WopiHost";
wopiCheckFileInfo.BreadcrumbBrandUrl = new("https://example.com");
wopiCheckFileInfo.BreadcrumbDocName = "test";
wopiCheckFileInfo.BreadcrumbFolderName = "root";
wopiCheckFileInfo.BreadcrumbFolderUrl = new("https://example.com/folder");
}
return Task.FromResult(wopiCheckFileInfo);
}
}
Loading

0 comments on commit c7f7e4b

Please sign in to comment.