Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/192 attribute-based authnz and WOPI validator sample #193

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,67 @@
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