From 053f3993ad2ec4cd8adc21f8e73d63d51e46566d Mon Sep 17 00:00:00 2001 From: Tingting Wen Date: Tue, 19 Feb 2019 15:25:41 +0800 Subject: [PATCH 1/8] Add test cases to test [MS-WOPI] server implementation about file and content --- MS-WOPITestCases.xml | 2490 +++++++++++++++++ TestCases.xsd | 42 + src/WopiValidator.Core/ConfigParser.cs | 3 + src/WopiValidator.Core/Constants.cs | 10 + .../Factories/RequestFactory.cs | 19 +- .../Factories/ValidatorFactory.cs | 6 +- .../MS-WOPICheckFileInfoSchema.json | 408 +++ .../JsonSchemas/ReadSecureStoreSchema.json | 25 + .../Requests/GetRestrictedLinkRequest.cs | 36 + .../Requests/GetShareUrlRequest.cs | 10 +- .../Requests/ReadSecureStoreRequest.cs | 39 + .../Requests/RevokeRestrictedLinkRequest.cs | 36 + .../Requests/WopiRequestParam.cs | 4 + src/WopiValidator.Core/TestCaseExecutor.cs | 12 +- .../Validators/JsonContentValidator.cs | 13 +- .../Validators/ResponseHeaderValidator.cs | 20 +- .../WopiValidator.Core.csproj | 4 + src/WopiValidator/Options.cs | 6 + src/WopiValidator/Program.cs | 3 + src/WopiValidator/WopiValidator.csproj | 3 + 20 files changed, 3174 insertions(+), 15 deletions(-) create mode 100644 MS-WOPITestCases.xml create mode 100644 src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json create mode 100644 src/WopiValidator.Core/JsonSchemas/ReadSecureStoreSchema.json create mode 100644 src/WopiValidator.Core/Requests/GetRestrictedLinkRequest.cs create mode 100644 src/WopiValidator.Core/Requests/ReadSecureStoreRequest.cs create mode 100644 src/WopiValidator.Core/Requests/RevokeRestrictedLinkRequest.cs diff --git a/MS-WOPITestCases.xml b/MS-WOPITestCases.xml new file mode 100644 index 0000000..6f5cb66 --- /dev/null +++ b/MS-WOPITestCases.xml @@ -0,0 +1,2490 @@ + + + + + + + + + + + + + + + + + The prereq WOPI validation that must pass prior to running the (potentially destructive) test suite. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the user has permission to perform Write operation. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the user is denied permission to call PutRelativeFile. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if file can be changed by the user. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the user has permission to call PutRelativeFile. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host declares support for Lock/Unlock/RefreshLock/UnlockAndRefreshLock + operations. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if host declares support for PutFile/PutRelativeFile operations. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if host declares support for GetLock operation. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if host declares support for extended lock lengths. + + + + + + + + + + + + + + + The prereq BusinessFlowPrereq must pass prior to running the feature validations related to business flows. + + + + + + + + + + + + + + + The host uses FileUrl for direct file access. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host sets SupportedShareUrlTypes in CheckFileInfo to any value. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if host declares support for RenameFile operation. + + + + + + + + + + + + + + + The host must support /files/PutUserInfo. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host declares support for GetRestrictedLink/RevokeRestrictedLink + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host declares support for ReadSecureStore + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host declares support for EnumerateChildren and DeleteFile + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host declares support for the "ReadOnly" Share Url type for the file. + + + + + + + + + + + + + + + Prereq WOPI Validation test to check if the host declares support for the "ReadWrite" Share Url type for the file. + + + + + + + + + + + + + + + + WopiValidatorPrereq + + + + + This tests that hosts' CheckFileInfo responses conform to the JSON schema. + + + + + + + + + + + + + + + + + + + This tests that host returns 401 or 404 response for a CheckFileInfo request with invalid access token. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + UserCanWritePrereq + + + + + This tests that file version is changed when the file content changes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + + + + + This tests that get a file successfully. + + + + + + + + + + + + + + + + + + + + This tests that host returns 401 or 404 response for a GetFile request with invalid access token. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + UserCanWriteRelativePrereq + LocksPrereq + SupportsFoldersPrereq + + + + + Tests the basic PutRelativeFile scenario where a suggested extension is specified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests the basic PutRelativeFile scenario where a suggested name is specified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a suggested name is specified but + a file with the target name already exists. Expects the request to succeed with the host + choosing a suitable name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a Relative name is specified. Expects the created file to have + the exact name specified. + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a Relative name is specified and OverwriteRelative + is set to true. Since no file with target name exists in this scenario, the header should have + no effect. + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a Relative name is specified and OverwriteRelative + is set to false. Since no file with target name exists in this scenario, the header should have + no effect. + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a Relative name is specified and OverwriteRelative + is not specified. Since a file with target name exists in this scenario, this should return a 409. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a Relative name is specified and OverwriteRelative + is set to false. Since a file with target name exists in this scenario, this should return a 409. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a Relative name is specified and OverwriteRelative is set to true. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests the PutRelativeFile scenario where a relative name is specified along with OverwriteRelative + set to true, but a file with the same target name already exists and is locked. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests that file name is encoded correctly after a PutRelativeFile operation. + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + NotReadOnlyPrereq + UserCanWritePrereq + LocksPrereq + + + + + This tests that put a file successfully. + + + + + + + + + + + + + + + + + + + This tests that put a file with matching lock value successfully after lock. + + + + + + + + + + + + + + + + + + + This tests that put a file with matching lock value successfully after unlock and relock. + + + + + + + + + + + + + + + + + + + + This tests that host returns 401 or 404 response for a PutFile request with invalid access token. + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + LocksPrereq + UserCanWritePrereq + + + + + This tests that lock mismatch when PutFile with incorrect lock id. + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + LocksPrereq + + + + + Simulates a successful sequence of lock-related requests: Lock, RefreshLock, UnlockAndRelock, Unlock. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Two Lock calls in a row with the same Lock ID should work. + + + + + + + + + + + + + + Responses to Unlock requests on an unlocked file should include the X-WOPI-Lock header set to the empty string. + + + + + + + + + + + + + + + + + This tests that lock mismatch when trying to refresh the lock with incorrect lock id. + + + + + + + + + + + + + + + + + + This tests that lock mismatch when trying to unlock with incorrect lock id. + + + + + + + + + + + + + + + + + + + This tests that lock mismatch when trying to refresh lock with incorrect lock id. + + + + + + + + + + + + + + + + + + This tests that lock mismatch when trying to unlock and relock with incorrect lock id. + + + + + + + + + + + + + + + + + + + This tests that lock mismatch when unlock with old lock. + + + + + + + + + + + + + + + + + + + This tests that host returns 401 or 404 response for locks requests with invalid access token. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + LocksPrereq + + + + + This tests that X-WOPI-Lock header is returned when lock mismatch for Lock request. + + + + + + + + + + + + + + + + + + + This tests that X-WOPI-Lock header is returned when lock mismatch for Unlock request. + + + + + + + + + + + + + + + + + + This tests that X-WOPI-Lock header is returned when lock mismatch for RefreshLock request. + + + + + + + + + + + + + + + + + + This tests that X-WOPI-Lock header is returned when lock mismatch for UnlockAndRelock request. + + + + + + + + + + + + + + + + + + + This tests that X-WOPI-Lock header is returned when unlock with old lock. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + LocksPrereq + FileEditingPrereq + + + + + This tests that X-WOPI-Lock header is returned when put file with incorrect lock ID. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + LocksPrereq + GetLockPrereq + + + + + Tests the standard GetLock flow; Lock, GetLock, then Unlock. + + + + + + + + + + + + + + + + + + + Tests that the lock ID GetLock returns changes after it's changed by UnlockAndRelock. + + + + + + + + + + + + + + + + + + Tests that host returns status code 401 or 404 when get lock with invalid access token. + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + LocksPrereq + GetLockPrereq + ExtendedLockLengthPrereq + + + + + Tests that the host is capable of handling WOPI Lock IDs of 1024 characters. + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + UserCanWriteRelativePrereq + FileEditingPrereq + SupportsFoldersPrereq + + + + + Tests CheckFileInfo operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + Tests GetFile operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + Tests DeleteFile operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + Tests PutRelativeFile operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + UserCanWriteRelativePrereq + LocksPrereq + FileEditingPrereq + SupportsFoldersPrereq + + + + + Tests Lock operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests Unlock operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests RefreshLock operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests UnlockAndRelock operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + UserCanWriteRelativePrereq + FileEditingPrereq + GetLockPrereq + SupportsFoldersPrereq + + + + + Tests that host returns status code 404 when get lock for unknown file. + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + UserCanWriteRelativePrereq + LocksPrereq + FileEditingPrereq + SupportsFoldersPrereq + + + + + Tests that host returns status code 404 when update unknown file. + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportsUserInfoPrereq + + + + + + This tests that PutUserInfo call returns 200 and subsequent CheckFileInfo calls return the correct value + + + + PutUserInfoTest + + + + + + + + + + + + + + + + + + + This tests that PutUserInfo request with invalid access token expects a 401 or 404 response. + + + + PutUserInfoTest + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportsUserInfoPrereq + UserCanWriteRelativePrereq + FileEditingPrereq + SupportsFoldersPrereq + + + + + Tests that host returns status code 404 when put user info based on unknown file. + + + + + + + + + + + + + + + PutUserInfoTest + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + LocksPrereq + SupportsFoldersPrereq + RenameFilePrereq + UserCanWriteRelativePrereq + + + + + Tests that rename a file successfully. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests that file name is encoded correctly after rename file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests that rename file with correct lock ID successfully after lock it. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests that host returns 404 status code for unknown file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests that host returns 401 or 403 status code for RenameFile request with invalid access token. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests that rename file should succeed or failed with status code 400 if rename to an existing file name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportsScenarioLinksPrereq + + + + + Tests the GetRestrictedLink operation for a file + + + + + + + + + + + + + + + + Tests the GetRestrictedLink operation for a file where the requested restricted link type is unknown. + + + + + + + + + + + + + + Simulates a GetRestrictedLink request with invalid access token and expects a 404 response. + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + SupportsFoldersPrereq + SupportsScenarioLinksPrereq + UserCanWriteRelativePrereq + + + + + Tests that GetRestrictedLink operation should fail with a 404 status code for a deleted file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportsScenarioLinksPrereq + + + + + Tests the RevokeRestrictedLink operation for a file + + + + + + + + + + + + + + + + Tests the RevokeRestrictedLink operation for a file where the requested restricted link type is unknown. + + + + + + + + + + + + + Tests that a RevokeRestrictedLink request with invalid access token expects a 404 response. + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + FileEditingPrereq + SupportsFoldersPrereq + SupportsScenarioLinksPrereq + UserCanWriteRelativePrereq + + + + + Tests that RevokeRestrictedLink operation should fail with a 404 status code for a deleted file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportsSecureStorePrereq + + + + + Tests the ReadSecureStore operation for a file + + + + + + + + + + + + + + + + + + + + Tests that ReadSecureStore request with invalid access token expects a 401 or 404 response. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + UserCanWriteRelativePrereq + FileEditingPrereq + SupportsFoldersPrereq + SupportsSecureStorePrereq + + + + + Tests ReadSecureStore operation returns 404 status code if file is unknown. + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + ShareUrlTypeReadOnlyForFilePrereq + + + + + Tests the GetShareUrl operation for a file where the requested share url type is "ReadOnly". + + + + + + + + + + + + + + + + + Tests that a GetShareUrl request for "ReadOnly" url type with invalid access token expects a 401 or 404 response. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + ShareUrlTypeReadWriteForFilePrereq + + + + + Tests the GetShareUrl operation for a file where the requested share url type is "ReadWrite". + + + + + + + + + + + + + + + + + Tests that a GetShareUrl request for "ReadWrite" url type with invalid access token expects a 401 or 404 response. + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportedShareUrlTypesForFilePrereq + + + + + Tests the GetShareUrl operation for a file where the requested share url type is unknown. + + + + + + + + + + + + + Tests the GetShareUrl operation for a file where the X-WOPI-UrlType header is not present. + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportedShareUrlTypesForFilePrereq + UserCanWriteRelativePrereq + FileEditingPrereq + SupportsFoldersPrereq + + + + + Tests that GetShareUrl operation should fail with a 404 status code for a deleted file. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WopiValidatorPrereq + SupportsSecureStorePrereq + + + + + Tests host returns a value for X-WOPI-PerfTrace if the header X-WOPI-PerfTraceRequested in the request is present and equal to "true". + + + + + + + + + + + + diff --git a/TestCases.xsd b/TestCases.xsd index 7363029..ab83e31 100644 --- a/TestCases.xsd +++ b/TestCases.xsd @@ -49,6 +49,7 @@ + @@ -143,6 +144,7 @@ + @@ -278,6 +280,7 @@ + @@ -289,6 +292,7 @@ + @@ -302,14 +306,17 @@ + + + @@ -374,6 +381,7 @@ + @@ -417,6 +425,7 @@ + @@ -444,12 +453,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -463,7 +503,9 @@ + + diff --git a/src/WopiValidator.Core/ConfigParser.cs b/src/WopiValidator.Core/ConfigParser.cs index 187a80c..2fd2a91 100644 --- a/src/WopiValidator.Core/ConfigParser.cs +++ b/src/WopiValidator.Core/ConfigParser.cs @@ -10,6 +10,9 @@ namespace Microsoft.Office.WopiValidator.Core { public static class ConfigParser { + public static string UsingRestrictedScenario { get; set; } + public static string ApplicationId { get; set; } + public static IEnumerable ParseExecutionData(string filePath, TestCategory targetTestCategory, string testGroupName = "") { return ParseExecutionData(filePath, new ResourceManagerFactory(), new TestCaseFactory(), testGroupName, targetTestCategory); diff --git a/src/WopiValidator.Core/Constants.cs b/src/WopiValidator.Core/Constants.cs index e8038ac..e2b0d03 100644 --- a/src/WopiValidator.Core/Constants.cs +++ b/src/WopiValidator.Core/Constants.cs @@ -35,6 +35,10 @@ public static class Headers public const string OverwriteRelative = "X-WOPI-OverwriteRelativeTarget"; public const string Version = "X-WOPI-ItemVersion"; public const string UrlType = "X-WOPI-UrlType"; + public const string RestrictedLink = "X-WOPI-RestrictedLink"; + public const string UsingRestrictedScenario = "X-WOPI-UsingRestrictedScenario"; + public const string ApplicationId = "X-WOPI-ApplicationId"; + public const string PerfTraceRequested = "X-WOPI-PerfTraceRequested"; // This is not an official WOPI header; it is used to pass exception information // back to the validator UI. See the ExceptionHelper class for more details. @@ -65,6 +69,9 @@ public static class Overrides public const string GetShareUrl = "GET_SHARE_URL"; public const string AddActivities = "ADD_ACTIVITIES"; public const string PutUserInfo = "PUT_USER_INFO"; + public const string GetRestrictedLink = "GET_RESTRICTED_LINK"; + public const string RevokeRestrictedLink = "REVOKE_RESTRICTED_LINK"; + public const string ReadSecureStore = "READ_SECURE_STORE"; } public static class RequestMethods @@ -101,6 +108,9 @@ public static class Requests public const string GetShareUrl = "GetShareUrl"; public const string AddActivities = "AddActivities"; public const string PutUserInfo = "PutUserInfo"; + public const string GetRestrictedLink = "GetRestrictedLink"; + public const string RevokeRestrictedLink = "RevokeRestrictedLink"; + public const string ReadSecureStore = "ReadSecureStore"; } public static class Validators diff --git a/src/WopiValidator.Core/Factories/RequestFactory.cs b/src/WopiValidator.Core/Factories/RequestFactory.cs index b8cf2a7..24ef3ef 100644 --- a/src/WopiValidator.Core/Factories/RequestFactory.cs +++ b/src/WopiValidator.Core/Factories/RequestFactory.cs @@ -51,6 +51,8 @@ private static IRequest GetRequest(XElement definition) UrlType = (string)definition.Attribute("UrlType"), Validators = validators ?? GetDefaultValidators(), WopiSrc = (string)definition.Attribute("WopiSrc"), + RestrictedLinkType = (string)definition.Attribute("RestrictedLink"), + PerfTraceRequested = string.IsNullOrEmpty((string)definition.Attribute("PerfTraceRequested")) ? false : Boolean.Parse((string)definition.Attribute("PerfTraceRequested")) }; if (requestBodyDefinition != null && !String.IsNullOrEmpty(requestBodyDefinition.Value)) @@ -74,6 +76,16 @@ private static IRequest GetRequest(XElement definition) } } + if (!string.IsNullOrEmpty(ConfigParser.UsingRestrictedScenario)) + { + wopiRequestParams.UsingRestrictedScenario = ConfigParser.UsingRestrictedScenario; + } + + if (!string.IsNullOrEmpty(ConfigParser.ApplicationId)) + { + wopiRequestParams.ApplicationId = ConfigParser.ApplicationId; + } + switch (elementName) { case Constants.Requests.CheckFile: @@ -128,7 +140,12 @@ private static IRequest GetRequest(XElement definition) return new AddActivitiesRequest(wopiRequestParams); case Constants.Requests.PutUserInfo: return new PutUserInfoRequest(wopiRequestParams); - + case Constants.Requests.GetRestrictedLink: + return new GetRestrictedLinkRequest(wopiRequestParams); + case Constants.Requests.RevokeRestrictedLink: + return new RevokeRestrictedLinkRequest(wopiRequestParams); + case Constants.Requests.ReadSecureStore: + return new ReadSecureStoreRequest(wopiRequestParams); default: throw new ArgumentException(string.Format("Unknown request: '{0}'", elementName)); } diff --git a/src/WopiValidator.Core/Factories/ValidatorFactory.cs b/src/WopiValidator.Core/Factories/ValidatorFactory.cs index 2b37134..227686b 100644 --- a/src/WopiValidator.Core/Factories/ValidatorFactory.cs +++ b/src/WopiValidator.Core/Factories/ValidatorFactory.cs @@ -77,8 +77,9 @@ private static IValidator GetResponseHeaderValidator(XElement definition) string expectedValue = (string)definition.Attribute("ExpectedValue"); bool isRequired = ((bool?)definition.Attribute("IsRequired")) ?? true; bool shouldMatch = ((bool?)definition.Attribute("ShouldMatch")) ?? true; + bool isUrl = ((bool?)definition.Attribute("IsUrl")) ?? false; - return new ResponseHeaderValidator(header, expectedValue, expectedStateKey, isRequired, shouldMatch); + return new ResponseHeaderValidator(header, expectedValue, expectedStateKey, isRequired, shouldMatch, isUrl); } /// @@ -172,7 +173,8 @@ private static JsonContentValidator.IJsonPropertyValidator GetJsonPropertyValida expectedValue, hasExpectedValue, endsWithValue, - expectedStateKey); + expectedStateKey, + shouldMatch); case Constants.Validators.Properties.StringRegexProperty: return new JsonContentValidator.JsonStringRegexPropertyValidator(key, diff --git a/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json new file mode 100644 index 0000000..4fa7806 --- /dev/null +++ b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json @@ -0,0 +1,408 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Full WOPI CheckFileInfo JSON Schema", + "description": "A WOPI CheckFileInfo JSON response", + "type": "object", + "additionalProperties": false, + "properties": { + "AllowAdditionalMicrosoftServices": { + "type": "boolean", + "default": false, + "optional": true + }, + "AllowExternalMarketplace": { + "type": "boolean", + "default": false, + "optional": true + }, + "BaseFileName": { + "type": "string", + "optional": false + }, + "BreadcrumbBrandName": { + "type": "string", + "default": "", + "optional": true + }, + "BreadcrumbBrandUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "BreadcrumbDocName": { + "type": "string", + "default": "", + "optional": true + }, + "BreadcrumbDocUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "BreadcrumbFolderName": { + "type": "string", + "default": "", + "optional": true + }, + "BreadcrumbFolderUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "ClientUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "CloseButtonClosesWindow": { + "type": "boolean", + "default": false, + "optional": true + }, + "ClosePostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "CloseUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "DisableBrowserCachingOfUserContent": { + "type": "boolean", + "default": false, + "optional": true + }, + "DisablePrint": { + "type": "boolean", + "default": false, + "optional": true + }, + "DisableTranslation": { + "type": "boolean", + "default": false, + "optional": true + }, + "DownloadUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "EditAndReplyUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "EditModePostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "EditNotificationPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "FileExtension": { + "type": "string", + "default": "", + "optional": true + }, + "FileNameMaxLength": { + "type": "integer", + "default": 250, + "optional": true + }, + "FileSharingPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "FileSharingUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "FileUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "HostAuthenticationId": { + "type": "string", + "default": "", + "optional": true + }, + "HostEditUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "HostEmbeddedEditUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "HostEmbeddedViewUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "HostName": { + "type": "string", + "default": "", + "optional": true + }, + "HostNotes": { + "type": "string", + "default": "", + "optional": true + }, + "HostRestUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "HostViewUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "IrmPolicyDescription": { + "type": "string", + "default": "", + "optional": true + }, + "IrmPolicyTitle": { + "type": "string", + "default": "", + "optional": true + }, + "LicenseCheckForEditIsEnabled": { + "type": "boolean", + "default": false, + "optional": true + }, + "OwnerId": { + "type": "string", + "optional": false + }, + "PostMessageOrigin": { + "type": "string", + "default": "", + "optional": true + }, + "PresenceProvider": { + "type": "string", + "default": "", + "optional": true + }, + "PresenceUserId": { + "type": "string", + "default": "", + "optional": true + }, + "PrivacyUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "ProtectInClient": { + "type": "boolean", + "default": false, + "optional": true + }, + "ReadOnly": { + "type": "boolean", + "default": false, + "optional": true + }, + "RestrictedWebViewOnly": { + "type": "boolean", + "default": false, + "optional": true + }, + "SHA256": { + "type": "string", + "optional": true + }, + "SignInUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "SignoutUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "Size": { + "type": "integer", + "optional": false + }, + "SupportedShareUrlTypes": { + "type": "array", + "default": [], + "optional": true + }, + "SupportsCoauth": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsCobalt": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsExtendedLockLength": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsFileCreation": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsFolders": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsGetLock": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsLocks": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsRename": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsScenarioLinks": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsSecureStore": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsUpdate": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsUserInfo": { + "type": "boolean", + "default": false, + "optional": true + }, + "TenantId": { + "type": "string", + "default": "", + "optional": true + }, + "TermsOfUseUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + }, + "TimeZone": { + "type": "string", + "default": "", + "optional": true + }, + "UniqueContentId": { + "type": "string", + "default": "", + "optional": true + }, + "UserCanAttend": { + "type": "boolean", + "default": false, + "optional": true + }, + "UserCanNotWriteRelative": { + "type": "boolean", + "default": false, + "optional": true + }, + "UserCanPresent": { + "type": "boolean", + "default": false, + "optional": true + }, + "UserCanRename": { + "type": "boolean", + "default": false, + "optional": true + }, + "UserCanWrite": { + "type": "boolean", + "default": false, + "optional": true + }, + "UserFriendlyName": { + "type": "string", + "default": "", + "optional": true + }, + "UserId": { + "type": "string", + "default": "", + "optional": true + }, + "UserInfo": { + "type": "string", + "default": "", + "optional": true + }, + "Version": { + "type": "string", + "optional": false + }, + "WebEditingDisabled": { + "type": "boolean", + "default": false, + "optional": true + }, + "WorkflowType": { + "type": "array", + "default": [], + "optional": true + }, + "WorkflowUrl": { + "type": "string", + "format": "uri", + "default": "", + "optional": true + } + } +} diff --git a/src/WopiValidator.Core/JsonSchemas/ReadSecureStoreSchema.json b/src/WopiValidator.Core/JsonSchemas/ReadSecureStoreSchema.json new file mode 100644 index 0000000..8d3179c --- /dev/null +++ b/src/WopiValidator.Core/JsonSchemas/ReadSecureStoreSchema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Full WOPI ReadSecureStore JSON Schema", + "description": "A WOPI ReadSecureStore JSON response", + "type": "object", + "additionalProperties": false, + "required": [ + "UserName", + "Password" + ], + "properties": { + "UserName": { + "type": "string" + }, + "Password": { + "type": "string" + }, + "IsWindowsCredentials": { + "type": "boolean" + }, + "IsGroup": { + "type": "boolean" + } + } +} diff --git a/src/WopiValidator.Core/Requests/GetRestrictedLinkRequest.cs b/src/WopiValidator.Core/Requests/GetRestrictedLinkRequest.cs new file mode 100644 index 0000000..3d42fda --- /dev/null +++ b/src/WopiValidator.Core/Requests/GetRestrictedLinkRequest.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Office.WopiValidator.Core.Requests +{ + class GetRestrictedLinkRequest : WopiRequest + { + public GetRestrictedLinkRequest(WopiRequestParam param) : base(param) + { + this.RestrictedLinkType = param.RestrictedLinkType; + this.UsingRestrictedScenario = param.UsingRestrictedScenario; + } + + public string RestrictedLinkType { get; private set; } + public string UsingRestrictedScenario { get; private set; } + public override string Name { get { return Constants.Requests.GetRestrictedLink; } } + protected override string WopiOverrideValue { get { return Constants.Overrides.GetRestrictedLink; } } + protected override IEnumerable> DefaultHeaders + { + get + { + Dictionary headers = new Dictionary(); + headers.Add(Constants.Headers.RestrictedLink, RestrictedLinkType); + + if (!string.IsNullOrEmpty(UsingRestrictedScenario)) + { + headers.Add(Constants.Headers.UsingRestrictedScenario, UsingRestrictedScenario); + } + + return headers; + } + } + } +} diff --git a/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs b/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs index 1352950..9f90073 100644 --- a/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs +++ b/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs @@ -19,10 +19,14 @@ protected override IEnumerable> DefaultHeaders { get { - return new Dictionary + Dictionary headers = new Dictionary(); + + if (!string.IsNullOrEmpty(this.UrlType)) { - {Constants.Headers.UrlType, UrlType} - }; + headers.Add(Constants.Headers.UrlType, UrlType); + } + + return headers; } } } diff --git a/src/WopiValidator.Core/Requests/ReadSecureStoreRequest.cs b/src/WopiValidator.Core/Requests/ReadSecureStoreRequest.cs new file mode 100644 index 0000000..8dfef4f --- /dev/null +++ b/src/WopiValidator.Core/Requests/ReadSecureStoreRequest.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Office.WopiValidator.Core.Requests +{ + class ReadSecureStoreRequest : WopiRequest + { + public ReadSecureStoreRequest(WopiRequestParam param) : base(param) + { + this.ApplicationId = param.ApplicationId; + this.PerfTraceRequested = param.PerfTraceRequested; + } + + public string ApplicationId { get; private set; } + public bool PerfTraceRequested { get; private set; } + public override string Name { get { return Constants.Requests.ReadSecureStore; } } + protected override string WopiOverrideValue { get { return Constants.Overrides.ReadSecureStore; } } + + protected override IEnumerable> GetCustomHeaders(Dictionary savedState, IResourceManager resourceManager) + { + if (string.IsNullOrEmpty(this.ApplicationId)) + { + throw new System.Exception("No value provided for header 'X-WOPI-ApplicationId' in ReadSecureStore request! \n Provide value for header 'X-WOPI-ApplicationId' by using command line argument '--ApplicationId'."); + } + + Dictionary headers = new Dictionary(); + headers.Add(Constants.Headers.ApplicationId, this.ApplicationId); + + if (this.PerfTraceRequested) + { + headers.Add(Constants.Headers.PerfTraceRequested, System.Boolean.TrueString); + } + + return headers; + } + } +} diff --git a/src/WopiValidator.Core/Requests/RevokeRestrictedLinkRequest.cs b/src/WopiValidator.Core/Requests/RevokeRestrictedLinkRequest.cs new file mode 100644 index 0000000..b4e1efd --- /dev/null +++ b/src/WopiValidator.Core/Requests/RevokeRestrictedLinkRequest.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Office.WopiValidator.Core.Requests +{ + class RevokeRestrictedLinkRequest : WopiRequest + { + public RevokeRestrictedLinkRequest(WopiRequestParam param) : base(param) + { + this.RestrictedLinkType = param.RestrictedLinkType; + this.UsingRestrictedScenario = param.UsingRestrictedScenario; + } + + public string RestrictedLinkType { get; private set; } + public string UsingRestrictedScenario { get; private set; } + public override string Name { get { return Constants.Requests.RevokeRestrictedLink; } } + protected override string WopiOverrideValue { get { return Constants.Overrides.RevokeRestrictedLink; } } + protected override IEnumerable> DefaultHeaders + { + get + { + Dictionary headers = new Dictionary(); + headers.Add(Constants.Headers.RestrictedLink, RestrictedLinkType); + + if (!string.IsNullOrEmpty(UsingRestrictedScenario)) + { + headers.Add(Constants.Headers.UsingRestrictedScenario, UsingRestrictedScenario); + } + + return headers; + } + } + } +} diff --git a/src/WopiValidator.Core/Requests/WopiRequestParam.cs b/src/WopiValidator.Core/Requests/WopiRequestParam.cs index 04cd590..5feb647 100644 --- a/src/WopiValidator.Core/Requests/WopiRequestParam.cs +++ b/src/WopiValidator.Core/Requests/WopiRequestParam.cs @@ -26,6 +26,10 @@ public struct WopiRequestParam public IEnumerable Validators { get; set; } public string WopiSrc { get; set; } public string UrlType { get; set; } + public string RestrictedLinkType { get; set; } + public string UsingRestrictedScenario { get; set; } + public string ApplicationId { get; set; } + public bool PerfTraceRequested { get; set; } } public enum PutRelativeFileMode diff --git a/src/WopiValidator.Core/TestCaseExecutor.cs b/src/WopiValidator.Core/TestCaseExecutor.cs index 1602721..343aafa 100644 --- a/src/WopiValidator.Core/TestCaseExecutor.cs +++ b/src/WopiValidator.Core/TestCaseExecutor.cs @@ -119,6 +119,12 @@ private TestCaseResult ExecuteTestCase(ITestCase testCase) requestDetails.Add(requestInfo); + // Save any state that was requested + foreach (IStateEntry stateSaver in request.State) + { + savedState[stateSaver.Name] = stateSaver.GetValue(responseData); + } + // return on the first request that fails if (validationFailures.Any()) { @@ -135,11 +141,7 @@ private TestCaseResult ExecuteTestCase(ITestCase testCase) break; } - // Save any state that was requested - foreach (IStateEntry stateSaver in request.State) - { - savedState[stateSaver.Name] = stateSaver.GetValue(responseData); - } + } } finally diff --git a/src/WopiValidator.Core/Validators/JsonContentValidator.cs b/src/WopiValidator.Core/Validators/JsonContentValidator.cs index 41559e6..84d9d41 100644 --- a/src/WopiValidator.Core/Validators/JsonContentValidator.cs +++ b/src/WopiValidator.Core/Validators/JsonContentValidator.cs @@ -239,7 +239,7 @@ protected virtual bool Compare(JToken actualValue, T expectedValue, out string e isValid = false; } - errorMessage = string.Format(CultureInfo.CurrentCulture, "Expected: '{0}', Actual: '{1}'", FormattedExpectedValue, formattedActualValue); + errorMessage = string.Format(CultureInfo.CurrentCulture, "Expected: '{0}', Actual: '{1}'", FormatValue(expectedValue), formattedActualValue); return isValid; } @@ -296,11 +296,13 @@ public override string FormatValue(bool value) public class JsonStringPropertyValidator : JsonPropertyEqualityValidator { private readonly string _endsWithValue; + private readonly bool _shouldMatch; - public JsonStringPropertyValidator(string key, bool isRequired, string expectedValue, bool hasExpectedValue, string endsWithValue, string expectedStateKey) + public JsonStringPropertyValidator(string key, bool isRequired, string expectedValue, bool hasExpectedValue, string endsWithValue, string expectedStateKey, bool shouldMatch = true) : base(key, isRequired, expectedValue, hasExpectedValue, expectedStateKey) { _endsWithValue = endsWithValue; + _shouldMatch = shouldMatch; } public override string FormatValue(string value) @@ -310,8 +312,13 @@ public override string FormatValue(string value) public override bool Validate(JToken actualValue, Dictionary savedState, out string errorMessage) { - if (!base.Validate(actualValue, savedState, out errorMessage)) + if (_shouldMatch & !base.Validate(actualValue, savedState, out errorMessage)) return false; + else if (!_shouldMatch & base.Validate(actualValue, savedState, out errorMessage)) + { + errorMessage = errorMessage.Replace("Expected", "Unexpected"); + return false; + } errorMessage = ""; if (String.IsNullOrWhiteSpace(_endsWithValue)) diff --git a/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs b/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs index fa0eb7a..8859fde 100644 --- a/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs +++ b/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs @@ -17,14 +17,16 @@ class ResponseHeaderValidator : IValidator public readonly string ExpectedStateKey; public readonly bool IsRequired; public readonly bool ShouldMatch; + public readonly bool IsUrl; - public ResponseHeaderValidator(string key, string expectedValue, string expectedStateKey, bool isRequired = true, bool shouldMatch = true) + public ResponseHeaderValidator(string key, string expectedValue, string expectedStateKey, bool isRequired = true, bool shouldMatch = true, bool isUrl = false) { Key = key; DefaultExpectedValue = expectedValue; ExpectedStateKey = expectedStateKey; IsRequired = isRequired; ShouldMatch = shouldMatch; + IsUrl = isUrl; } public string Name @@ -48,6 +50,22 @@ public ValidationResult Validate(IResponseData data, IResourceManager resourceMa } } + if (IsUrl) + { + if (string.IsNullOrEmpty(headerValue)) + { + return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "'{0}' header value should be any non empty string.", + Key)); + } + + Uri uri; + if (!Uri.TryCreate(headerValue, UriKind.Absolute, out uri)) + { + return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "'{0}' header value should be a valid url.", + Key)); + } + } + // If the "ExpectedValue" and "ExpectedStateKey" attributes are non-empty on a Validator, then ExpectedStateKey will take precedence. // But if the mentioned "ExpectedStateKey" is invalid or doesn't have a saved state value, then the logic below will default to the value set in // "ExpectedValue" attribute of the Validator. diff --git a/src/WopiValidator.Core/WopiValidator.Core.csproj b/src/WopiValidator.Core/WopiValidator.Core.csproj index e70376f..9f8ddfb 100644 --- a/src/WopiValidator.Core/WopiValidator.Core.csproj +++ b/src/WopiValidator.Core/WopiValidator.Core.csproj @@ -16,10 +16,14 @@ + + + + diff --git a/src/WopiValidator/Options.cs b/src/WopiValidator/Options.cs index 10494d5..417f63b 100644 --- a/src/WopiValidator/Options.cs +++ b/src/WopiValidator/Options.cs @@ -20,6 +20,12 @@ class Options [Option('l', "token_ttl", Required = true, HelpText = "WOPI access token ttl")] public long AccessTokenTtl { get; set; } + [Option("UsingRestrictedScenario", Required = false, HelpText = "Header 'X-WOPI-UsingRestrictedScenario' used Restricted scenario")] + public string UsingRestrictedScenario { get; set; } + + [Option("ApplicationId", Required = false, HelpText = "Header 'X-WOPI-ApplicationId' indicates id of an application stored in secure store")] + public string ApplicationId { get; set; } + [Option('c', "config", Required = false, Default = "TestCases.xml", HelpText = "Path to XML file with test definitions")] public string RunConfigurationFilePath { get; set; } diff --git a/src/WopiValidator/Program.cs b/src/WopiValidator/Program.cs index f879939..5b0387e 100644 --- a/src/WopiValidator/Program.cs +++ b/src/WopiValidator/Program.cs @@ -62,6 +62,9 @@ private static int Main(string[] args) private static ExitCode Execute(Options options) { + ConfigParser.UsingRestrictedScenario = options.UsingRestrictedScenario; + ConfigParser.ApplicationId = options.ApplicationId; + // get run configuration from XML IEnumerable testData = ConfigParser.ParseExecutionData(options.RunConfigurationFilePath, options.TestCategory); diff --git a/src/WopiValidator/WopiValidator.csproj b/src/WopiValidator/WopiValidator.csproj index 65e30a4..4b21102 100644 --- a/src/WopiValidator/WopiValidator.csproj +++ b/src/WopiValidator/WopiValidator.csproj @@ -43,6 +43,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest From 3d6a2b383525aa75617291f1ace81fb9f19bfbd6 Mon Sep 17 00:00:00 2001 From: Tingting Wen Date: Wed, 20 Feb 2019 09:53:26 +0800 Subject: [PATCH 2/8] Add test cases to test [MS-WOPI] server implementation about folder and folder content --- MS-WOPIFoldersTestCases.xml | 100 ++++++++++++ TestCases.xsd | 7 + src/WopiValidator.Core/Constants.cs | 1 + .../Factories/RequestFactory.cs | 2 + .../MS-WOPICheckFolderInfoSchema.json | 144 ++++++++++++++++++ .../Requests/CheckFolderInfoRequest.cs | 16 ++ .../WopiValidator.Core.csproj | 2 + src/WopiValidator/WopiValidator.csproj | 3 + 8 files changed, 275 insertions(+) create mode 100644 MS-WOPIFoldersTestCases.xml create mode 100644 src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json create mode 100644 src/WopiValidator.Core/Requests/CheckFolderInfoRequest.cs diff --git a/MS-WOPIFoldersTestCases.xml b/MS-WOPIFoldersTestCases.xml new file mode 100644 index 0000000..6370924 --- /dev/null +++ b/MS-WOPIFoldersTestCases.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + This tests that hosts' CheckFolderInfo responses conform to the JSON schema. + + + + + + + + + + + + + + + + + + + This tests that hosts' EnumerateChildren responses conform to the JSON schema. + + + + + + + + + + + + + + + + + + + This tests that Version in EnumerateChildren response matches the value in Version field in CheckFileInfo response. + + + + + + + + + + + + + + + + + + + + + + + + + + + This tests that Version in EnumerateChildren response changes when the file changes. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TestCases.xsd b/TestCases.xsd index ab83e31..f4384bc 100644 --- a/TestCases.xsd +++ b/TestCases.xsd @@ -490,6 +490,13 @@ + + + + + + + diff --git a/src/WopiValidator.Core/Constants.cs b/src/WopiValidator.Core/Constants.cs index e2b0d03..6e083f4 100644 --- a/src/WopiValidator.Core/Constants.cs +++ b/src/WopiValidator.Core/Constants.cs @@ -111,6 +111,7 @@ public static class Requests public const string GetRestrictedLink = "GetRestrictedLink"; public const string RevokeRestrictedLink = "RevokeRestrictedLink"; public const string ReadSecureStore = "ReadSecureStore"; + public const string CheckFolderInfo = "CheckFolderInfo"; } public static class Validators diff --git a/src/WopiValidator.Core/Factories/RequestFactory.cs b/src/WopiValidator.Core/Factories/RequestFactory.cs index 24ef3ef..e702f5d 100644 --- a/src/WopiValidator.Core/Factories/RequestFactory.cs +++ b/src/WopiValidator.Core/Factories/RequestFactory.cs @@ -146,6 +146,8 @@ private static IRequest GetRequest(XElement definition) return new RevokeRestrictedLinkRequest(wopiRequestParams); case Constants.Requests.ReadSecureStore: return new ReadSecureStoreRequest(wopiRequestParams); + case Constants.Requests.CheckFolderInfo: + return new CheckFolderInfoRequest(wopiRequestParams); default: throw new ArgumentException(string.Format("Unknown request: '{0}'", elementName)); } diff --git a/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json new file mode 100644 index 0000000..15d9d37 --- /dev/null +++ b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json @@ -0,0 +1,144 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Full WOPI CheckFolderInfo JSON Schema", + "description": "A WOPI CheckFolderInfo JSON response", + "type": "object", + "additionalProperties": false, + "required": [ + "FolderName", + "OwnerId" + ], + "properties": { + "FolderName": { + "type": "string" + }, + "BreadcrumbBrandIconUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "BreadcrumbBrandName": { + "type": "string", + "default": "" + }, + "BreadcrumbBrandUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "BreadcrumbDocName": { + "type": "string", + "default": "" + }, + "BreadcrumbDocUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "BreadcrumbFolderName": { + "type": "string", + "default": "" + }, + "BreadcrumbFolderUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "ClientUrl": { + "type": "string", + "default": "" + }, + "CloseButtonClosesWindow": { + "type": "boolean", + "default": false + }, + "CloseUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "FileSharingUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "HostAuthenticationId": { + "type": "string", + "default": "" + }, + "HostEditUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "HostEmbeddedEditUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "HostEmbeddedViewUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "HostName": { + "type": "string", + "default": "" + }, + "HostViewUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "OwnerId": { + "type": "string" + }, + "PresenceProvider": { + "type": "string", + "default": "" + }, + "PresenceUserId": { + "type": "string", + "default": "" + }, + "PrivacyUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "SignoutUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "SupportsSecureStore": { + "type": "boolean", + "default": false + }, + "TenantId": { + "type": "string", + "default": "" + }, + "TermsOfUseUrl": { + "type": "string", + "format": "uri", + "default": "" + }, + "UserCanWrite": { + "type": "boolean", + "default": false + }, + "UserFriendlyName": { + "type": "string", + "default": "" + }, + "UserId": { + "type": "string", + "default": "" + }, + "WebEditingDisabled": { + "type": "boolean", + "default": false + } + } +} diff --git a/src/WopiValidator.Core/Requests/CheckFolderInfoRequest.cs b/src/WopiValidator.Core/Requests/CheckFolderInfoRequest.cs new file mode 100644 index 0000000..6f001ad --- /dev/null +++ b/src/WopiValidator.Core/Requests/CheckFolderInfoRequest.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Office.WopiValidator.Core.Requests +{ + class CheckFolderInfoRequest : WopiRequest + { + public CheckFolderInfoRequest(WopiRequestParam param) : base(param) + { + } + + public override string Name { get { return Constants.Requests.CheckFolderInfo; } } + protected override string RequestMethod { get { return Constants.RequestMethods.Get; } } + protected override string WopiOverrideValue { get { return null; } } + } +} diff --git a/src/WopiValidator.Core/WopiValidator.Core.csproj b/src/WopiValidator.Core/WopiValidator.Core.csproj index 9f8ddfb..b85ec87 100644 --- a/src/WopiValidator.Core/WopiValidator.Core.csproj +++ b/src/WopiValidator.Core/WopiValidator.Core.csproj @@ -17,12 +17,14 @@ + + diff --git a/src/WopiValidator/WopiValidator.csproj b/src/WopiValidator/WopiValidator.csproj index 4b21102..3c6ca25 100644 --- a/src/WopiValidator/WopiValidator.csproj +++ b/src/WopiValidator/WopiValidator.csproj @@ -43,6 +43,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest From a4fce18941243d618da236bcaa16629f8abad128 Mon Sep 17 00:00:00 2001 From: Tingting Wen Date: Mon, 4 Mar 2019 18:16:02 +0800 Subject: [PATCH 3/8] Update MS-WOPI test cases as testing against SharePoint server. --- MS-WOPIFoldersTestCases.xml | 6 +- MS-WOPITestCases.xml | 636 ++---------------- TestCases.xsd | 1 + .../Factories/ValidatorFactory.cs | 3 +- .../Validators/ResponseHeaderValidator.cs | 15 +- 5 files changed, 64 insertions(+), 597 deletions(-) diff --git a/MS-WOPIFoldersTestCases.xml b/MS-WOPIFoldersTestCases.xml index 6370924..a6980f9 100644 --- a/MS-WOPIFoldersTestCases.xml +++ b/MS-WOPIFoldersTestCases.xml @@ -4,7 +4,7 @@ - + @@ -23,7 +23,7 @@ - + @@ -88,7 +88,7 @@ - + diff --git a/MS-WOPITestCases.xml b/MS-WOPITestCases.xml index 6f5cb66..47d66ab 100644 --- a/MS-WOPITestCases.xml +++ b/MS-WOPITestCases.xml @@ -12,21 +12,6 @@ - - - The prereq WOPI validation that must pass prior to running the (potentially destructive) test suite. - - - - - - - - - - - - Prereq WOPI Validation test to check if the user has permission to perform Write operation. @@ -118,36 +103,6 @@ - - - Prereq WOPI Validation test to check if host declares support for GetLock operation. - - - - - - - - - - - - - - - Prereq WOPI Validation test to check if host declares support for extended lock lengths. - - - - - - - - - - - - The prereq BusinessFlowPrereq must pass prior to running the feature validations related to business flows. @@ -208,21 +163,6 @@ - - - The host must support /files/PutUserInfo. - - - - - - - - - - - - Prereq WOPI Validation test to check if the host declares support for GetRestrictedLink/RevokeRestrictedLink @@ -300,9 +240,6 @@ - - WopiValidatorPrereq - @@ -312,10 +249,6 @@ - - - - @@ -346,7 +279,6 @@ - WopiValidatorPrereq FileEditingPrereq UserCanWritePrereq @@ -385,9 +317,6 @@ - - WopiValidatorPrereq - @@ -432,7 +361,6 @@ - WopiValidatorPrereq FileEditingPrereq UserCanWriteRelativePrereq LocksPrereq @@ -529,7 +457,7 @@ - + @@ -793,7 +721,6 @@ - WopiValidatorPrereq FileEditingPrereq NotReadOnlyPrereq UserCanWritePrereq @@ -885,7 +812,6 @@ - WopiValidatorPrereq FileEditingPrereq LocksPrereq UserCanWritePrereq @@ -913,7 +839,6 @@ - WopiValidatorPrereq LocksPrereq @@ -953,36 +878,6 @@ - - - Two Lock calls in a row with the same Lock ID should work. - - - - - - - - - - - - - - Responses to Unlock requests on an unlocked file should include the X-WOPI-Lock header set to the empty string. - - - - - - - - - - - - - @@ -1116,7 +1011,6 @@ - WopiValidatorPrereq LocksPrereq @@ -1141,204 +1035,121 @@ - This tests that X-WOPI-Lock header is returned when lock mismatch for Unlock request. + This tests that X-WOPI-Lock header is included when responding with the 409 status code for Unlock request if no lock exists on the file. - - + - - - - - This tests that X-WOPI-Lock header is returned when lock mismatch for RefreshLock request. + This tests that X-WOPI-Lock header is included when responding with the 409 status code for RefreshLock request if no lock exists on the file. - - + - - This tests that X-WOPI-Lock header is returned when lock mismatch for UnlockAndRelock request. + This tests that X-WOPI-Lock header is included when responding with the 409 status code for UnlockAndRelock request if no lock exists on the file. - - + - - - - - - - - - This tests that X-WOPI-Lock header is returned when unlock with old lock. - - - - - - - - - - - - - + - WopiValidatorPrereq LocksPrereq - FileEditingPrereq - + - This tests that X-WOPI-Lock header is returned when put file with incorrect lock ID. + This tests that X-WOPI-Lock header is not returned when lock success. - - + - + - - + - - - - - - WopiValidatorPrereq - LocksPrereq - GetLockPrereq - - - + - Tests the standard GetLock flow; Lock, GetLock, then Unlock. + This tests that X-WOPI-Lock header is not returned when Unlock success. - + - - - + - + - + - Tests that the lock ID GetLock returns changes after it's changed by UnlockAndRelock. + This tests that X-WOPI-Lock header is not returned when RefreshLock success. - - + - + - + - + - + - Tests that host returns status code 401 or 404 when get lock with invalid access token. + This tests that X-WOPI-Lock header is not returned when UnlockAndRelock succeess. - - - - + - - - - + - + - - - - - - - - WopiValidatorPrereq - FileEditingPrereq - LocksPrereq - GetLockPrereq - ExtendedLockLengthPrereq - - - - - Tests that the host is capable of handling WOPI Lock IDs of 1024 characters. - - - - - - - - - - - - - - - + @@ -1346,7 +1157,6 @@ - WopiValidatorPrereq UserCanWriteRelativePrereq FileEditingPrereq SupportsFoldersPrereq @@ -1464,7 +1274,6 @@ - WopiValidatorPrereq UserCanWriteRelativePrereq LocksPrereq FileEditingPrereq @@ -1604,44 +1413,8 @@ - - - WopiValidatorPrereq - UserCanWriteRelativePrereq - FileEditingPrereq - GetLockPrereq - SupportsFoldersPrereq - - - - - Tests that host returns status code 404 when get lock for unknown file. - - - - - - - - - - - - - - - - - - - - - - - - WopiValidatorPrereq UserCanWriteRelativePrereq LocksPrereq FileEditingPrereq @@ -1674,102 +1447,11 @@ - - - WopiValidatorPrereq - SupportsUserInfoPrereq - - - - - - This tests that PutUserInfo call returns 200 and subsequent CheckFileInfo calls return the correct value - - - - PutUserInfoTest - - - - - - - - - - - - - - - - - - - This tests that PutUserInfo request with invalid access token expects a 401 or 404 response. - - - - PutUserInfoTest - - - - - - - - - - - - - - - - - - - WopiValidatorPrereq - SupportsUserInfoPrereq - UserCanWriteRelativePrereq - FileEditingPrereq - SupportsFoldersPrereq - - - - - Tests that host returns status code 404 when put user info based on unknown file. - - - - - - - - - - - - - - - PutUserInfoTest - - - - - - - - - - WopiValidatorPrereq FileEditingPrereq LocksPrereq SupportsFoldersPrereq - RenameFilePrereq UserCanWriteRelativePrereq @@ -1797,6 +1479,7 @@ + @@ -1809,6 +1492,7 @@ + @@ -1818,13 +1502,13 @@ Tests that file name is encoded correctly after rename file. - + - + @@ -1837,6 +1521,7 @@ + @@ -1847,46 +1532,7 @@ - - - - - - - Tests that rename file with correct lock ID successfully after lock it. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -2002,13 +1648,14 @@ + - + @@ -2020,6 +1667,7 @@ + @@ -2029,7 +1677,6 @@ - WopiValidatorPrereq SupportsScenarioLinksPrereq @@ -2049,20 +1696,6 @@ - - - Tests the GetRestrictedLink operation for a file where the requested restricted link type is unknown. - - - - - - - - - - - Simulates a GetRestrictedLink request with invalid access token and expects a 404 response. @@ -2073,7 +1706,10 @@ - + + + + @@ -2083,7 +1719,6 @@ - WopiValidatorPrereq FileEditingPrereq SupportsFoldersPrereq SupportsScenarioLinksPrereq @@ -2129,7 +1764,6 @@ - WopiValidatorPrereq SupportsScenarioLinksPrereq @@ -2149,19 +1783,6 @@ - - - Tests the RevokeRestrictedLink operation for a file where the requested restricted link type is unknown. - - - - - - - - - - Tests that a RevokeRestrictedLink request with invalid access token expects a 404 response. @@ -2172,7 +1793,10 @@ - + + + + @@ -2182,7 +1806,6 @@ - WopiValidatorPrereq FileEditingPrereq SupportsFoldersPrereq SupportsScenarioLinksPrereq @@ -2225,7 +1848,6 @@ - WopiValidatorPrereq SupportsSecureStorePrereq @@ -2272,7 +1894,6 @@ - WopiValidatorPrereq UserCanWriteRelativePrereq FileEditingPrereq SupportsFoldersPrereq @@ -2305,171 +1926,8 @@ - - - WopiValidatorPrereq - ShareUrlTypeReadOnlyForFilePrereq - - - - - Tests the GetShareUrl operation for a file where the requested share url type is "ReadOnly". - - - - - - - - - - - - - - - - - Tests that a GetShareUrl request for "ReadOnly" url type with invalid access token expects a 401 or 404 response. - - - - - - - - - - - - - - - - - - - - - WopiValidatorPrereq - ShareUrlTypeReadWriteForFilePrereq - - - - - Tests the GetShareUrl operation for a file where the requested share url type is "ReadWrite". - - - - - - - - - - - - - - - - - Tests that a GetShareUrl request for "ReadWrite" url type with invalid access token expects a 401 or 404 response. - - - - - - - - - - - - - - - - - - - - - WopiValidatorPrereq - SupportedShareUrlTypesForFilePrereq - - - - - Tests the GetShareUrl operation for a file where the requested share url type is unknown. - - - - - - - - - - - - - Tests the GetShareUrl operation for a file where the X-WOPI-UrlType header is not present. - - - - - - - - - - - - - - - WopiValidatorPrereq - SupportedShareUrlTypesForFilePrereq - UserCanWriteRelativePrereq - FileEditingPrereq - SupportsFoldersPrereq - - - - - Tests that GetShareUrl operation should fail with a 404 status code for a deleted file. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WopiValidatorPrereq SupportsSecureStorePrereq diff --git a/TestCases.xsd b/TestCases.xsd index f4384bc..3cbc15a 100644 --- a/TestCases.xsd +++ b/TestCases.xsd @@ -145,6 +145,7 @@ + diff --git a/src/WopiValidator.Core/Factories/ValidatorFactory.cs b/src/WopiValidator.Core/Factories/ValidatorFactory.cs index 227686b..4db13c1 100644 --- a/src/WopiValidator.Core/Factories/ValidatorFactory.cs +++ b/src/WopiValidator.Core/Factories/ValidatorFactory.cs @@ -78,8 +78,9 @@ private static IValidator GetResponseHeaderValidator(XElement definition) bool isRequired = ((bool?)definition.Attribute("IsRequired")) ?? true; bool shouldMatch = ((bool?)definition.Attribute("ShouldMatch")) ?? true; bool isUrl = ((bool?)definition.Attribute("IsUrl")) ?? false; + bool isExcluded = ((bool?)definition.Attribute("IsExcluded")) ?? false; - return new ResponseHeaderValidator(header, expectedValue, expectedStateKey, isRequired, shouldMatch, isUrl); + return new ResponseHeaderValidator(header, expectedValue, expectedStateKey, isRequired, shouldMatch, isUrl, isExcluded); } /// diff --git a/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs b/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs index 8859fde..b07b402 100644 --- a/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs +++ b/src/WopiValidator.Core/Validators/ResponseHeaderValidator.cs @@ -18,8 +18,9 @@ class ResponseHeaderValidator : IValidator public readonly bool IsRequired; public readonly bool ShouldMatch; public readonly bool IsUrl; + public readonly bool IsExcluded; - public ResponseHeaderValidator(string key, string expectedValue, string expectedStateKey, bool isRequired = true, bool shouldMatch = true, bool isUrl = false) + public ResponseHeaderValidator(string key, string expectedValue, string expectedStateKey, bool isRequired = true, bool shouldMatch = true, bool isUrl = false, bool isExcluded = false) { Key = key; DefaultExpectedValue = expectedValue; @@ -27,6 +28,7 @@ public ResponseHeaderValidator(string key, string expectedValue, string expected IsRequired = isRequired; ShouldMatch = shouldMatch; IsUrl = isUrl; + IsExcluded = isExcluded; } public string Name @@ -40,16 +42,21 @@ public ValidationResult Validate(IResponseData data, IResourceManager resourceMa if (!data.Headers.TryGetValue(Key, out headerValue)) { - if (IsRequired) + if (IsExcluded || !IsRequired) { - return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "'{0}' header is not present on the response", Key)); + return new ValidationResult(); } else { - return new ValidationResult(); + return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "'{0}' header is not present on the response", Key)); } } + if (IsExcluded) + { + return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "'{0}' header should not be present on the response", Key)); + } + if (IsUrl) { if (string.IsNullOrEmpty(headerValue)) From c808f52276b3f9db4e042e86989014ed9c3c1a02 Mon Sep 17 00:00:00 2001 From: TingtingWen Date: Mon, 11 Mar 2019 12:48:53 +0800 Subject: [PATCH 4/8] Implement verb 'discovery' based on Tyler's verb design --- src/WopiValidator.Core/ConfigParser.cs | 17 +- src/WopiValidator.Core/DiscoveryListener.cs | 278 ++++++++ .../Factories/ITestCaseFactory.cs | 8 +- .../Factories/TestCaseFactory.cs | 36 +- src/WopiValidator.Core/IFilterOptions.cs | 52 ++ src/WopiValidator.Core/ITestCase.cs | 4 +- src/WopiValidator.Core/TestCase.cs | 17 +- src/WopiValidator.Core/TestCategory.cs | 52 ++ src/WopiValidator.Core/TestExecutionData.cs | 8 - src/WopiValidator.Core/WopiDiscovery.cs | 594 ++++++++++++++++++ src/WopiValidator/DiscoveryOptions.cs | 45 ++ src/WopiValidator/Helpers.cs | 47 ++ src/WopiValidator/ListOptions.cs | 59 ++ src/WopiValidator/Options.cs | 44 -- src/WopiValidator/OptionsBase.cs | 42 ++ src/WopiValidator/Program.cs | 178 +----- src/WopiValidator/RunOptions.cs | 174 +++++ 17 files changed, 1382 insertions(+), 273 deletions(-) create mode 100644 src/WopiValidator.Core/DiscoveryListener.cs create mode 100644 src/WopiValidator.Core/IFilterOptions.cs create mode 100644 src/WopiValidator.Core/TestCategory.cs create mode 100644 src/WopiValidator.Core/WopiDiscovery.cs create mode 100644 src/WopiValidator/DiscoveryOptions.cs create mode 100644 src/WopiValidator/Helpers.cs create mode 100644 src/WopiValidator/ListOptions.cs delete mode 100644 src/WopiValidator/Options.cs create mode 100644 src/WopiValidator/OptionsBase.cs create mode 100644 src/WopiValidator/RunOptions.cs diff --git a/src/WopiValidator.Core/ConfigParser.cs b/src/WopiValidator.Core/ConfigParser.cs index 2fd2a91..b913114 100644 --- a/src/WopiValidator.Core/ConfigParser.cs +++ b/src/WopiValidator.Core/ConfigParser.cs @@ -13,9 +13,9 @@ public static class ConfigParser public static string UsingRestrictedScenario { get; set; } public static string ApplicationId { get; set; } - public static IEnumerable ParseExecutionData(string filePath, TestCategory targetTestCategory, string testGroupName = "") + public static IEnumerable ParseExecutionData(string filePath) { - return ParseExecutionData(filePath, new ResourceManagerFactory(), new TestCaseFactory(), testGroupName, targetTestCategory); + return ParseExecutionData(filePath, new ResourceManagerFactory(), new TestCaseFactory()); } /// @@ -24,9 +24,7 @@ public static IEnumerable ParseExecutionData(string filePath, public static IEnumerable ParseExecutionData( string filePath, IResourceManagerFactory resourceManagerFactory, - ITestCaseFactory testCaseFactory, - string testGroupName, - TestCategory targetTestCategory) + ITestCaseFactory testCaseFactory) { XDocument xDoc = XDocument.Load(filePath); @@ -34,23 +32,22 @@ public static IEnumerable ParseExecutionData( IResourceManager resourceManager = resourceManagerFactory.GetResourceManager(resourcesElement); XElement prereqCasesElement = xDoc.Root.Element("PrereqCases") ?? new XElement("PrereqCases"); - IEnumerable prereqCases = testCaseFactory.GetTestCases(prereqCasesElement, targetTestCategory); + IEnumerable prereqCases = testCaseFactory.GetTestCases(prereqCasesElement); Dictionary prereqCasesDictionary = prereqCases.ToDictionary(e => e.Name); return xDoc.Root.Elements("TestGroup") - .SelectMany(x => GetTestExecutionDataForGroup(x, prereqCasesDictionary, testCaseFactory, resourceManager, targetTestCategory)); + .SelectMany(x => GetTestExecutionDataForGroup(x, prereqCasesDictionary, testCaseFactory, resourceManager)); } private static IEnumerable GetTestExecutionDataForGroup( XElement definition, Dictionary prereqCasesDictionary, ITestCaseFactory testCaseFactory, - IResourceManager resourceManager, - TestCategory targetTestCategory) + IResourceManager resourceManager) { IEnumerable prereqs; IEnumerable groupTestCases; - testCaseFactory.GetTestCases(definition, prereqCasesDictionary, out prereqs, out groupTestCases, targetTestCategory); + testCaseFactory.GetTestCases(definition, prereqCasesDictionary, out prereqs, out groupTestCases); List prereqList = prereqs.ToList(); diff --git a/src/WopiValidator.Core/DiscoveryListener.cs b/src/WopiValidator.Core/DiscoveryListener.cs new file mode 100644 index 0000000..7762c1b --- /dev/null +++ b/src/WopiValidator.Core/DiscoveryListener.cs @@ -0,0 +1,278 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +namespace Microsoft.Office.WopiValidator.Core +{ + public class DiscoveryListener + { + private TcpListener listener = null; + public string proofKey; + public string proofKeyOld; + private string progid = null; + private int port = -1; + + public DiscoveryListener(string proofKey, string proofKeyOld, int port = 80, string progid = "OneNote.Notebook") + { + this.proofKey = proofKey; + this.proofKeyOld = proofKeyOld; + this.progid = progid; + this.port = port; + } + + public void Start() + { + if (listener == null) + { + IPAddress address = IPAddress.Any; + IPEndPoint endPoint = new IPEndPoint(address, this.port); + listener = new TcpListener(endPoint); + } + + try + { + listener.Start(); + listener.BeginAcceptTcpClient(HandleRequest, null); + } + catch (Exception ex) + { + + } + } + + private void HandleRequest(IAsyncResult result) + { + TcpClient client = listener.EndAcceptTcpClient(result); + NetworkStream netstream = client.GetStream(); + + listener.BeginAcceptTcpClient(HandleRequest, null); + + byte[] buffer = new byte[2048]; + + int receivelength = netstream.Read(buffer, 0, 2048); + string requeststring = Encoding.UTF8.GetString(buffer, 0, receivelength); + + if (!requeststring.StartsWith(@"GET /hosting/discovery", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + string xmlBody = GetDiscoveryResponseXmlString(); + + string statusLine = "HTTP/1.1 200 OK\r\n"; + byte[] responseStatusLineBytes = Encoding.UTF8.GetBytes(statusLine); + + string responseHeader = + string.Format( + "Content-Type: text/xml; charset=UTf-8\r\nContent-Length: {0}\r\n", xmlBody.Length); + byte[] responseHeaderBytes = Encoding.UTF8.GetBytes(responseHeader); + + byte[] responseBodyBytes = Encoding.UTF8.GetBytes(xmlBody); + + netstream.Write(responseStatusLineBytes, 0, responseStatusLineBytes.Length); + netstream.Write(responseHeaderBytes, 0, responseHeaderBytes.Length); + netstream.Write(new byte[] { 13, 10 }, 0, 2); + netstream.Write(responseBodyBytes, 0, responseBodyBytes.Length); + client.Close(); + } + + /// + /// Discovery WOPI discovery response xml. It indicates the WOPI client supports 4 types file extensions: ".txt", ".zip", ".one" , ".wopitest" + /// + /// Discovery response xml. + public string GetDiscoveryResponseXmlString() + { + ct_wopidiscovery wopiDiscovery = new ct_wopidiscovery(); + + // Add http and https net zone into the wopiDiscovery + wopiDiscovery.netzone = GetNetZones(); + + // ProofKey element + wopiDiscovery.proofkey = new ct_proofkey(); + wopiDiscovery.proofkey.oldvalue = proofKeyOld; + wopiDiscovery.proofkey.value = proofKey; + string xmlStringOfResponseDiscovery = GetDiscoveryXmlFromDiscoveryObject(wopiDiscovery); + + return xmlStringOfResponseDiscovery; + } + + /// + /// Get internal http and internal https ct_netzone. + /// + /// An array of ct_netzone type instances. + private ct_netzone[] GetNetZones() + { + string fakedWOPIClientActionHostName = string.Format(@"{0}.com", Guid.NewGuid().ToString("N")); + + // HTTP net zone + ct_netzone httpNetZone = GetSingleNetZoneForWopiDiscoveryResponse(st_wopizone.internalhttp, fakedWOPIClientActionHostName); + + // HTTPS Net zone + ct_netzone httpsNetZone = GetSingleNetZoneForWopiDiscoveryResponse(st_wopizone.internalhttps, fakedWOPIClientActionHostName); + + return new ct_netzone[] { httpNetZone, httpsNetZone }; + } + + /// + /// Get a single ct_netzone type instance for current test client. + /// + /// protocol and intended network-type + /// Host name for the action of the WOPI client supports. + /// A ct_netzone type instance. + private ct_netzone GetSingleNetZoneForWopiDiscoveryResponse(st_wopizone netZoneType, string fakedWOPIClientActionHostName) + { + string clientName = Dns.GetHostName(); + + string transportValue = st_wopizone.internalhttp == netZoneType ? Uri.UriSchemeHttp : Uri.UriSchemeHttps; + Random radomInstance = new Random((int)DateTime.UtcNow.Ticks & 0x0000FFFF); + string appName = string.Format( + @"MSWOPITESTAPP {0} _for {1} WOPIServer_{2}", + radomInstance.Next(), + clientName, + netZoneType); + + Uri favIconUrlValue = new Uri( + string.Format(@"{0}://{1}/wv/resources/1033/FavIcon_Word.ico", transportValue, fakedWOPIClientActionHostName), + UriKind.Absolute); + + Uri urlsrcValueOfTextFile = new Uri( + string.Format(@"{0}://{1}/wv/wordviewerframe.aspx?<ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>", transportValue, fakedWOPIClientActionHostName), + UriKind.Absolute); + + Uri urlsrcValueOfZipFile = new Uri( + string.Format(@"{0}://{1}/wv/zipviewerframe.aspx?<ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>", transportValue, fakedWOPIClientActionHostName), + UriKind.Absolute); + + Uri urlsrcValueOfUsingprogid = new Uri( + string.Format(@"{0}://{1}/o/onenoteframe.aspx?edit=0&<ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>", transportValue, fakedWOPIClientActionHostName), + UriKind.Absolute); + + // Setting netZone's sub element's values + ct_appname appElement = new ct_appname(); + appElement.name = appName; + appElement.favIconUrl = favIconUrlValue.OriginalString; + appElement.checkLicense = true; + + // Action element for txt file + ct_wopiaction actionForTextFile = new ct_wopiaction(); + actionForTextFile.name = st_wopiactionvalues.view; + actionForTextFile.ext = "txt"; + actionForTextFile.requires = "containers"; + actionForTextFile.@default = true; + actionForTextFile.urlsrc = urlsrcValueOfTextFile.OriginalString; + + // Action element for txt file + ct_wopiaction formeditactionForTextFile = new ct_wopiaction(); + formeditactionForTextFile.name = st_wopiactionvalues.formedit; + formeditactionForTextFile.ext = "txt"; + formeditactionForTextFile.@default = true; + formeditactionForTextFile.urlsrc = urlsrcValueOfTextFile.OriginalString; + + ct_wopiaction formViewactionForTextFile = new ct_wopiaction(); + formViewactionForTextFile.name = st_wopiactionvalues.formsubmit; + formViewactionForTextFile.ext = "txt"; + formViewactionForTextFile.@default = true; + formViewactionForTextFile.urlsrc = urlsrcValueOfTextFile.OriginalString; + + // Action element for zip file + ct_wopiaction actionForZipFile = new ct_wopiaction(); + actionForZipFile.name = st_wopiactionvalues.view; + actionForZipFile.ext = "zip"; + actionForZipFile.@default = true; + actionForZipFile.urlsrc = urlsrcValueOfZipFile.OriginalString; + + // Action elements for one note. + ct_wopiaction actionForOneNote = new ct_wopiaction(); + actionForOneNote.name = st_wopiactionvalues.view; + actionForOneNote.ext = "one"; + actionForOneNote.requires = "cobalt"; + actionForOneNote.@default = true; + actionForOneNote.urlsrc = urlsrcValueOfUsingprogid.OriginalString; + + // Action elements for one note. + ct_wopiaction actionForOneNoteProg = new ct_wopiaction(); + actionForOneNoteProg.name = st_wopiactionvalues.view; + actionForOneNoteProg.progid = progid; + actionForOneNoteProg.requires = "cobalt,containers"; + actionForOneNoteProg.@default = true; + actionForOneNoteProg.urlsrc = urlsrcValueOfUsingprogid.OriginalString; + + // Action element for wopitest file + ct_wopiaction actionForWopitestFile = new ct_wopiaction(); + actionForWopitestFile.name = st_wopiactionvalues.view; + actionForWopitestFile.ext = "wopitest"; + actionForWopitestFile.requires = "containers"; + actionForWopitestFile.@default = true; + actionForWopitestFile.urlsrc = urlsrcValueOfTextFile.OriginalString; + + ct_wopiaction formeditactionForWopitestFile = new ct_wopiaction(); + formeditactionForWopitestFile.name = st_wopiactionvalues.formedit; + formeditactionForWopitestFile.ext = "wopitest"; + formeditactionForWopitestFile.@default = true; + formeditactionForWopitestFile.urlsrc = urlsrcValueOfTextFile.OriginalString; + + ct_wopiaction formViewactionForWopitestFile = new ct_wopiaction(); + formViewactionForWopitestFile.name = st_wopiactionvalues.formsubmit; + formViewactionForWopitestFile.ext = "wopitest"; + formViewactionForWopitestFile.@default = true; + formViewactionForWopitestFile.urlsrc = urlsrcValueOfTextFile.OriginalString; + + // Add action elements into the app element. + appElement.action = new ct_wopiaction[] { + actionForTextFile, + actionForOneNote, + actionForZipFile, + formeditactionForTextFile, + formViewactionForTextFile, + actionForOneNoteProg, + actionForWopitestFile, + formeditactionForWopitestFile, + formViewactionForWopitestFile }; + + // Add app element into the netzone element. + ct_netzone netZoneInstance = new ct_netzone(); + netZoneInstance.app = new ct_appname[] { appElement }; + netZoneInstance.name = netZoneType; + netZoneInstance.nameSpecified = true; + return netZoneInstance; + } + + /// + /// Get a xml string from a WOPI Discovery type object. + /// + /// ct_wopidiscovery instance. + /// xml string which contains discovery information. + public string GetDiscoveryXmlFromDiscoveryObject(ct_wopidiscovery wopiDiscovery) + { + XmlSerializer xmlSerializer = new XmlSerializer(typeof(ct_wopidiscovery)); + string xmlString = string.Empty; + + MemoryStream memorySteam = new MemoryStream(); + StreamWriter streamWriter = new StreamWriter(memorySteam, Encoding.UTF8); + + // Remove w3c default namespace prefix in serialize process. + XmlSerializerNamespaces nameSpaceInstance = new XmlSerializerNamespaces(); + nameSpaceInstance.Add(string.Empty, string.Empty); + xmlSerializer.Serialize(streamWriter, wopiDiscovery, nameSpaceInstance); + + // Read the MemoryStream to output the xml string. + memorySteam.Position = 0; + using (StreamReader streamReader = new StreamReader(memorySteam)) + { + xmlString = streamReader.ReadToEnd(); + } + + streamWriter.Dispose(); + memorySteam.Dispose(); + + // Format the serialized xml string. + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xmlString); + return xmlDoc.OuterXml; + } + } +} diff --git a/src/WopiValidator.Core/Factories/ITestCaseFactory.cs b/src/WopiValidator.Core/Factories/ITestCaseFactory.cs index be16cf3..a14b4b1 100644 --- a/src/WopiValidator.Core/Factories/ITestCaseFactory.cs +++ b/src/WopiValidator.Core/Factories/ITestCaseFactory.cs @@ -12,11 +12,9 @@ public interface ITestCaseFactory /// Parse XML run configuration to get list of Test Cases /// /// ]]> element from run configuration XML file. - /// This helps to select the correct test cases. /// Collection of Test Cases. IEnumerable GetTestCases( - XElement definitions, - TestCategory targetTestCategory); + XElement definitions); /// /// Parse XML run configuration testgroup element to get a list of TestCases. @@ -25,11 +23,9 @@ IEnumerable GetTestCases( /// Dictionary of name to testcase already parsed from ]]> element from run configuration file. /// PrereqCases applicable to testcases in this test group. /// TestCases in this test group. - /// This helps to select the correct test cases. void GetTestCases(XElement definition, Dictionary prereqCasesDictionary, out IEnumerable prereqTests, - out IEnumerable groupTests, - TestCategory targetTestCategory); + out IEnumerable groupTests); } } diff --git a/src/WopiValidator.Core/Factories/TestCaseFactory.cs b/src/WopiValidator.Core/Factories/TestCaseFactory.cs index a71333c..910c558 100644 --- a/src/WopiValidator.Core/Factories/TestCaseFactory.cs +++ b/src/WopiValidator.Core/Factories/TestCaseFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; @@ -12,23 +12,22 @@ namespace Microsoft.Office.WopiValidator.Core.Factories { public class TestCaseFactory : ITestCaseFactory { - public IEnumerable GetTestCases(XElement definitions, TestCategory targetTestCategory) + public IEnumerable GetTestCases(XElement definitions) { - return definitions.Elements("TestCase").Where(x => DoesTestCategoryMatchTargetTestCategory(x, targetTestCategory)).Select(x => GetTestCase(x)); + return definitions.Elements("TestCase").Select(x => GetTestCase(x)); } public void GetTestCases( XElement definition, Dictionary prereqCasesDictionary, out IEnumerable prereqTests, - out IEnumerable groupTests, - TestCategory targetTestCategory) + out IEnumerable groupTests) { XElement prereqsElement = definition.Element("PrereqTests") ?? new XElement("PrereqTests"); prereqTests = GetPrereqTests(prereqsElement, prereqCasesDictionary); XElement testCasesElement = definition.Element("TestCases") ?? new XElement("TestCases"); - groupTests = GetTestCases(testCasesElement, targetTestCategory); + groupTests = GetTestCases(testCasesElement); } private static IEnumerable GetPrereqTests(XElement definition, Dictionary prereqsDictionary) @@ -86,31 +85,6 @@ private static ITestCase GetTestCase(XElement definition) return testCase; } - /// - /// This function helps ensure that, - /// We are getting all the TestCases if the targetTestCategory is set to "All" - /// We are getting all the TestCases with "WopiCore" as their "Category", regardless of the targetTestCategory. - /// The rest of the test cases are picked up if their "Category" matches the targetTestCategory. - /// - private static bool DoesTestCategoryMatchTargetTestCategory(XElement definition, TestCategory targetTestCategory) - { - string category = (string)definition.Attribute("Category"); - string name = (string)definition.Attribute("Name"); - - if (string.IsNullOrEmpty(category)) - { - throw new Exception(string.Format(CultureInfo.InvariantCulture, "The category of {0} TestCase is empty", name)); - } - - TestCategory testCaseCategory; - if (!Enum.TryParse(category, true /* ignoreCase */, out testCaseCategory)) - { - throw new Exception(string.Format(CultureInfo.InvariantCulture, "The category of {0} TestCase is invalid", name)); - } - - return targetTestCategory == TestCategory.All || testCaseCategory == TestCategory.WopiCore || targetTestCategory == testCaseCategory; - } - /// /// Condenses a multi-line string into a more compact form. /// diff --git a/src/WopiValidator.Core/IFilterOptions.cs b/src/WopiValidator.Core/IFilterOptions.cs new file mode 100644 index 0000000..f23fa7d --- /dev/null +++ b/src/WopiValidator.Core/IFilterOptions.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.Office.WopiValidator.Core +{ + public interface IFilterOptions + { + string TestName { get; set; } + TestCategory? TestCategory { get; set; } + string TestGroup { get; set; } + } + + public static class IFilterOptionsExtensions + { + public static IEnumerable ApplyFilters(this IEnumerable testData, IFilterOptions options) + { + var toReturn = testData; + + // Filter by test name + if (!string.IsNullOrEmpty(options.TestName)) + { + toReturn = toReturn.Where(t => t.TestCase.Name == options.TestName); + if (toReturn.Count() == 1) + { + return toReturn; + } + } + + if (options.TestCategory != null) + { + toReturn = toReturn.Where(t => t.TestCategoryMatches(options.TestCategory) == true); + } + + if (!string.IsNullOrEmpty(options.TestGroup)) + { + toReturn = toReturn.Where(t => t.TestGroupName.Equals(options.TestGroup, StringComparison.InvariantCultureIgnoreCase)); + } + + return toReturn; + } + + public static IEnumerable ApplyToData(this IFilterOptions filters, IEnumerable testData) + { + return testData.ApplyFilters(filters); + } + } +} diff --git a/src/WopiValidator.Core/ITestCase.cs b/src/WopiValidator.Core/ITestCase.cs index 2e82368..4c39e46 100644 --- a/src/WopiValidator.Core/ITestCase.cs +++ b/src/WopiValidator.Core/ITestCase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Collections.Generic; @@ -28,5 +28,7 @@ public interface ITestCase bool DeleteDocumentOnTearDown { get; } string Category { get; } + + TestCategory TestCategory { get; } } } diff --git a/src/WopiValidator.Core/TestCase.cs b/src/WopiValidator.Core/TestCase.cs index 2d04721..18aaf1b 100644 --- a/src/WopiValidator.Core/TestCase.cs +++ b/src/WopiValidator.Core/TestCase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; @@ -10,7 +10,7 @@ namespace Microsoft.Office.WopiValidator.Core /// /// Represents a single test case. /// - class TestCase : ITestCase + internal class TestCase : ITestCase { public TestCase( string resourceId, @@ -57,9 +57,20 @@ public TestCase( public string ResourceId { get; private set; } public string UiScreenShot { get; set; } public string DocumentationLink { get; set; } - public string FailMessage {get; set; } + public string FailMessage { get; set; } public bool UploadDocumentOnSetup { get; private set; } public bool DeleteDocumentOnTearDown { get; private set; } public string Category { get; private set; } + public TestCategory TestCategory + { + get + { + if (!Enum.TryParse(Category, true /* ignoreCase */, out TestCategory testCategory)) + { + throw new Exception($"Invalid TestCategory: {Category}"); + } + return testCategory; + } + } } } diff --git a/src/WopiValidator.Core/TestCategory.cs b/src/WopiValidator.Core/TestCategory.cs new file mode 100644 index 0000000..9bc0009 --- /dev/null +++ b/src/WopiValidator.Core/TestCategory.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Xml.Linq; + +namespace Microsoft.Office.WopiValidator.Core +{ + public enum TestCategory + { + All = 0, + WopiCore = 1, + OfficeOnline = 2, + OfficeNativeClient = 3 + } + + internal static class TestCategoryExtensions + { + /// + /// This function applies the rules of category filtering. If this returns true, the test should be included based on the category. + /// + /// The rules to apply are as follows: + /// If the filterCategory is All or null, the test should be included. + /// If the test's category is WopiCore, it should be included (all WopiCore tests should always be included). + /// If the test's category matches the filterCategory, it should be included. + /// + internal static bool TestCategoryMatches(this TestExecutionData testData, TestCategory? filterCategory) + { + return TestCategoryMatches(testData.TestCase, filterCategory); + } + + /// + /// This function applies the rules of category filtering. If this returns true, the test should be included based on the category. + /// + /// The rules to apply are as follows: + /// If the filterCategory is All or null, the test should be included. + /// If the test's category is WopiCore, it should be included (all WopiCore tests should always be included). + /// If the test's category matches the filterCategory, it should be included. + /// + internal static bool TestCategoryMatches(this ITestCase testCase, TestCategory? category) + { + if (!category.HasValue || + category == TestCategory.All || + testCase.TestCategory == TestCategory.WopiCore) + { + return true; + } + + return testCase.TestCategory == category; + } + } +} diff --git a/src/WopiValidator.Core/TestExecutionData.cs b/src/WopiValidator.Core/TestExecutionData.cs index 9ee5b93..d45614e 100644 --- a/src/WopiValidator.Core/TestExecutionData.cs +++ b/src/WopiValidator.Core/TestExecutionData.cs @@ -7,14 +7,6 @@ namespace Microsoft.Office.WopiValidator.Core { - public enum TestCategory - { - All = 0, - WopiCore = 1, - OfficeOnline = 2, - OfficeNativeClient = 3 - } - public class TestExecutionData { internal TestExecutionData(ITestCase testCase, IEnumerable prereqCases, IResourceManager resourceManager, string testGroupName) diff --git a/src/WopiValidator.Core/WopiDiscovery.cs b/src/WopiValidator.Core/WopiDiscovery.cs new file mode 100644 index 0000000..c7f9b10 --- /dev/null +++ b/src/WopiValidator.Core/WopiDiscovery.cs @@ -0,0 +1,594 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.Xml.Serialization; + +// +// This source code was auto-generated by xsd, Version=4.6.1055.0. +// + +namespace Microsoft.Office.WopiValidator.Core +{ + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "ct_wopi-discovery")] + [System.Xml.Serialization.XmlRootAttribute("wopi-discovery", Namespace = "", IsNullable = false)] + public partial class ct_wopidiscovery + { + + private ct_netzone[] netzoneField; + + private ct_proofkey proofkeyField; + + /// + [System.Xml.Serialization.XmlElementAttribute("net-zone")] + public ct_netzone[] netzone + { + get + { + return this.netzoneField; + } + set + { + this.netzoneField = value; + } + } + + /// + [System.Xml.Serialization.XmlElementAttribute("proof-key")] + public ct_proofkey proofkey + { + get + { + return this.proofkeyField; + } + set + { + this.proofkeyField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "ct_net-zone")] + public partial class ct_netzone + { + + private ct_appname[] appField; + + private st_wopizone nameField; + + private bool nameFieldSpecified; + + /// + [System.Xml.Serialization.XmlElementAttribute("app")] + public ct_appname[] app + { + get + { + return this.appField; + } + set + { + this.appField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public st_wopizone name + { + get + { + return this.nameField; + } + set + { + this.nameField = value; + } + } + + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + public bool nameSpecified + { + get + { + return this.nameFieldSpecified; + } + set + { + this.nameFieldSpecified = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "ct_app-name")] + public partial class ct_appname + { + + private ct_wopiaction[] actionField; + + private string nameField; + + private string favIconUrlField; + + private bool checkLicenseField; + + public ct_appname() + { + this.checkLicenseField = false; + } + + /// + [System.Xml.Serialization.XmlElementAttribute("action")] + public ct_wopiaction[] action + { + get + { + return this.actionField; + } + set + { + this.actionField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string name + { + get + { + return this.nameField; + } + set + { + this.nameField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string favIconUrl + { + get + { + return this.favIconUrlField; + } + set + { + this.favIconUrlField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(false)] + public bool checkLicense + { + get + { + return this.checkLicenseField; + } + set + { + this.checkLicenseField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "ct_wopi-action")] + public partial class ct_wopiaction + { + + private st_wopiactionvalues nameField; + + private bool defaultField; + + private string requiresField; + + private string urlsrcField; + + private string extField; + + private string progidField; + + private string newprogidField; + + private string newextField; + + private bool useParentField; + + private string targetextField; + + public ct_wopiaction() + { + this.defaultField = false; + this.useParentField = false; + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public st_wopiactionvalues name + { + get + { + return this.nameField; + } + set + { + this.nameField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(false)] + public bool @default + { + get + { + return this.defaultField; + } + set + { + this.defaultField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string requires + { + get + { + return this.requiresField; + } + set + { + this.requiresField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string urlsrc + { + get + { + return this.urlsrcField; + } + set + { + this.urlsrcField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string ext + { + get + { + return this.extField; + } + set + { + this.extField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string progid + { + get + { + return this.progidField; + } + set + { + this.progidField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string newprogid + { + get + { + return this.newprogidField; + } + set + { + this.newprogidField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string newext + { + get + { + return this.newextField; + } + set + { + this.newextField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(false)] + public bool useParent + { + get + { + return this.useParentField; + } + set + { + this.useParentField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string targetext + { + get + { + return this.targetextField; + } + set + { + this.targetextField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "st_wopi-action-values")] + public enum st_wopiactionvalues + { + + /// + view, + + /// + edit, + + /// + mobileView, + + /// + embedview, + + /// + embededit, + + /// + mobileclient, + + /// + present, + + /// + presentservice, + + /// + attend, + + /// + attendservice, + + /// + editnew, + + /// + imagepreview, + + /// + interactivepreview, + + /// + formsubmit, + + /// + formedit, + + /// + rest, + + /// + preloadview, + + /// + preloadedit, + + /// + rtc, + + /// + getinfo, + + /// + convert, + + /// + syndicate, + + /// + legacywebservice, + + /// + collab, + + /// + formpreview, + + /// + documentchat, + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "ct_proof-key")] + public partial class ct_proofkey + { + + private string exponentField; + + private string modulusField; + + private string oldexponentField; + + private string oldmodulusField; + + private string oldvalueField; + + private string valueField; + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string exponent + { + get + { + return this.exponentField; + } + set + { + this.exponentField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string modulus + { + get + { + return this.modulusField; + } + set + { + this.modulusField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string oldexponent + { + get + { + return this.oldexponentField; + } + set + { + this.oldexponentField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string oldmodulus + { + get + { + return this.oldmodulusField; + } + set + { + this.oldmodulusField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string oldvalue + { + get + { + return this.oldvalueField; + } + set + { + this.oldvalueField = value; + } + } + + /// + [System.Xml.Serialization.XmlAttributeAttribute()] + public string value + { + get + { + return this.valueField; + } + set + { + this.valueField = value; + } + } + } + + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.1055.0")] + [System.SerializableAttribute()] + [System.Xml.Serialization.XmlTypeAttribute(TypeName = "st_wopi-zone")] + public enum st_wopizone + { + + /// + [System.Xml.Serialization.XmlEnumAttribute("internal-http")] + internalhttp, + + /// + [System.Xml.Serialization.XmlEnumAttribute("internal-https")] + internalhttps, + + /// + [System.Xml.Serialization.XmlEnumAttribute("external-http")] + externalhttp, + + /// + [System.Xml.Serialization.XmlEnumAttribute("external-https")] + externalhttps, + } +} diff --git a/src/WopiValidator/DiscoveryOptions.cs b/src/WopiValidator/DiscoveryOptions.cs new file mode 100644 index 0000000..20cfe8f --- /dev/null +++ b/src/WopiValidator/DiscoveryOptions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CommandLine; +using Microsoft.Office.WopiValidator.Core; +using System; + +namespace Microsoft.Office.WopiValidator +{ + /// + /// Options for the discovery command. + /// + [Verb("discovery", HelpText = "Provide XML that describes the supported abilities of this WOPI client")] + internal class DiscoveryOptions : OptionsBase + { + [Option("port", Required = false, HelpText = "Port number used for discovery")] + public string Port { get; set; } + + [Option("progid", Required = false, HelpText = "progid that identifies a folder as being associated with a specific application")] + public string ProgId { get; set; } + + [Option('p', "ProofKey", Required = true, HelpText = "Public key used to decrypt X-WOPI-Proof HTTP header")] + public string ProofKey { get; set; } + + [Option('o', "ProofKeyOld", Required = true, HelpText = "Public key used to decrypt X-WOPI-ProofOld HTTP header")] + public string ProofKeyOld { get; set; } + + public static ExitCode DiscoveryCommand(DiscoveryOptions options) + { + DiscoveryListener listener = new DiscoveryListener(options.ProofKey, options.ProofKeyOld, Convert.ToInt32(options.Port)); + listener.Start(); + + return ExitCode.Success; + } + + private static TestCaseExecutor GetTestCaseExecutor(TestExecutionData testExecutionData, RunOptions options, TestCategory inputTestCategory) + { + bool officeNative = inputTestCategory == TestCategory.OfficeNativeClient || + testExecutionData.TestCase.TestCategory == TestCategory.OfficeNativeClient; + string userAgent = officeNative ? Constants.HeaderValues.OfficeNativeClientUserAgent : null; + + return new TestCaseExecutor(testExecutionData, options.WopiEndpoint, options.AccessToken, options.AccessTokenTtl, userAgent); + } + } +} diff --git a/src/WopiValidator/Helpers.cs b/src/WopiValidator/Helpers.cs new file mode 100644 index 0000000..dd16d33 --- /dev/null +++ b/src/WopiValidator/Helpers.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Office.WopiValidator +{ + internal static class Helpers + { + internal static void WriteToConsole(string message, ConsoleColor color, int indentLevel = 0) + { + ConsoleColor currentColor = Console.ForegroundColor; + Console.ForegroundColor = color; + string indent = new string(' ', indentLevel * 2); + Console.Write(indent + message); + Console.ForegroundColor = currentColor; + } + + internal static bool ContainsAny(this HashSet set, params T[] items) + { + return set.Intersect(items).Any(); + } + + internal static string StripNewLines(this string str) + { + StringBuilder sb = new StringBuilder(str); + bool newLineAtStart = str.StartsWith(Environment.NewLine); + bool newLineAtEnd = str.EndsWith(Environment.NewLine); + sb.Replace(Environment.NewLine, " "); + + if (newLineAtStart) + { + sb.Insert(0, Environment.NewLine); + } + + if (newLineAtEnd) + { + sb.Append(Environment.NewLine); + } + return sb.ToString(); + } + + } +} diff --git a/src/WopiValidator/ListOptions.cs b/src/WopiValidator/ListOptions.cs new file mode 100644 index 0000000..1c976f3 --- /dev/null +++ b/src/WopiValidator/ListOptions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CommandLine; +using Microsoft.Office.WopiValidator.Core; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Office.WopiValidator +{ + /// + /// Options for the list command. + /// + [Verb("list", HelpText = "List tests that match the filter criteria")] + internal class ListOptions : OptionsBase + { + [Option('t', "tags", Required = false, HelpText = "Filter to tests with these tags")] + public IEnumerable Tags { get; set; } + + internal static ExitCode ListCommand(ListOptions options) + { + // get run configuration from XML + IEnumerable testData = ConfigParser.ParseExecutionData(options.RunConfigurationFilePath); + + // Filter the tests + IEnumerable executionData = testData.ApplyFilters(options); + + // Create executor groups + var executorGroups = executionData.GroupBy(d => d.TestGroupName) + .Select(g => new + { + Name = g.Key, + TestData = g.Select(x => x) + }); + + + foreach (var group in executorGroups) + { + Helpers.WriteToConsole($"\nTest group: {group.Name}\n", ConsoleColor.White); + + foreach(var test in group.TestData) + { + Helpers.WriteToConsole($"{test.TestCase.Name}\n", ConsoleColor.Blue); + } + } + return ExitCode.Failure; + } + + //private static TestCaseExecutor GetTestCaseExecutor(TestExecutionData testExecutionData, ListOptions options, TestCategory inputTestCategory) + //{ + // bool officeNative = inputTestCategory == TestCategory.OfficeNativeClient || + // testExecutionData.TestCase.TestCategory == TestCategory.OfficeNativeClient; + // string userAgent = officeNative ? Constants.HeaderValues.OfficeNativeClientUserAgent : null; + + // return new TestCaseExecutor(testExecutionData, options.WopiEndpoint, options.AccessToken, options.AccessTokenTtl, userAgent); + //} + } +} diff --git a/src/WopiValidator/Options.cs b/src/WopiValidator/Options.cs deleted file mode 100644 index 417f63b..0000000 --- a/src/WopiValidator/Options.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using CommandLine; -using Microsoft.Office.WopiValidator.Core; - -namespace Microsoft.Office.WopiValidator -{ - /// - /// Represents set of command line arguments that can be used to modify behavior of the application. - /// - class Options - { - [Option('w', "wopisrc", Required = true, HelpText = "WopiSrc URL for a wopitest file")] - public string WopiEndpoint { get; set; } - - [Option('t', "token", Required = true, HelpText = "WOPI access token")] - public string AccessToken { get; set; } - - [Option('l', "token_ttl", Required = true, HelpText = "WOPI access token ttl")] - public long AccessTokenTtl { get; set; } - - [Option("UsingRestrictedScenario", Required = false, HelpText = "Header 'X-WOPI-UsingRestrictedScenario' used Restricted scenario")] - public string UsingRestrictedScenario { get; set; } - - [Option("ApplicationId", Required = false, HelpText = "Header 'X-WOPI-ApplicationId' indicates id of an application stored in secure store")] - public string ApplicationId { get; set; } - - [Option('c', "config", Required = false, Default = "TestCases.xml", HelpText = "Path to XML file with test definitions")] - public string RunConfigurationFilePath { get; set; } - - [Option('g', "testgroup", Required = false, HelpText = "Run only the tests in the specified group (cannot be used with testname)")] - public string TestGroup { get; set; } - - [Option('n', "testname", Required = false, HelpText = "Run only the test specified (cannot be used with testgroup)")] - public string TestName { get; set; } - - [Option('e', "testcategory", Required = false, Default = TestCategory.All, HelpText = "Run only the tests in the specified category")] - public TestCategory TestCategory { get; set; } - - [Option('s', "ignore-skipped", Required = false, HelpText = "Don't output any info about skipped tests.")] - public bool IgnoreSkipped { get; set; } - } -} diff --git a/src/WopiValidator/OptionsBase.cs b/src/WopiValidator/OptionsBase.cs new file mode 100644 index 0000000..0b3bc49 --- /dev/null +++ b/src/WopiValidator/OptionsBase.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CommandLine; +using Microsoft.Office.WopiValidator.Core; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Office.WopiValidator +{ + /// + /// Options shared by all commands + /// + internal abstract class OptionsBase : IFilterOptions + { + [Option('c', "config", Required = false, Default = "TestCases.xml", HelpText = "Path to XML file with test definitions")] + public string RunConfigurationFilePath { get; set; } + + [Option('g', "testgroup", Required = false, HelpText = "Run only the tests in the specified group (cannot be used with testname)")] + public string TestGroup { get; set; } + + [Option('n', "testname", Required = false, HelpText = "Run only the test specified (cannot be used with testgroup)")] + public string TestName { get; set; } + + [Option('e', "testcategory", Required = false, Default = Core.TestCategory.All, HelpText = "Run only the tests in the specified category")] + public TestCategory TestCategory { get; set; } + + TestCategory? IFilterOptions.TestCategory + { + get { return TestCategory; } + set + { + if (!value.HasValue) + { + TestCategory = TestCategory.All; + } + TestCategory = value.Value; + } + } + } +} diff --git a/src/WopiValidator/Program.cs b/src/WopiValidator/Program.cs index 5b0387e..24011c6 100644 --- a/src/WopiValidator/Program.cs +++ b/src/WopiValidator/Program.cs @@ -2,14 +2,8 @@ // Licensed under the MIT License. using CommandLine; -using Microsoft.Office.WopiValidator.Core; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Text; namespace Microsoft.Office.WopiValidator { @@ -21,19 +15,6 @@ internal enum ExitCode internal class Program { - private static TestCaseExecutor GetTestCaseExecutor(TestExecutionData testExecutionData, Options options, TestCategory inputTestCategory) - { - TestCategory testCategory; - if (!Enum.TryParse(testExecutionData.TestCase.Category, true /* ignoreCase */, out testCategory)) - { - throw new Exception(string.Format(CultureInfo.InvariantCulture, "Invalid TestCategory for TestCase : {0}", testExecutionData.TestCase.Name)); - } - - string userAgent = (inputTestCategory == TestCategory.OfficeNativeClient || testCategory == TestCategory.OfficeNativeClient) ? Constants.HeaderValues.OfficeNativeClientUserAgent : null; - - return new TestCaseExecutor(testExecutionData, options.WopiEndpoint, options.AccessToken, options.AccessTokenTtl, userAgent); - } - private static int Main(string[] args) { // Wrapping all logic in a top-level Exception handler to ensure that exceptions are @@ -41,168 +22,25 @@ private static int Main(string[] args) ExitCode exitCode = ExitCode.Success; try { - exitCode = Parser.Default.ParseArguments(args) - .MapResult( - (Options options) => Execute(options), - parseErrors => ExitCode.Failure); + exitCode = Parser.Default.ParseArguments(args) + .MapResult( + (RunOptions options) => RunOptions.RunCommand(options), + (ListOptions options) => ListOptions.ListCommand(options), + (DiscoveryOptions options) => DiscoveryOptions.DiscoveryCommand(options), + parseErrors => ExitCode.Failure); } catch (Exception ex) { - WriteToConsole(ex.ToString(), ConsoleColor.Red); + Helpers.WriteToConsole(ex.ToString(), ConsoleColor.Red); exitCode = ExitCode.Failure; } if (Debugger.IsAttached) { - WriteToConsole("Press any key to exit", ConsoleColor.White); + Helpers.WriteToConsole("Press any key to exit", ConsoleColor.White); Console.ReadLine(); } return (int)exitCode; } - - private static ExitCode Execute(Options options) - { - ConfigParser.UsingRestrictedScenario = options.UsingRestrictedScenario; - ConfigParser.ApplicationId = options.ApplicationId; - - // get run configuration from XML - IEnumerable testData = ConfigParser.ParseExecutionData(options.RunConfigurationFilePath, options.TestCategory); - - if (!String.IsNullOrEmpty(options.TestGroup)) - { - testData = testData.Where(d => d.TestGroupName == options.TestGroup); - } - - IEnumerable executionData; - if (!String.IsNullOrWhiteSpace(options.TestName)) - { - executionData = new TestExecutionData[] { TestExecutionData.GetDataForSpecificTest(testData, options.TestName) }; - } - else - { - executionData = testData; - } - - // Create executor groups - var executorGroups = executionData.GroupBy(d => d.TestGroupName) - .Select(g => new - { - Name = g.Key, - Executors = g.Select(x => GetTestCaseExecutor(x, options, options.TestCategory)) - }); - - ConsoleColor baseColor = ConsoleColor.White; - HashSet resultStatuses = new HashSet(); - foreach (var group in executorGroups) - { - WriteToConsole($"\nTest group: {group.Name}\n", ConsoleColor.White); - - // define execution query - evaluation is lazy; test cases are executed one at a time - // as you iterate over returned collection - var results = group.Executors.Select(x => x.Execute()); - - // iterate over results and print success/failure indicators into console - foreach (TestCaseResult testCaseResult in results) - { - resultStatuses.Add(testCaseResult.Status); - switch (testCaseResult.Status) - { - case ResultStatus.Pass: - baseColor = ConsoleColor.Green; - WriteToConsole($"Pass: {testCaseResult.Name}\n", baseColor, 1); - break; - - case ResultStatus.Skipped: - baseColor = ConsoleColor.Yellow; - if (!options.IgnoreSkipped) - { - WriteToConsole($"Skipped: {testCaseResult.Name}\n", baseColor, 1); - } - break; - - case ResultStatus.Fail: - default: - baseColor = ConsoleColor.Red; - WriteToConsole($"Fail: {testCaseResult.Name}\n", baseColor, 1); - break; - } - - if (testCaseResult.Status == ResultStatus.Fail || - (testCaseResult.Status == ResultStatus.Skipped && !options.IgnoreSkipped)) - { - foreach (var request in testCaseResult.RequestDetails) - { - var responseStatus = (HttpStatusCode)request.ResponseStatusCode; - var color = request.ValidationFailures.Count == 0 ? ConsoleColor.DarkGreen : baseColor; - WriteToConsole($"{request.Name}, response code: {request.ResponseStatusCode} {responseStatus}\n", color, 2); - foreach (var failure in request.ValidationFailures) - { - foreach (var error in failure.Errors) - WriteToConsole($"{error.StripNewLines()}\n", baseColor, 3); - } - } - - WriteToConsole($"Re-run command: .\\wopivalidator.exe -n {testCaseResult.Name} -w {options.WopiEndpoint} -t {options.AccessToken} -l {options.AccessTokenTtl}\n", baseColor, 2); - Console.WriteLine(); - } - } - - if (options.IgnoreSkipped && !resultStatuses.ContainsAny(ResultStatus.Pass, ResultStatus.Fail)) - { - WriteToConsole($"All tests skipped.\n", baseColor, 1); - } - } - - // If skipped tests are ignored, don't consider them when determining whether the test run passed or failed - if (options.IgnoreSkipped) - { - if (resultStatuses.Contains(ResultStatus.Fail)) - { - return ExitCode.Failure; - } - } - // Otherwise consider skipped tests as failures - else if (resultStatuses.ContainsAny(ResultStatus.Skipped, ResultStatus.Fail)) - { - return ExitCode.Failure; - } - return ExitCode.Success; - } - - private static void WriteToConsole(string message, ConsoleColor color, int indentLevel = 0) - { - ConsoleColor currentColor = Console.ForegroundColor; - Console.ForegroundColor = color; - string indent = new string(' ', indentLevel * 2); - Console.Write(indent + message); - Console.ForegroundColor = currentColor; - } - } - - internal static class ExtensionMethods - { - internal static bool ContainsAny(this HashSet set, params T[] items) - { - return set.Intersect(items).Any(); - } - - internal static string StripNewLines(this string str) - { - StringBuilder sb = new StringBuilder(str); - bool newLineAtStart = str.StartsWith(Environment.NewLine); - bool newLineAtEnd = str.EndsWith(Environment.NewLine); - sb.Replace(Environment.NewLine, " "); - - if (newLineAtStart) - { - sb.Insert(0, Environment.NewLine); - } - - if (newLineAtEnd) - { - sb.Append(Environment.NewLine); - } - return sb.ToString(); - } } } diff --git a/src/WopiValidator/RunOptions.cs b/src/WopiValidator/RunOptions.cs new file mode 100644 index 0000000..8addc8d --- /dev/null +++ b/src/WopiValidator/RunOptions.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using CommandLine; +using Microsoft.Office.WopiValidator.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Globalization; +using System.Text; + +namespace Microsoft.Office.WopiValidator +{ + /// + /// Represents set of command line arguments that can be used to modify behavior of the application. + /// + [Verb("run", HelpText = "Run tests.")] + internal class RunOptions : OptionsBase + { + [Option('w', "wopisrc", Required = true, HelpText = "WopiSrc URL for a wopitest file")] + public string WopiEndpoint { get; set; } + + [Option('t', "token", Required = true, HelpText = "WOPI access token")] + public string AccessToken { get; set; } + + [Option('l', "token_ttl", Required = true, HelpText = "WOPI access token ttl")] + public long AccessTokenTtl { get; set; } + + [Option("UsingRestrictedScenario", Required = false, HelpText = "Header 'X-WOPI-UsingRestrictedScenario' used Restricted scenario")] + public string UsingRestrictedScenario { get; set; } + + [Option("ApplicationId", Required = false, HelpText = "Header 'X-WOPI-ApplicationId' indicates id of an application stored in secure store")] + public string ApplicationId { get; set; } + + [Option('p', "ProofKey", Required = false, HelpText = "Public key used to decrypt X-WOPI-Proof HTTP header")] + public string ProofKey { get; set; } + + [Option('o', "ProofKeyOld", Required = false, HelpText = "Public key used to decrypt X-WOPI-ProofOld HTTP header")] + public string ProofKeyOld { get; set; } + + [Option('s', "ignore-skipped", Required = false, HelpText = "Don't output any info about skipped tests.")] + public bool IgnoreSkipped { get; set; } + + /// + /// A string represents the unique key-pairs which is match the Asymmetric encrypt algorithm. It is used for "X-WOPI-Proof" header. + /// + private const string AsymmetricEncryptKeypairsOfCurrent = @"BwIAAACkAABSU0EyAAQAAAEAAQBxwXpxCIYvyvtnFmflVBmFEYpn/hhuZCqVH1PhqnAQr/ONAVkiONeMTToP7n2kUi1wntw5MbMaoIPWoNejZOLDgIVUqfjCOH2EbXOMmp6zTN35zAYbGZ3XWgLtmVkHIU60IQGvl7rOEHnEJ4v7a7Q2s6r4IOdVqFMS1T2YpmT6r5W6lKKyvjRKtwu3RClOcoNR9cpQNlzRqP6Tsl1B2UHAlRhNOBB7jEcttjdFFr/C1M6e5+XpDIhDlJt4BMODGN1tEAYZ71eMpnXkUBpUewXyaGZSSi5H/1cSki0srmONVNj7amPdk8QdEnlz+WnhLSjjeyNBHCVhhtVYaKfxd8LLnfRqGmPxGkcLsbTTL9Ngv1fNBFGCq45NsSNq4MGp+eja+2oJSE6duSpjeSOapLQ/vPcfkVZQP3AmOvEvxKV3wHUnzIlbvaklNhbg2LAJOZ2lT7bWVHfrgQ06lcjlapAaAoSNbPBhhhSOdUPrdRy4ebAhoUiriJiXoMNy9NS6GHqploWkCU/HDdVBTYS/yjqVFOAbhQA4edSgzyIT5P1tyIWImQ7ziE+7gFMPbXosoWmDL/iSmaAyuSU3lqun3lrBIWTXuHul48OgGadg2k2c6PBX0y7fqgBfEOAOFSii+c/d1G2umh321WigSaeg0VrsPO5pH1MbFOOFS/ZjMGWmZSYZfopkIOCqXL1UbiNwsIa0OG1FxrIMI9zgQt1aGPV4NK7unMeBz/t9uO2Y9nsrztQnYuje6K0cPTK+HlOExao="; + + /// + /// A string represents the unique key-pairs which is match the Asymmetric encrypt algorithm. It is used for "X-WOPI-ProofOld" header. + /// + private const string AsymmetricEncryptKeypairsOfOld = @"BwIAAACkAABSU0EyAAQAAAEAAQC3T8ExrB2fjcvpVJF7ZYbAh9yfsHsXMcqHa/0i0ncEdoejYr1s1NMbZtGbautAmDH2Q5/dUoZ6UHvymDxGh3VypfCHg7heRaPoeBLBrKyhIbG8oy2KUlpUSBGi9s2ZTb4tMyef8ZTA+f5jneAIZDC8U4DZF0mifHJtXrQHqSY9kkHv/7WdvxVsoLToq78tX3CZDR5btKGsOJD8qjwJ0Tthsq3l79rhh39kxci9YzMKVK4rQVlUSAopVtRuWXa2j8X3eOs/YEmObMpUxEPK6yZk8Rj9UYLMm7rmO+iB0vMrTAkKOI7csfDEg+XZKSM3tRmkOJHPyUlxgeLBcR7PDX+9gVPEILISnGGj+2qsHT+ywcmC7/dYiwjhj/VXgzZvl2KjDbfaa2N10CQN6MnBMXzawvsnrSA4x8UGmzLuzLiEuTlg6Ed1WuL7uv3p+Rs9NX/yuj2jPuuFWDNNWlqGb4455eERQv73YXLNFMGRc87peBib/gN2YUO5suH8J5NzyUOGA0YPEvNWHIZo0k0JcJWzY3zVLENdKxHZFjc60bfRbAM0wTl20aWEvUEUBPOikuRmeEFveJNYSGUvcscIefhtgdTzsafiOpUW/2nR+tpxoIQM4gFZXIs85838T46kNCX1M/RBLjailtbAuyjOhA8Dixjm2jL4nndpkKPRl5ZC/pmYnUZGVd/nbh7h9rKglRSLYFzc3+OnPI9Mj0UoDPy72SE4DO6e4QN7npbklrWAKSqGUF4DNT4Y7iw5pibqSw4="; + + public static ExitCode RunCommand(RunOptions options) + { + // get run configuration from XML + IEnumerable testData = ConfigParser.ParseExecutionData(options.RunConfigurationFilePath); + + // Filter the tests + IEnumerable executionData = testData.ApplyFilters(options); + + ConfigParser.UsingRestrictedScenario = options.UsingRestrictedScenario; + ConfigParser.ApplicationId = options.ApplicationId; + + RSACryptoServiceProvider rsaProvider = null; + RSACryptoServiceProvider rsaProviderOld = null; + + if (!string.IsNullOrEmpty(options.ProofKey) && !string.IsNullOrEmpty(options.ProofKeyOld)) + { + rsaProvider = new RSACryptoServiceProvider(); + rsaProvider.ImportCspBlob(Convert.FromBase64String(AsymmetricEncryptKeypairsOfCurrent)); + + rsaProviderOld = new RSACryptoServiceProvider(); + rsaProviderOld.ImportCspBlob(Convert.FromBase64String(AsymmetricEncryptKeypairsOfOld)); + } + + // Create executor groups + var executorGroups = executionData.GroupBy(d => d.TestGroupName) + .Select(g => new + { + Name = g.Key, + Executors = g.Select(x => GetTestCaseExecutor(x, options, options.TestCategory, rsaProvider, rsaProviderOld)) + }); + + ConsoleColor baseColor = ConsoleColor.White; + HashSet resultStatuses = new HashSet(); + foreach (var group in executorGroups) + { + Helpers.WriteToConsole($"\nTest group: {group.Name}\n", ConsoleColor.White); + + // define execution query - evaluation is lazy; test cases are executed one at a time + // as you iterate over returned collection + var results = group.Executors.Select(x => x.Execute()); + + // iterate over results and print success/failure indicators into console + foreach (TestCaseResult testCaseResult in results) + { + resultStatuses.Add(testCaseResult.Status); + switch (testCaseResult.Status) + { + case ResultStatus.Pass: + baseColor = ConsoleColor.Green; + Helpers.WriteToConsole($"Pass: {testCaseResult.Name}\n", baseColor, 1); + break; + + case ResultStatus.Skipped: + baseColor = ConsoleColor.Yellow; + if (!options.IgnoreSkipped) + { + Helpers.WriteToConsole($"Skipped: {testCaseResult.Name}\n", baseColor, 1); + } + break; + + case ResultStatus.Fail: + default: + baseColor = ConsoleColor.Red; + Helpers.WriteToConsole($"Fail: {testCaseResult.Name}\n", baseColor, 1); + break; + } + + if (testCaseResult.Status == ResultStatus.Fail || + (testCaseResult.Status == ResultStatus.Skipped && !options.IgnoreSkipped)) + { + foreach (var request in testCaseResult.RequestDetails) + { + var responseStatus = (HttpStatusCode)request.ResponseStatusCode; + var color = request.ValidationFailures.Count == 0 ? ConsoleColor.DarkGreen : baseColor; + Helpers.WriteToConsole($"{request.Name}, response code: {request.ResponseStatusCode} {responseStatus}\n", color, 2); + foreach (var failure in request.ValidationFailures) + { + foreach (var error in failure.Errors) + Helpers.WriteToConsole($"{error.StripNewLines()}\n", baseColor, 3); + } + } + + Helpers.WriteToConsole($"Re-run command: .\\wopivalidator.exe -n {testCaseResult.Name} -w {options.WopiEndpoint} -t {options.AccessToken} -l {options.AccessTokenTtl}\n", baseColor, 2); + Console.WriteLine(); + } + } + + if (options.IgnoreSkipped && !resultStatuses.ContainsAny(ResultStatus.Pass, ResultStatus.Fail)) + { + Helpers.WriteToConsole($"All tests skipped.\n", baseColor, 1); + } + } + + // If skipped tests are ignored, don't consider them when determining whether the test run passed or failed + if (options.IgnoreSkipped) + { + if (resultStatuses.Contains(ResultStatus.Fail)) + { + return ExitCode.Failure; + } + } + // Otherwise consider skipped tests as failures + else if (resultStatuses.ContainsAny(ResultStatus.Skipped, ResultStatus.Fail)) + { + return ExitCode.Failure; + } + return ExitCode.Success; + } + + private static TestCaseExecutor GetTestCaseExecutor(TestExecutionData testExecutionData, RunOptions options, TestCategory inputTestCategory, RSACryptoServiceProvider rsaProvider, RSACryptoServiceProvider rsaProviderOld) + { + bool officeNative = inputTestCategory == TestCategory.OfficeNativeClient || + testExecutionData.TestCase.TestCategory == TestCategory.OfficeNativeClient; + string userAgent = officeNative ? Constants.HeaderValues.OfficeNativeClientUserAgent : null; + + return new TestCaseExecutor(testExecutionData, options.WopiEndpoint, options.AccessToken, options.AccessTokenTtl, userAgent, rsaProvider, rsaProviderOld); + } + } +} From a779033d919547b214d4a99e1e037429750452c4 Mon Sep 17 00:00:00 2001 From: TingtingWen Date: Mon, 11 Mar 2019 15:47:56 +0800 Subject: [PATCH 5/8] Pass ApplicationId and UsingRestrictedLink to RequestFactory --- src/WopiValidator.Core/ConfigParser.cs | 21 ++++++++++--------- .../Factories/ITestCaseFactory.cs | 12 +++++++++-- .../Factories/RequestFactory.cs | 18 +++++----------- .../Factories/TestCaseFactory.cs | 18 +++++++++------- src/WopiValidator/RunOptions.cs | 5 +---- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/WopiValidator.Core/ConfigParser.cs b/src/WopiValidator.Core/ConfigParser.cs index b913114..2b0d29f 100644 --- a/src/WopiValidator.Core/ConfigParser.cs +++ b/src/WopiValidator.Core/ConfigParser.cs @@ -10,12 +10,9 @@ namespace Microsoft.Office.WopiValidator.Core { public static class ConfigParser { - public static string UsingRestrictedScenario { get; set; } - public static string ApplicationId { get; set; } - - public static IEnumerable ParseExecutionData(string filePath) + public static IEnumerable ParseExecutionData(string filePath, string applicationId = null, string usingRestrictedScenario = null) { - return ParseExecutionData(filePath, new ResourceManagerFactory(), new TestCaseFactory()); + return ParseExecutionData(filePath, new ResourceManagerFactory(), new TestCaseFactory(), applicationId, usingRestrictedScenario); } /// @@ -24,7 +21,9 @@ public static IEnumerable ParseExecutionData(string filePath) public static IEnumerable ParseExecutionData( string filePath, IResourceManagerFactory resourceManagerFactory, - ITestCaseFactory testCaseFactory) + ITestCaseFactory testCaseFactory, + string applicationId, + string usingRestrictedScenario) { XDocument xDoc = XDocument.Load(filePath); @@ -32,22 +31,24 @@ public static IEnumerable ParseExecutionData( IResourceManager resourceManager = resourceManagerFactory.GetResourceManager(resourcesElement); XElement prereqCasesElement = xDoc.Root.Element("PrereqCases") ?? new XElement("PrereqCases"); - IEnumerable prereqCases = testCaseFactory.GetTestCases(prereqCasesElement); + IEnumerable prereqCases = testCaseFactory.GetTestCases(prereqCasesElement, applicationId, usingRestrictedScenario); Dictionary prereqCasesDictionary = prereqCases.ToDictionary(e => e.Name); return xDoc.Root.Elements("TestGroup") - .SelectMany(x => GetTestExecutionDataForGroup(x, prereqCasesDictionary, testCaseFactory, resourceManager)); + .SelectMany(x => GetTestExecutionDataForGroup(x, prereqCasesDictionary, testCaseFactory, resourceManager, applicationId, usingRestrictedScenario)); } private static IEnumerable GetTestExecutionDataForGroup( XElement definition, Dictionary prereqCasesDictionary, ITestCaseFactory testCaseFactory, - IResourceManager resourceManager) + IResourceManager resourceManager, + string applicationId, + string usingRestrictedScenario) { IEnumerable prereqs; IEnumerable groupTestCases; - testCaseFactory.GetTestCases(definition, prereqCasesDictionary, out prereqs, out groupTestCases); + testCaseFactory.GetTestCases(definition, prereqCasesDictionary, out prereqs, out groupTestCases, applicationId, usingRestrictedScenario); List prereqList = prereqs.ToList(); diff --git a/src/WopiValidator.Core/Factories/ITestCaseFactory.cs b/src/WopiValidator.Core/Factories/ITestCaseFactory.cs index a14b4b1..abdded6 100644 --- a/src/WopiValidator.Core/Factories/ITestCaseFactory.cs +++ b/src/WopiValidator.Core/Factories/ITestCaseFactory.cs @@ -12,9 +12,13 @@ public interface ITestCaseFactory /// Parse XML run configuration to get list of Test Cases /// /// ]]> element from run configuration XML file. + /// application argument + /// application argument /// Collection of Test Cases. IEnumerable GetTestCases( - XElement definitions); + XElement definitions, + string applicationId, + string usingRestrictedScenario); /// /// Parse XML run configuration testgroup element to get a list of TestCases. @@ -23,9 +27,13 @@ IEnumerable GetTestCases( /// Dictionary of name to testcase already parsed from ]]> element from run configuration file. /// PrereqCases applicable to testcases in this test group. /// TestCases in this test group. + /// application argument + /// application argument void GetTestCases(XElement definition, Dictionary prereqCasesDictionary, out IEnumerable prereqTests, - out IEnumerable groupTests); + out IEnumerable groupTests, + string applicationId, + string usingRestrictedScenario); } } diff --git a/src/WopiValidator.Core/Factories/RequestFactory.cs b/src/WopiValidator.Core/Factories/RequestFactory.cs index e702f5d..2f577d4 100644 --- a/src/WopiValidator.Core/Factories/RequestFactory.cs +++ b/src/WopiValidator.Core/Factories/RequestFactory.cs @@ -15,15 +15,15 @@ class RequestFactory /// /// Parses requests information from XML into a collection of IWopiRequest /// - public static IEnumerable GetRequests(XElement definition) + public static IEnumerable GetRequests(XElement definition, string applicationId, string usingRestrictedScenario) { - return definition.Elements().Select(GetRequest); + return definition.Elements().Select(x => GetRequest(x, applicationId, usingRestrictedScenario)); } /// /// Parses single request definition and instantiates proper IWopiRequest instance based on element name /// - private static IRequest GetRequest(XElement definition) + private static IRequest GetRequest(XElement definition, string applicationId, string usingRestrictedScenario) { string elementName = definition.Name.LocalName; XElement validatorsDefinition = definition.Element("Validators"); @@ -52,6 +52,8 @@ private static IRequest GetRequest(XElement definition) Validators = validators ?? GetDefaultValidators(), WopiSrc = (string)definition.Attribute("WopiSrc"), RestrictedLinkType = (string)definition.Attribute("RestrictedLink"), + ApplicationId = applicationId, + UsingRestrictedScenario = usingRestrictedScenario, PerfTraceRequested = string.IsNullOrEmpty((string)definition.Attribute("PerfTraceRequested")) ? false : Boolean.Parse((string)definition.Attribute("PerfTraceRequested")) }; @@ -76,16 +78,6 @@ private static IRequest GetRequest(XElement definition) } } - if (!string.IsNullOrEmpty(ConfigParser.UsingRestrictedScenario)) - { - wopiRequestParams.UsingRestrictedScenario = ConfigParser.UsingRestrictedScenario; - } - - if (!string.IsNullOrEmpty(ConfigParser.ApplicationId)) - { - wopiRequestParams.ApplicationId = ConfigParser.ApplicationId; - } - switch (elementName) { case Constants.Requests.CheckFile: diff --git a/src/WopiValidator.Core/Factories/TestCaseFactory.cs b/src/WopiValidator.Core/Factories/TestCaseFactory.cs index 910c558..8961f58 100644 --- a/src/WopiValidator.Core/Factories/TestCaseFactory.cs +++ b/src/WopiValidator.Core/Factories/TestCaseFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; @@ -12,22 +12,24 @@ namespace Microsoft.Office.WopiValidator.Core.Factories { public class TestCaseFactory : ITestCaseFactory { - public IEnumerable GetTestCases(XElement definitions) + public IEnumerable GetTestCases(XElement definitions, string applicationId, string usingRestrictedScenario) { - return definitions.Elements("TestCase").Select(x => GetTestCase(x)); + return definitions.Elements("TestCase").Select(x => GetTestCase(x, applicationId, usingRestrictedScenario)); } public void GetTestCases( XElement definition, Dictionary prereqCasesDictionary, out IEnumerable prereqTests, - out IEnumerable groupTests) + out IEnumerable groupTests, + string applicationId, + string usingRestrictedScenario) { XElement prereqsElement = definition.Element("PrereqTests") ?? new XElement("PrereqTests"); prereqTests = GetPrereqTests(prereqsElement, prereqCasesDictionary); XElement testCasesElement = definition.Element("TestCases") ?? new XElement("TestCases"); - groupTests = GetTestCases(testCasesElement); + groupTests = GetTestCases(testCasesElement, applicationId, usingRestrictedScenario); } private static IEnumerable GetPrereqTests(XElement definition, Dictionary prereqsDictionary) @@ -48,7 +50,7 @@ private static IEnumerable GetPrereqTests(XElement definition, Dictio /// /// User RequestFactory.GetRequests to parse requests defined in that Test Case. /// - private static ITestCase GetTestCase(XElement definition) + private static ITestCase GetTestCase(XElement definition, string applicationId, string usingRestrictedScenario) { string category = (string)definition.Attribute("Category"); string name = (string)definition.Attribute("Name"); @@ -62,12 +64,12 @@ private static ITestCase GetTestCase(XElement definition) bool deleteDocumentOnTeardown = (bool?)definition.Attribute("DeleteDocumentOnTeardown") ?? true; XElement requestsDefinition = definition.Element("Requests"); - IEnumerable requests = RequestFactory.GetRequests(requestsDefinition); + IEnumerable requests = RequestFactory.GetRequests(requestsDefinition, applicationId, usingRestrictedScenario); IEnumerable cleanupRequests = null; XElement cleanupRequestsDefinition = definition.Element("CleanupRequests"); if (cleanupRequestsDefinition != null) - cleanupRequests = RequestFactory.GetRequests(cleanupRequestsDefinition); + cleanupRequests = RequestFactory.GetRequests(cleanupRequestsDefinition, applicationId, usingRestrictedScenario); ITestCase testCase = new TestCase(resourceId, requests, diff --git a/src/WopiValidator/RunOptions.cs b/src/WopiValidator/RunOptions.cs index 8addc8d..953ba9b 100644 --- a/src/WopiValidator/RunOptions.cs +++ b/src/WopiValidator/RunOptions.cs @@ -56,14 +56,11 @@ internal class RunOptions : OptionsBase public static ExitCode RunCommand(RunOptions options) { // get run configuration from XML - IEnumerable testData = ConfigParser.ParseExecutionData(options.RunConfigurationFilePath); + IEnumerable testData = ConfigParser.ParseExecutionData(options.RunConfigurationFilePath, options.ApplicationId, options.UsingRestrictedScenario); // Filter the tests IEnumerable executionData = testData.ApplyFilters(options); - ConfigParser.UsingRestrictedScenario = options.UsingRestrictedScenario; - ConfigParser.ApplicationId = options.ApplicationId; - RSACryptoServiceProvider rsaProvider = null; RSACryptoServiceProvider rsaProviderOld = null; From 1c6ce9ec8fcc0929e813867af099a39c4eb109a6 Mon Sep 17 00:00:00 2001 From: TingtingWen Date: Tue, 12 Mar 2019 11:08:09 +0800 Subject: [PATCH 6/8] Put RSA key paris as application arguments --- src/WopiValidator/DiscoveryOptions.cs | 10 ++++++++-- src/WopiValidator/RunOptions.cs | 24 +++++++----------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/WopiValidator/DiscoveryOptions.cs b/src/WopiValidator/DiscoveryOptions.cs index 20cfe8f..ee2ca6a 100644 --- a/src/WopiValidator/DiscoveryOptions.cs +++ b/src/WopiValidator/DiscoveryOptions.cs @@ -13,7 +13,7 @@ namespace Microsoft.Office.WopiValidator [Verb("discovery", HelpText = "Provide XML that describes the supported abilities of this WOPI client")] internal class DiscoveryOptions : OptionsBase { - [Option("port", Required = false, HelpText = "Port number used for discovery")] + [Option("port", Required = true, HelpText = "Port number used for discovery")] public string Port { get; set; } [Option("progid", Required = false, HelpText = "progid that identifies a folder as being associated with a specific application")] @@ -27,7 +27,13 @@ internal class DiscoveryOptions : OptionsBase public static ExitCode DiscoveryCommand(DiscoveryOptions options) { - DiscoveryListener listener = new DiscoveryListener(options.ProofKey, options.ProofKeyOld, Convert.ToInt32(options.Port)); + int port; + if (!Int32.TryParse(options.Port, out port)) + { + throw new ArgumentException(string.Format("Value for argument 'port' must be an integer, actual value '{0}'.", options.Port)); + } + + DiscoveryListener listener = new DiscoveryListener(options.ProofKey, options.ProofKeyOld, port); listener.Start(); return ExitCode.Success; diff --git a/src/WopiValidator/RunOptions.cs b/src/WopiValidator/RunOptions.cs index 953ba9b..66aebc8 100644 --- a/src/WopiValidator/RunOptions.cs +++ b/src/WopiValidator/RunOptions.cs @@ -34,25 +34,15 @@ internal class RunOptions : OptionsBase [Option("ApplicationId", Required = false, HelpText = "Header 'X-WOPI-ApplicationId' indicates id of an application stored in secure store")] public string ApplicationId { get; set; } - [Option('p', "ProofKey", Required = false, HelpText = "Public key used to decrypt X-WOPI-Proof HTTP header")] - public string ProofKey { get; set; } + [Option("RSACryptoKeyPairValue", Required = false, HelpText = "key-pairs match the Asymmetric encrypt algorithm used for X-WOPI-Proof header")] + public string RSACryptoKeyPairValue { get; set; } - [Option('o', "ProofKeyOld", Required = false, HelpText = "Public key used to decrypt X-WOPI-ProofOld HTTP header")] - public string ProofKeyOld { get; set; } + [Option("RSACryptoKeyPairOldValue", Required = false, HelpText = "key-pairs match the Asymmetric encrypt algorithm used for X-WOPI-ProofOld header")] + public string RSACryptoKeyPairOldValue { get; set; } [Option('s', "ignore-skipped", Required = false, HelpText = "Don't output any info about skipped tests.")] public bool IgnoreSkipped { get; set; } - /// - /// A string represents the unique key-pairs which is match the Asymmetric encrypt algorithm. It is used for "X-WOPI-Proof" header. - /// - private const string AsymmetricEncryptKeypairsOfCurrent = @"BwIAAACkAABSU0EyAAQAAAEAAQBxwXpxCIYvyvtnFmflVBmFEYpn/hhuZCqVH1PhqnAQr/ONAVkiONeMTToP7n2kUi1wntw5MbMaoIPWoNejZOLDgIVUqfjCOH2EbXOMmp6zTN35zAYbGZ3XWgLtmVkHIU60IQGvl7rOEHnEJ4v7a7Q2s6r4IOdVqFMS1T2YpmT6r5W6lKKyvjRKtwu3RClOcoNR9cpQNlzRqP6Tsl1B2UHAlRhNOBB7jEcttjdFFr/C1M6e5+XpDIhDlJt4BMODGN1tEAYZ71eMpnXkUBpUewXyaGZSSi5H/1cSki0srmONVNj7amPdk8QdEnlz+WnhLSjjeyNBHCVhhtVYaKfxd8LLnfRqGmPxGkcLsbTTL9Ngv1fNBFGCq45NsSNq4MGp+eja+2oJSE6duSpjeSOapLQ/vPcfkVZQP3AmOvEvxKV3wHUnzIlbvaklNhbg2LAJOZ2lT7bWVHfrgQ06lcjlapAaAoSNbPBhhhSOdUPrdRy4ebAhoUiriJiXoMNy9NS6GHqploWkCU/HDdVBTYS/yjqVFOAbhQA4edSgzyIT5P1tyIWImQ7ziE+7gFMPbXosoWmDL/iSmaAyuSU3lqun3lrBIWTXuHul48OgGadg2k2c6PBX0y7fqgBfEOAOFSii+c/d1G2umh321WigSaeg0VrsPO5pH1MbFOOFS/ZjMGWmZSYZfopkIOCqXL1UbiNwsIa0OG1FxrIMI9zgQt1aGPV4NK7unMeBz/t9uO2Y9nsrztQnYuje6K0cPTK+HlOExao="; - - /// - /// A string represents the unique key-pairs which is match the Asymmetric encrypt algorithm. It is used for "X-WOPI-ProofOld" header. - /// - private const string AsymmetricEncryptKeypairsOfOld = @"BwIAAACkAABSU0EyAAQAAAEAAQC3T8ExrB2fjcvpVJF7ZYbAh9yfsHsXMcqHa/0i0ncEdoejYr1s1NMbZtGbautAmDH2Q5/dUoZ6UHvymDxGh3VypfCHg7heRaPoeBLBrKyhIbG8oy2KUlpUSBGi9s2ZTb4tMyef8ZTA+f5jneAIZDC8U4DZF0mifHJtXrQHqSY9kkHv/7WdvxVsoLToq78tX3CZDR5btKGsOJD8qjwJ0Tthsq3l79rhh39kxci9YzMKVK4rQVlUSAopVtRuWXa2j8X3eOs/YEmObMpUxEPK6yZk8Rj9UYLMm7rmO+iB0vMrTAkKOI7csfDEg+XZKSM3tRmkOJHPyUlxgeLBcR7PDX+9gVPEILISnGGj+2qsHT+ywcmC7/dYiwjhj/VXgzZvl2KjDbfaa2N10CQN6MnBMXzawvsnrSA4x8UGmzLuzLiEuTlg6Ed1WuL7uv3p+Rs9NX/yuj2jPuuFWDNNWlqGb4455eERQv73YXLNFMGRc87peBib/gN2YUO5suH8J5NzyUOGA0YPEvNWHIZo0k0JcJWzY3zVLENdKxHZFjc60bfRbAM0wTl20aWEvUEUBPOikuRmeEFveJNYSGUvcscIefhtgdTzsafiOpUW/2nR+tpxoIQM4gFZXIs85838T46kNCX1M/RBLjailtbAuyjOhA8Dixjm2jL4nndpkKPRl5ZC/pmYnUZGVd/nbh7h9rKglRSLYFzc3+OnPI9Mj0UoDPy72SE4DO6e4QN7npbklrWAKSqGUF4DNT4Y7iw5pibqSw4="; - public static ExitCode RunCommand(RunOptions options) { // get run configuration from XML @@ -64,13 +54,13 @@ public static ExitCode RunCommand(RunOptions options) RSACryptoServiceProvider rsaProvider = null; RSACryptoServiceProvider rsaProviderOld = null; - if (!string.IsNullOrEmpty(options.ProofKey) && !string.IsNullOrEmpty(options.ProofKeyOld)) + if (!string.IsNullOrEmpty(options.RSACryptoKeyPairValue) && !string.IsNullOrEmpty(options.RSACryptoKeyPairOldValue)) { rsaProvider = new RSACryptoServiceProvider(); - rsaProvider.ImportCspBlob(Convert.FromBase64String(AsymmetricEncryptKeypairsOfCurrent)); + rsaProvider.ImportCspBlob(Convert.FromBase64String(options.RSACryptoKeyPairValue)); rsaProviderOld = new RSACryptoServiceProvider(); - rsaProviderOld.ImportCspBlob(Convert.FromBase64String(AsymmetricEncryptKeypairsOfOld)); + rsaProviderOld.ImportCspBlob(Convert.FromBase64String(options.RSACryptoKeyPairOldValue)); } // Create executor groups From 9a98edd5b87a2f15234b93cdf4bcd0f73cdd3233 Mon Sep 17 00:00:00 2001 From: Tingting Wen Date: Mon, 22 Apr 2019 15:41:51 +0800 Subject: [PATCH 7/8] Debug after get latest commits --- MS-WOPITestCases.xml | 26 +- .../MS-WOPICheckFileInfoSchema.json | 244 +++++++++++++++++- .../MS-WOPICheckFolderInfoSchema.json | 123 ++++++++- src/WopiValidator/DiscoveryOptions.cs | 9 - 4 files changed, 378 insertions(+), 24 deletions(-) diff --git a/MS-WOPITestCases.xml b/MS-WOPITestCases.xml index 47d66ab..4f38760 100644 --- a/MS-WOPITestCases.xml +++ b/MS-WOPITestCases.xml @@ -10,7 +10,7 @@ - + @@ -390,7 +390,7 @@ - + @@ -420,7 +420,7 @@ - + @@ -652,7 +652,7 @@ - + @@ -722,7 +722,7 @@ FileEditingPrereq - NotReadOnlyPrereq + NotReadOnlyPrereq UserCanWritePrereq LocksPrereq @@ -731,7 +731,7 @@ This tests that put a file successfully. - + @@ -740,12 +740,12 @@ - + - + This tests that put a file with matching lock value successfully after lock. @@ -756,7 +756,7 @@ - + @@ -775,7 +775,7 @@ - + @@ -784,7 +784,7 @@ - + This tests that host returns 401 or 404 response for a PutFile request with invalid access token. @@ -1035,7 +1035,7 @@ - This tests that X-WOPI-Lock header is included when responding with the 409 status code for Unlock request if no lock exists on the file. + This tests that X-WOPI-Lock header is included when responding with the 409 status code for Unlock request if no lock exists on the file. @@ -1154,7 +1154,7 @@ - + UserCanWriteRelativePrereq diff --git a/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json index 4fa7806..a32797d 100644 --- a/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json +++ b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFileInfoSchema.json @@ -5,18 +5,54 @@ "type": "object", "additionalProperties": false, "properties": { + "AADUserObjectId": { + "type": "string", + "default": "", + "optional": true + }, + "AccessTokenExpiry": { + "type": "integer", + "default": 0, + "optional": true + }, + "AllowAddActivitiesUserBatching": { + "type": "boolean", + "default": false, + "optional": true + }, "AllowAdditionalMicrosoftServices": { "type": "boolean", "default": false, "optional": true }, + "AllowEarlyFeatures": { + "type": "boolean", + "default": false, + "optional": true + }, + "AllowErrorReportPrompt": { + "type": "boolean", + "default": false, + "optional": true + }, "AllowExternalMarketplace": { "type": "boolean", "default": false, "optional": true }, + "AppCatalogUrl": { + "type": "string", + "default": "", + "optional": true + }, + "AppliedPolicyId": { + "type": "string", + "default": "", + "optional": true + }, "BaseFileName": { "type": "string", + "default": "", "optional": false }, "BreadcrumbBrandName": { @@ -74,6 +110,11 @@ "default": "", "optional": true }, + "DirectInvokeDAVUrl": { + "type": "string", + "default": "", + "optional": true + }, "DisableBrowserCachingOfUserContent": { "type": "boolean", "default": false, @@ -101,6 +142,11 @@ "default": "", "optional": true }, + "EditingCannotSave": { + "type": "boolean", + "default": false, + "optional": true + }, "EditModePostMessage": { "type": "boolean", "default": false, @@ -111,6 +157,31 @@ "default": false, "optional": true }, + "EmbeddingPageOrigin": { + "type": "string", + "default": "", + "optional": true + }, + "EmbeddingPageSessionInfo": { + "type": "string", + "default": "", + "optional": true + }, + "EnabledApplicationFeatures": { + "type": "array", + "default": [], + "optional": true + }, + "FileEmbedCommandPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "FileEmbedCommandUrl": { + "type": "string", + "default": "", + "optional": true + }, "FileExtension": { "type": "string", "default": "", @@ -138,11 +209,31 @@ "default": "", "optional": true }, + "FileVersionPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "FileVersionUrl": { + "type": "string", + "default": "", + "optional": true + }, "HostAuthenticationId": { "type": "string", "default": "", "optional": true }, + "HostAuthenticationIdType": { + "type": "string", + "default": "", + "optional": true + }, + "HostDivSyndicationViewUrl": { + "type": "string", + "default": "", + "optional": true + }, "HostEditUrl": { "type": "string", "format": "uri", @@ -183,6 +274,11 @@ "default": "", "optional": true }, + "InsertImagePostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, "IrmPolicyDescription": { "type": "string", "default": "", @@ -193,15 +289,65 @@ "default": "", "optional": true }, + "IsAnonymousUser": { + "type": "boolean", + "default": false, + "optional": true + }, + "IsEduUser": { + "type": "boolean", + "default": false, + "optional": true + }, + "IsYammerEnabled": { + "type": "boolean", + "default": false, + "optional": true + }, + "LastModifiedTime": { + "type": "string", + "default": "", + "optional": true + }, "LicenseCheckForEditIsEnabled": { "type": "boolean", "default": false, "optional": true }, + "LicensedOrganization": { + "type": "string", + "default": "", + "optional": true + }, + "OfficeCollaborationServiceEndpointUrl": { + "type": "string", + "default": "", + "optional": true + }, + "OpenInClientCommandUrl": { + "type": "string", + "default": "", + "optional": true + }, + "OpenInClientPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, "OwnerId": { "type": "string", "optional": false }, + "PermissionsPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "PolicyCheckPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, "PostMessageOrigin": { "type": "string", "default": "", @@ -223,6 +369,11 @@ "default": "", "optional": true }, + "ProtectedFile": { + "type": "boolean", + "default": false, + "optional": true + }, "ProtectInClient": { "type": "boolean", "default": false, @@ -233,13 +384,29 @@ "default": false, "optional": true }, + "ReportAbusePostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, + "ReportAbuseUrl": { + "type": "string", + "default": "", + "optional": true + }, "RestrictedWebViewOnly": { "type": "boolean", "default": false, "optional": true }, + "SafeLinksStatus": { + "type": "string", + "default": "", + "optional": true + }, "SHA256": { "type": "string", + "default": "", "optional": true }, "SignInUrl": { @@ -256,6 +423,7 @@ }, "Size": { "type": "integer", + "default": -1, "optional": false }, "SupportedShareUrlTypes": { @@ -263,6 +431,21 @@ "default": [], "optional": true }, + "SupportsAddActivities": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsCheckPolicy": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsCheckUserAccess": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsCoauth": { "type": "boolean", "default": false, @@ -273,6 +456,26 @@ "default": false, "optional": true }, + "SupportsContactsResolution": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsContainers": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsDeleteFile": { + "type": "boolean", + "default": false, + "optional": true + }, + "SupportsEcosystem": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsExtendedLockLength": { "type": "boolean", "default": false, @@ -283,26 +486,51 @@ "default": false, "optional": true }, + "SupportsFileUserValue": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsFolders": { "type": "boolean", "default": false, "optional": true }, + "SupportsGetActivities": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsGetLock": { "type": "boolean", "default": false, "optional": true }, + "SupportsGrantUserAccess": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsLocks": { "type": "boolean", "default": false, "optional": true }, + "SupportsPolicies": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsRename": { "type": "boolean", "default": false, "optional": true }, + "SupportsReviewing": { + "type": "boolean", + "default": false, + "optional": true + }, "SupportsScenarioLinks": { "type": "boolean", "default": false, @@ -364,6 +592,11 @@ "default": false, "optional": true }, + "UserCanReview": { + "type": "boolean", + "default": false, + "optional": true + }, "UserCanWrite": { "type": "boolean", "default": false, @@ -384,6 +617,11 @@ "default": "", "optional": true }, + "UserPrincipalName": { + "type": "string", + "default": "", + "optional": true + }, "Version": { "type": "string", "optional": false @@ -393,6 +631,11 @@ "default": false, "optional": true }, + "WorkflowPostMessage": { + "type": "boolean", + "default": false, + "optional": true + }, "WorkflowType": { "type": "array", "default": [], @@ -400,7 +643,6 @@ }, "WorkflowUrl": { "type": "string", - "format": "uri", "default": "", "optional": true } diff --git a/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json index 15d9d37..649071b 100644 --- a/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json +++ b/src/WopiValidator.Core/JsonSchemas/MS-WOPICheckFolderInfoSchema.json @@ -9,8 +9,29 @@ "OwnerId" ], "properties": { + "AADUserObjectId": { + "type": "string", + "default": "" + }, + "AccessTokenExpiry": { + "type": "integer", + "default": 0 + }, + "AllowEarlyFeatures": { + "type": "boolean", + "default": false + }, + "AllowExternalMarketplace": { + "type": "boolean", + "default": false + }, + "AppCatalogUrl": { + "type": "string", + "default": "" + }, "FolderName": { - "type": "string" + "type": "string", + "default": "" }, "BreadcrumbBrandIconUrl": { "type": "string", @@ -52,20 +73,52 @@ "type": "boolean", "default": false }, + "ClosePostMessage": { + "type": "boolean", + "default": false + }, "CloseUrl": { "type": "string", "format": "uri", "default": "" }, + "DirectInvokeDAVUrl": { + "type": "string", + "default": "" + }, + "DisablePrint": { + "type": "boolean", + "default": false + }, + "EditNotificationPostMessage": { + "type": "boolean", + "default": false + }, + "FileSharingPostMessage": { + "type": "boolean", + "default": false + }, "FileSharingUrl": { "type": "string", "format": "uri", "default": "" }, + "FileVersionPostMessage": { + "type": "boolean", + "default": false + }, + "FileVersionUrl": { + "type": "string", + "default": "" + }, "HostAuthenticationId": { "type": "string", "default": "" }, + "HostAuthenticationIdType": { + "type": "string", + "default": "" + }, "HostEditUrl": { "type": "string", "format": "uri", @@ -85,14 +138,42 @@ "type": "string", "default": "" }, + "HostNotes": { + "type": "string", + "default": "" + }, "HostViewUrl": { "type": "string", "format": "uri", "default": "" }, + "IrmPolicyDescription": { + "type": "string", + "default": "" + }, + "IrmPolicyTitle": { + "type": "string", + "default": "" + }, + "IsAnonymousUser": { + "type": "boolean", + "default": false + }, + "OpenInClientCommandUrl": { + "type": "string", + "default": "" + }, + "OpenInClientPostMessage": { + "type": "boolean", + "default": false + }, "OwnerId": { "type": "string" }, + "PostMessageOrigin": { + "type": "string", + "default": "" + }, "PresenceProvider": { "type": "string", "default": "" @@ -106,11 +187,31 @@ "format": "uri", "default": "" }, + "ProtectInClient": { + "type": "boolean", + "default": false + }, + "ReportAbusePostMessage": { + "type": "boolean", + "default": false + }, + "ReportAbuseUrl": { + "type": "string", + "default": "" + }, + "SafeLinksStatus": { + "type": "string", + "default": "" + }, "SignoutUrl": { "type": "string", "format": "uri", "default": "" }, + "SupportsFileUserValue": { + "type": "boolean", + "default": false + }, "SupportsSecureStore": { "type": "boolean", "default": false @@ -124,6 +225,10 @@ "format": "uri", "default": "" }, + "UserCanReview": { + "type": "boolean", + "default": false + }, "UserCanWrite": { "type": "boolean", "default": false @@ -136,9 +241,25 @@ "type": "string", "default": "" }, + "UserPrincipalName": { + "type": "string", + "default": "" + }, "WebEditingDisabled": { "type": "boolean", "default": false + }, + "WorkflowPostMessage": { + "type": "boolean", + "default": false + }, + "WorkflowType": { + "type": "array", + "default": [] + }, + "WorkflowUrl": { + "type": "string", + "default": "" } } } diff --git a/src/WopiValidator/DiscoveryOptions.cs b/src/WopiValidator/DiscoveryOptions.cs index ee2ca6a..f19a05c 100644 --- a/src/WopiValidator/DiscoveryOptions.cs +++ b/src/WopiValidator/DiscoveryOptions.cs @@ -38,14 +38,5 @@ public static ExitCode DiscoveryCommand(DiscoveryOptions options) return ExitCode.Success; } - - private static TestCaseExecutor GetTestCaseExecutor(TestExecutionData testExecutionData, RunOptions options, TestCategory inputTestCategory) - { - bool officeNative = inputTestCategory == TestCategory.OfficeNativeClient || - testExecutionData.TestCase.TestCategory == TestCategory.OfficeNativeClient; - string userAgent = officeNative ? Constants.HeaderValues.OfficeNativeClientUserAgent : null; - - return new TestCaseExecutor(testExecutionData, options.WopiEndpoint, options.AccessToken, options.AccessTokenTtl, userAgent); - } } } From 84fe1b539fdb6f2b5c07966b11a5b650ef061bb9 Mon Sep 17 00:00:00 2001 From: Tingting Wen Date: Mon, 22 Apr 2019 17:58:10 +0800 Subject: [PATCH 8/8] Revert unnecessary changes --- TestCases.xsd | 5 ----- src/WopiValidator.Core/Requests/GetShareUrlRequest.cs | 10 +++------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/TestCases.xsd b/TestCases.xsd index 28b49c7..72c21ff 100644 --- a/TestCases.xsd +++ b/TestCases.xsd @@ -316,11 +316,9 @@ - - @@ -457,7 +455,6 @@ - @@ -514,9 +511,7 @@ - - diff --git a/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs b/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs index 9f90073..1352950 100644 --- a/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs +++ b/src/WopiValidator.Core/Requests/GetShareUrlRequest.cs @@ -19,14 +19,10 @@ protected override IEnumerable> DefaultHeaders { get { - Dictionary headers = new Dictionary(); - - if (!string.IsNullOrEmpty(this.UrlType)) + return new Dictionary { - headers.Add(Constants.Headers.UrlType, UrlType); - } - - return headers; + {Constants.Headers.UrlType, UrlType} + }; } } }