public override Task <bool> Authorize(WopiRequest wopiRequest) { try { if (string.IsNullOrEmpty(wopiRequest.AccessToken)) { return(Task.FromResult <bool>(false)); } // Get the requested file from Document DB var itemId = new Guid(wopiRequest.ResourceId); var wopiFile = DocumentDBRepository <DetailedFileModel> .GetItem("Files", file => file.id == itemId); // Check for missing file if (wopiFile == null) { return(Task.FromResult <bool>(false)); } // Validate the access token return(Task.FromResult <bool>(WopiSecurity.ValidateToken(wopiRequest.AccessToken, wopiFile.Container, wopiFile.id.ToString()))); } catch (Exception) { // Any exception will return false, but should probably return an alternate status codes return(Task.FromResult <bool>(false)); } }
public void ProcessRequestPrivate(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context = listener.EndGetContext(result); if (!_authorization.ValidateWopiProofKey(context.Request)) { _errHandler.ReturnServerError(context.Response); } _processor = new WopiProcessor(_authorization, _errHandler, context.Response); WopiRequest requestData = ParseRequest(context.Request); switch (requestData.Type) { case RequestType.CheckFileInfo: _processor.HandleCheckFileInfoRequest(requestData); break; case RequestType.Lock: _processor.HandleLockRequest(requestData); break; case RequestType.Unlock: _processor.HandleUnlockRequest(requestData); break; case RequestType.RefreshLock: _processor.HandleRefreshLockRequest(requestData); break; case RequestType.UnlockAndRelock: _processor.HandleUnlockAndRelockRequest(requestData); break; case RequestType.GetFile: _processor.HandleGetFileRequest(requestData); break; case RequestType.PutFile: _processor.HandlePutFileRequest(requestData); break; case RequestType.PutRelativeFile: case RequestType.EnumerateChildren: case RequestType.CheckFolderInfo: case RequestType.DeleteFile: case RequestType.ExecuteCobaltRequest: case RequestType.GetRestrictedLink: case RequestType.ReadSecureStore: case RequestType.RevokeRestrictedLink: _errHandler.ReturnUnsupported(context.Response); break; default: _errHandler.ReturnServerError(context.Response); break; } }
public void HandleUnlockAndRelockRequest(WopiRequest requestData) { lock (this) { // userId(user@polihub) will be passed from the policyHub application if (!_authorization.ValidateToken(requestData.AccessToken, "user@policyhub", requestData.Id)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } string newLock = requestData.LockId; string oldLock = requestData.OldLockId; lock (LockInfo.Locks) { LockInfo existingLock; if (LockInfo.TryGetLock(requestData.Id, out existingLock)) { if (existingLock.Lock == oldLock) { LockInfo.Locks[requestData.Id] = new LockInfo() { DateCreated = DateTime.UtcNow, Lock = newLock }; _response.AddHeader(WopiHeaders.Lock, newLock); _response.StatusCode = (int)HttpStatusCode.OK; _errorHandler.ReturnSuccess(_response); } else { _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.AddHeader(WopiHeaders.LockFailureReason, "Lock mismatch/Locked by another interface"); _response.StatusCode = (int)HttpStatusCode.Conflict; _errorHandler.ReturnLockMismatch(_response, existingLock.Lock); } } else { _response.AddHeader(WopiHeaders.Lock, newLock); _response.AddHeader(WopiHeaders.LockFailureReason, "File not locked"); _response.StatusCode = (int)HttpStatusCode.Conflict; _errorHandler.ReturnLockMismatch(_response, reason: "File not locked"); } } _response.Close(); } }
public void HandleRefreshLockRequest(WopiRequest requestData) { lock (this) { if (!_authorization.ValidateAccess(requestData, writeAccessRequired: true)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } string newLock = requestData.LockId; lock (LockInfo.Locks) { LockInfo existingLock; if (LockInfo.TryGetLock(requestData.Id, out existingLock)) { if (existingLock.Lock == newLock) { existingLock.DateCreated = DateTime.UtcNow; _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.StatusCode = (int)HttpStatusCode.OK; _errorHandler.ReturnSuccess(_response); } else { _errorHandler.ReturnLockMismatch(_response, existingLock.Lock); _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.AddHeader(WopiHeaders.LockFailureReason, "Lock mismatch/Locked by another interface"); _response.StatusCode = (int)HttpStatusCode.Conflict; } } else { _errorHandler.ReturnLockMismatch(_response, reason: "File not locked"); _response.AddHeader(WopiHeaders.Lock, newLock); _response.AddHeader(WopiHeaders.LockFailureReason, "File not locked"); _response.StatusCode = (int)HttpStatusCode.Conflict; } } _response.Close(); } }
public void HandleCheckFileInfoRequest(WopiRequest requestData) { lock (this) { // userId(user@polihub) will be passed from the policyHub application if (!_authorization.ValidateToken(requestData.AccessToken, ConfigurationManager.AppSettings["userId"], requestData.Id)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } try { FileInfo fileInfo = new FileInfo(requestData.FullPath); ResponseGenerator generator = new ResponseGenerator(fileInfo); if (!fileInfo.Exists) { _errorHandler.ReturnFileUnknown(_response); return; } var memoryStream = new MemoryStream(); var json = new DataContractJsonSerializer(typeof(WopiCheckFileInfo)); json.WriteObject(memoryStream, generator.GetFileInfoResponse()); memoryStream.Flush(); memoryStream.Position = 0; StreamReader streamReader = new StreamReader(memoryStream); var jsonResponse = Encoding.UTF8.GetBytes(streamReader.ReadToEnd()); _response.ContentType = ConfigurationManager.AppSettings["appJson"]; _response.ContentLength64 = jsonResponse.Length; _response.OutputStream.Write(jsonResponse, 0, jsonResponse.Length); _errorHandler.ReturnSuccess(_response); } catch (UnauthorizedAccessException) { _errorHandler.ReturnFileUnknown(_response); } _response.Close(); } }
public WopiResponse HandleRefresh() { var requestData = WopiRequest.ParseRequest(_request); var documentId = Convert.ToInt64(requestData.Id); var document = FB.GetDocument(documentId); if (document == null) { return(new WopiResponse() { ResponseType = WopiResponseType.FileUnknown, Message = "File not found" }); } var newLock = new LockInfo() { DateCreated = DateTime.UtcNow, Lock = _request.Headers[WopiHeaders.Lock] }; var currentLockInfo = FB.GetLockInfo(document); var currentLock = !string.IsNullOrEmpty(currentLockInfo) ? Newtonsoft.Json.JsonConvert.DeserializeObject <LockInfo>(currentLockInfo) : null; if (currentLock == null) { // on refresh, there should already be a lock return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch, Message = "" }); } lock (currentLock) { if (currentLock.Lock != newLock.Lock) { // trying to lock again with a different lock? YEAH RIGHT return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch, Message = currentLock.Lock }); } // update the lock (refresh) and return success: FB.UpdateLockInfo(document, Newtonsoft.Json.JsonConvert.SerializeObject(newLock)); return(new WopiResponse() { ResponseType = WopiResponseType.Success }); } }
public void HandleUnlockRequest(WopiRequest requestData) { lock (this) { // userId(user@polihub) will be passed from the policyHub application if (!_authorization.ValidateToken(requestData.AccessToken, ConfigurationManager.AppSettings["userId"], requestData.Id)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } string newLock = requestData.LockId; lock (LockInfo.Locks) { LockInfo existingLock; if (LockInfo.TryGetLock(requestData.Id, out existingLock)) { if (existingLock.Lock == newLock) { LockInfo.Locks.Remove(requestData.Id); _errorHandler.ReturnSuccess(_response); _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.StatusCode = (int)HttpStatusCode.OK; } else { _errorHandler.ReturnLockMismatch(_response, existingLock.Lock); _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.AddHeader(WopiHeaders.LockFailureReason, ConfigurationManager.AppSettings["lockMismatch"]); _response.StatusCode = (int)HttpStatusCode.Conflict; } } else { _errorHandler.ReturnLockMismatch(_response, reason: ConfigurationManager.AppSettings["notLocked"]); _response.AddHeader(WopiHeaders.Lock, newLock); _response.AddHeader(WopiHeaders.LockFailureReason, ConfigurationManager.AppSettings["notLocked"]); _response.StatusCode = (int)HttpStatusCode.Conflict; } } _response.Close(); } }
public void HandleCheckFileInfoRequest(WopiRequest requestData) { lock (this) { if (!_authorization.ValidateAccess(requestData, writeAccessRequired: false)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } try { FileInfo fileInfo = new FileInfo(requestData.FullPath); ResponseGenerator generator = new ResponseGenerator(fileInfo); if (!fileInfo.Exists) { _errorHandler.ReturnFileUnknown(_response); return; } var memoryStream = new MemoryStream(); var json = new DataContractJsonSerializer(typeof(WopiCheckFileInfo)); json.WriteObject(memoryStream, generator.GetFileInfoResponse()); memoryStream.Flush(); memoryStream.Position = 0; StreamReader streamReader = new StreamReader(memoryStream); var jsonResponse = Encoding.UTF8.GetBytes(streamReader.ReadToEnd()); _response.ContentType = @"application/json"; _response.ContentLength64 = jsonResponse.Length; _response.OutputStream.Write(jsonResponse, 0, jsonResponse.Length); _errorHandler.ReturnSuccess(_response); } catch (UnauthorizedAccessException) { _errorHandler.ReturnFileUnknown(_response); } _response.Close(); } }
/// <summary> /// Determines if the user is authorized to access the WebAPI endpoint based on the bearer token /// </summary> protected override bool IsAuthorized(HttpActionContext actionContext) { try { // Parse the query string and ensure there is an access_token var queryParams = parseRequestParams(actionContext.Request.RequestUri.Query); if (!queryParams.ContainsKey("access_token")) { return(false); } // Get the details of the WOPI request Token token; try { token = Token.Get(queryParams["access_token"]); } catch { return(false); } WopiRequest requestData = new WopiRequest() { RequestType = WopiRequestType.None, AccessToken = token, Id = actionContext.RequestContext.RouteData.Values["id"].ToString() }; // Get the requested file from Document DB var itemId = new Guid(requestData.Id); //var file = DocumentDBRepository<DetailedFileModel>.GetItem("Files", i => i.id == itemId); var file = Models.Api.File.Get(itemId); // Check for missing file if (file == null) { return(false); } // Validate the access token return(WopiSecurity.ValidateToken(requestData.AccessToken, file.id.ToString())); } catch (Exception) { // Any exception will return false, but should probably return an alternate status codes return(false); } }
// required endpoints: // https://wopi.readthedocs.io/en/latest/wopi_requirements.html#requirements protected override void OnActionExecuting(ActionExecutingContext context) { var requestData = WopiRequest.ParseRequest(Request); if (!FB.ValidateAccessToken(Request.QueryString["access_token"], Convert.ToInt64(requestData.Id))) { HandleResponse(new WopiResponse() { ResponseType = WopiResponseType.InvalidToken, Message = "Invalid Token" }); context.Result = new HttpStatusCodeResult(Response.StatusCode, Response.StatusDescription); } base.OnActionExecuting(context); }
bool IWopiRequestFactory.TryCreateRequest(HttpRequest httpRequest, out WopiRequest wopiRequest) { if (httpRequest is null) { throw new ArgumentNullException(nameof(httpRequest)); } wopiRequest = WopiRequest.EMPTY; var path = httpRequest.Path; if (path.HasValue && path.StartsWithSegments("/wopi", StringComparison.OrdinalIgnoreCase)) { var accessToken = httpRequest.Query["access_token"].FirstOrDefault(); if (string.IsNullOrWhiteSpace(accessToken)) { return(EmptyResponse("The access token query parameter is either missing, or it has an empty value")); // TODO - Might be better to be more specific with a WopiRequest.MissingAccessToken response? } if (path.StartsWithSegments("/wopi/files", StringComparison.OrdinalIgnoreCase)) { wopiRequest = IdentifyFileRequest(httpRequest, path, accessToken, _features); } else if (path.StartsWithSegments("/wopi/folders", StringComparison.OrdinalIgnoreCase)) { wopiRequest = IdentifyFolderRequest(); } else { return(EmptyResponse($"Failed to identify WOPI request. Endpoint '{path}' not supported")); } if (wopiRequest.IsUnableToValidateAccessToken()) { wopiRequest = WopiRequest.EMPTY; return(EmptyResponse($"The access token provided '{accessToken}' could not be identified as valid")); // TODO - Might be better to be more specific with a WopiRequest.InvalidAccessToken response? } else { return(true); // Valid WOPI request that can be actioned } } _logger?.LogTrace($"Determined the request does not related to WOPI: '{path.Value}'"); return(false); // Not a WOPI request so nothing for us to do }
/// <summary> /// Validates the WOPI Proof on an incoming WOPI request /// </summary> public async Task <bool> ValidateAsync(WopiRequest wopiRequest) { var hostUrl = wopiRequest.RequestUri.OriginalString.Replace(":44300", "").Replace(":443", ""); // Make sure the request has the correct headers if (wopiRequest.Proof == null || wopiRequest.Timestamp == null) { return(false); } // Set the requested proof values var requestProof = wopiRequest.Proof; var requestProofOld = string.Empty; if (wopiRequest.ProofOld != null) { requestProofOld = wopiRequest.ProofOld; } // Get the WOPI proof info from discovery var discoProof = await GetWopiProofAsync(); // Encode the values into bytes var accessTokenBytes = Encoding.UTF8.GetBytes(wopiRequest.AccessToken); var hostUrlBytes = Encoding.UTF8.GetBytes(hostUrl.ToUpperInvariant()); var timeStampBytes = BitConverter.GetBytes(Convert.ToInt64(wopiRequest.Timestamp)).Reverse().ToArray(); // Build expected proof List <byte> expected = new List <byte>( 4 + accessTokenBytes.Length + 4 + hostUrlBytes.Length + 4 + timeStampBytes.Length); // Add the values to the expected variable expected.AddRange(BitConverter.GetBytes(accessTokenBytes.Length).Reverse().ToArray()); expected.AddRange(accessTokenBytes); expected.AddRange(BitConverter.GetBytes(hostUrlBytes.Length).Reverse().ToArray()); expected.AddRange(hostUrlBytes); expected.AddRange(BitConverter.GetBytes(timeStampBytes.Length).Reverse().ToArray()); expected.AddRange(timeStampBytes); byte[] expectedBytes = expected.ToArray(); return(VerifyProof(expectedBytes, requestProof, discoProof.value) || VerifyProof(expectedBytes, requestProof, discoProof.oldvalue) || VerifyProof(expectedBytes, requestProofOld, discoProof.value)); }
public override Task <bool> Authorize(WopiRequest wopiRequest) { try { if (string.IsNullOrEmpty(wopiRequest.AccessToken)) { return(Task.FromResult <bool>(false)); } // Validate the access token contains authenticated user // We're only doing authentication here and deferring authorization to the other WOPI operations // to avoid multiple DB queries var userId = WopiSecurity.GetIdentityNameFromToken(wopiRequest.AccessToken); return(Task.FromResult <bool>(userId != null)); } catch (Exception) { // Any exception will return false, but should probably return an alternate status codes return(Task.FromResult <bool>(false)); } }
public void GetFileLockId(WopiRequest requestData) { lock (this) { if (!_authorization.ValidateAccess(requestData, writeAccessRequired: true)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } lock (LockInfo.Locks) { LockInfo existingLock; bool fLocked = LockInfo.TryGetLock(requestData.Id, out existingLock); if (fLocked) { _errorHandler.ReturnSuccess(_response); _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.StatusCode = (int)HttpStatusCode.OK; } else { _errorHandler.ReturnSuccess(_response); _response.AddHeader(WopiHeaders.Lock, ""); _response.AddHeader(WopiHeaders.LockFailureReason, ConfigurationManager.AppSettings["notLocked"]); _response.StatusCode = (int)HttpStatusCode.OK; } } _response.Close(); } }
public void HandleGetFileRequest(WopiRequest requestData) { lock (this) { // userId(user@polihub) will be passed from the policyHub application if (!_authorization.ValidateToken(requestData.AccessToken, ConfigurationManager.AppSettings["userId"], requestData.Id)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } try { FileInfo fileInfo = new FileInfo(requestData.FullPath); ResponseGenerator generator = new ResponseGenerator(fileInfo); var content = generator.GetFileContent(); _response.ContentType = ConfigurationManager.AppSettings["appXbinary"]; _response.ContentLength64 = content.Length; _response.OutputStream.Write(content, 0, content.Length); _errorHandler.ReturnSuccess(_response); } catch (UnauthorizedAccessException) { _errorHandler.ReturnFileUnknown(_response); } catch (FileNotFoundException) { _errorHandler.ReturnFileUnknown(_response); } } }
public WopiResponse Handle() { var requestData = WopiRequest.ParseRequest(_request); try { var documentId = Convert.ToInt64(requestData.Id); FB.DeleteDocument(documentId); } catch (Exception ex) { return(new WopiResponse() { ResponseType = WopiResponseType.ServerError, Message = ex.Message }); } return(new WopiResponse() { ResponseType = WopiResponseType.Success }); }
public void HandleGetFileRequest(WopiRequest requestData) { lock (this) { if (!_authorization.ValidateAccess(requestData, writeAccessRequired: false)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } try { FileInfo fileInfo = new FileInfo(requestData.FullPath); ResponseGenerator generator = new ResponseGenerator(fileInfo); var content = generator.GetFileContent(); _response.ContentType = @"application/x-binary"; _response.ContentLength64 = content.Length; _response.OutputStream.Write(content, 0, content.Length); _errorHandler.ReturnSuccess(_response); } catch (UnauthorizedAccessException) { _errorHandler.ReturnFileUnknown(_response); } catch (FileNotFoundException) { _errorHandler.ReturnFileUnknown(_response); } }
public abstract Task <bool> Authorize(WopiRequest wopiRequest);
public WopiResponse Handle() { var requestData = WopiRequest.ParseRequest(_request); var documentId = Convert.ToInt64(requestData.Id); var document = FB.GetDocument(documentId); if (document == null) { return(new WopiResponse() { ResponseType = WopiResponseType.FileUnknown, Message = "File not found" }); } var newLock = new LockInfo() { DateCreated = DateTime.UtcNow, Lock = _request.Headers[WopiHeaders.Lock] }; var oldLock = _request.Headers[WopiHeaders.OldLock]; var currentLockInfo = FB.GetLockInfo(document); var currentLock = !string.IsNullOrEmpty(currentLockInfo) ? Newtonsoft.Json.JsonConvert.DeserializeObject <LockInfo>(currentLockInfo) : null; if (currentLock == null) { // The file is not currently locked or the lock has already expired FB.UpdateLockInfo(document, Newtonsoft.Json.JsonConvert.SerializeObject(newLock)); return(new WopiResponse() { ResponseType = WopiResponseType.Success }); } lock (currentLock) { // unlock & relock: if (!string.IsNullOrEmpty(oldLock)) { if (oldLock == currentLock.Lock && oldLock != newLock.Lock) { FB.UpdateLockInfo(document, Newtonsoft.Json.JsonConvert.SerializeObject(newLock)); return(new WopiResponse() { ResponseType = WopiResponseType.Success }); } if (oldLock != currentLock.Lock) { return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch, Message = currentLock.Lock }); } } if (currentLock.Lock != newLock.Lock) { // trying to lock again with a different lock? YEAH RIGHT return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch, Message = currentLock.Lock }); } // locking with the same lock? validator says this should be ok: return(new WopiResponse() { ResponseType = WopiResponseType.Success }); } }
public bool ValidateAccess(WopiRequest requestData, bool writeAccessRequired) { return(!String.IsNullOrWhiteSpace(requestData.AccessToken) && (requestData.AccessToken != "INVALID")); }
private static WopiRequest ParseRequest(HttpListenerRequest request) { WopiRequest requestData = new WopiRequest() { Type = RequestType.None, AccessToken = request.QueryString["access_token"], Id = "", LockId = request.Headers[WopiHeaders.Lock], OldLockId = request.Headers[WopiHeaders.OldLock] }; string requestPath = request.Url.AbsolutePath; string wopiPath = requestPath.Substring(WopiPath.Length); if (wopiPath.StartsWith(FilesRequestPath)) { string rawId = wopiPath.Substring(FilesRequestPath.Length); if (rawId.EndsWith(ContentsRequestPath)) { requestData.Id = rawId.Substring(0, rawId.Length - ContentsRequestPath.Length); if (request.HttpMethod == "GET") { requestData.Type = RequestType.GetFile; } if (request.HttpMethod == "POST") { requestData.Type = RequestType.PutFile; using (var memstream = new MemoryStream()) { memstream.Flush(); memstream.Position = 0; request.InputStream.CopyTo(memstream); requestData.FileData = memstream.ToArray(); } } } else { requestData.Id = rawId; if (request.HttpMethod == "GET") { requestData.Type = RequestType.CheckFileInfo; } else if (request.HttpMethod == "POST") { string wopiOverride = request.Headers[WopiHeaders.RequestType]; switch (wopiOverride) { case "PUT_RELATIVE": requestData.Type = RequestType.PutRelativeFile; if (request.Headers[WopiHeaders.RelativeTarget] != null) { requestData.RelativeTarget = request.Headers[WopiHeaders.RelativeTarget]; } if (request.Headers[WopiHeaders.SuggestedTarget] != null) { requestData.SuggestedTarget = request.Headers[WopiHeaders.SuggestedTarget]; } if (request.Headers[WopiHeaders.OverwriteRelativeTarget] != null) { requestData.OverwriteTarget = bool.Parse(request.Headers[WopiHeaders.OverwriteRelativeTarget]); } using (var memstream = new MemoryStream()) { memstream.Flush(); memstream.Position = 0; request.InputStream.CopyTo(memstream); requestData.FileData = memstream.ToArray(); } break; case "LOCK": if (request.Headers[WopiHeaders.OldLock] != null) { requestData.Type = RequestType.UnlockAndRelock; } else { requestData.Type = RequestType.Lock; } break; case "UNLOCK": requestData.Type = RequestType.Unlock; break; case "REFRESH_LOCK": requestData.Type = RequestType.RefreshLock; break; case "COBALT": requestData.Type = RequestType.ExecuteCobaltRequest; break; case "DELETE": requestData.Type = RequestType.DeleteFile; break; case "READ_SECURE_STORE": requestData.Type = RequestType.ReadSecureStore; break; case "GET_RESTRICTED_LINK": requestData.Type = RequestType.GetRestrictedLink; break; case "REVOKE_RESTRICTED_LINK": requestData.Type = RequestType.RevokeRestrictedLink; break; case "GET_LOCK": requestData.Type = RequestType.GetLock; break; } } } } else if (wopiPath.StartsWith(FoldersRequestPath)) { string rawId = wopiPath.Substring(FoldersRequestPath.Length); if (rawId.EndsWith(ChildrenRequestPath)) { requestData.Id = rawId.Substring(0, rawId.Length - ChildrenRequestPath.Length); requestData.Type = RequestType.EnumerateChildren; } else { requestData.Id = rawId; requestData.Type = RequestType.CheckFolderInfo; } } else { requestData.Type = RequestType.None; } return(requestData); }
public WopiResponse Handle() { var requestData = WopiRequest.ParseRequest(_request); try { var documentId = Convert.ToInt64(requestData.Id); var document = FB.GetDocument(documentId); if (document == null) { return(new WopiResponse() { ResponseType = WopiResponseType.FileUnknown, Message = "File not found" }); } var currentLock = ""; if (LockHelper.IsLockMismatch(_request, document, out currentLock)) { return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch, Message = currentLock }); } // FileInfo putTargetFileInfo = new FileInfo(requestData.FullPath); // The WOPI spec allows for a PutFile to succeed on a non-locked file if the file is currently zero bytes in length. // This allows for a more efficient Create New File flow that saves the Lock roundtrips. //if (!hasExistingLock && putTargetFileInfo.Length != 0) //{ // // With no lock and a non-zero file, a PutFile could potentially result in data loss by clobbering // // existing content. Therefore, return a lock mismatch error. // ReturnLockMismatch(context.Response, reason: "PutFile on unlocked file with current size != 0"); //} // Either the file has a valid lock that matches the lock in the request, or the file is unlocked // and is zero bytes. Either way, proceed with the PutFile. var binary = IOHelper.StreamToBytes(_request.InputStream); FB.SaveDocument(document, binary); } catch (UnauthorizedAccessException uex) { return(new WopiResponse() { ResponseType = WopiResponseType.FileUnknown, Message = uex.Message }); } catch (Exception ex) { return(new WopiResponse() { ResponseType = WopiResponseType.ServerError, Message = ex.Message }); } return(new WopiResponse() { ResponseType = WopiResponseType.Success }); }
/// <summary> /// Called at the beginning of a WOPI request to parse the request and determine the request type /// </summary> public static WopiRequest ParseRequest(HttpRequest request) { // Initilize wopi request data object with default values WopiRequest requestData = new WopiRequest() { RequestType = WopiRequestType.None, AccessToken = request.QueryString["access_token"], Id = "" }; // Get request path, e.g. /<...>/wopi/files/<id> string requestPath = request.Url.AbsolutePath.ToLower(); // Remove /<...>/wopi/ string wopiPath = requestPath.Substring(requestPath.IndexOf(WopiUtil.WOPI_BASE_PATH) + WopiUtil.WOPI_BASE_PATH.Length); // Check the type of request being made if (wopiPath.StartsWith(WopiUtil.WOPI_FILES_PATH)) { // This is a file-related request // Remove /files/ from the beginning of wopiPath string rawId = wopiPath.Substring(WopiUtil.WOPI_FILES_PATH.Length); if (rawId.EndsWith(WopiUtil.WOPI_CONTENTS_PATH)) { // The rawId ends with /contents so this is a request to read/write the file contents // Remove /contents from the end of rawId to get the actual file id requestData.Id = rawId.Substring(0, rawId.Length - WopiUtil.WOPI_CONTENTS_PATH.Length); // Check request verb to determine file operation if (request.HttpMethod == "GET") { requestData.RequestType = WopiRequestType.GetFile; } if (request.HttpMethod == "POST") { requestData.RequestType = WopiRequestType.PutFile; } } else { requestData.Id = rawId; if (request.HttpMethod == "GET") { // GET requests to the file are always CheckFileInfo requestData.RequestType = WopiRequestType.CheckFileInfo; } else if (request.HttpMethod == "POST") { // Use the X-WOPI-Override header to determine the request type for POSTs string wopiOverride = request.Headers[WopiRequestHeaders.OVERRIDE]; switch (wopiOverride) { case "LOCK": // Check lock type based on presence of OldLock header if (request.Headers[WopiRequestHeaders.OLD_LOCK] != null) { requestData.RequestType = WopiRequestType.UnlockAndRelock; } else { requestData.RequestType = WopiRequestType.Lock; } break; case "GET_LOCK": requestData.RequestType = WopiRequestType.GetLock; break; case "REFRESH_LOCK": requestData.RequestType = WopiRequestType.RefreshLock; break; case "UNLOCK": requestData.RequestType = WopiRequestType.Unlock; break; case "PUT_RELATIVE": requestData.RequestType = WopiRequestType.PutRelativeFile; break; case "RENAME_FILE": requestData.RequestType = WopiRequestType.RenameFile; break; case "PUT_USER_INFO": requestData.RequestType = WopiRequestType.PutUserInfo; break; /* * // The following WOPI_Override values were referenced in the product group sample, but not in the documentation * case "COBALT": * requestData.RequestType = WopiRequestType.ExecuteCobaltRequest; * break; * case "DELETE": * requestData.RequestType = WopiRequestType.DeleteFile; * break; * case "READ_SECURE_STORE": * requestData.RequestType = WopiRequestType.ReadSecureStore; * break; * case "GET_RESTRICTED_LINK": * requestData.RequestType = WopiRequestType.GetRestrictedLink; * break; * case "REVOKE_RESTRICTED_LINK": * requestData.RequestType = WopiRequestType.RevokeRestrictedLink; * break; */ } } } } else if (wopiPath.StartsWith(WopiUtil.WOPI_FOLDERS_PATH)) { // This is a folder-related request // Remove /folders/ from the beginning of wopiPath string rawId = wopiPath.Substring(WopiUtil.WOPI_FOLDERS_PATH.Length); if (rawId.EndsWith(WopiUtil.WOPI_CHILDREN_PATH)) { // rawId ends with /children, so it's an EnumerateChildren request. // Remove /children from the end of rawId requestData.Id = rawId.Substring(0, WopiUtil.WOPI_CHILDREN_PATH.Length); //requestData.RequestType = WopiRequestType.EnumerateChildren; } else { // rawId doesn't end with /children, so it's a CheckFolderInfo. requestData.Id = rawId; //requestData.RequestType = WopiRequestType.CheckFolderInfo; } } else { // This is an unknown request requestData.RequestType = WopiRequestType.None; } return(requestData); }
public void HandlePutRelativeFileRequest(WopiRequest requestData) { lock (this) { // userId(user@polihub) will be passed from the policyHub application if (!_authorization.ValidateToken(requestData.AccessToken, "user@policyhub", requestData.Id)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } if (requestData.RelativeTarget != null && requestData.SuggestedTarget != null) { // Theses headers are mutually exclusive, so we should return a 501 Not Implemented _errorHandler.ReturnBadRequest(_response); _response.Close(); return; } else if (requestData.RelativeTarget == null && requestData.SuggestedTarget == null) { _errorHandler.ReturnBadRequest(_response); _response.Close(); return; } else if (requestData.RelativeTarget != null || requestData.SuggestedTarget != null) { string fileName = ""; string filePath = ""; if (requestData.RelativeTarget != null) { // Specific mode...use the exact filename fileName = requestData.RelativeTarget; bool IsinvalidName = (string.IsNullOrEmpty(fileName) || fileName.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) >= 0); if (IsinvalidName || fileName.IndexOf('.') == 0) { _errorHandler.ReturnBadRequest(_response); _response.Close(); return; } filePath = Path.Combine(WopiHandler.LocalStoragePath, fileName); //if file already exist if (File.Exists(filePath)) { if (!requestData.OverwriteTarget) { while (File.Exists(filePath)) { int i = 1; var filenamewithoutext = Path.GetFileNameWithoutExtension(fileName); fileName = filenamewithoutext + "_" + i.ToString() + Path.GetExtension(filePath); filePath = Path.Combine(WopiHandler.LocalStoragePath, fileName); i = i + 1; } _response.AddHeader(WopiHeaders.ValidRelativeTarget, fileName); _errorHandler.ReturnConflict(_response); _response.Close(); return; } else { LockInfo existingLock; bool hasExistingLock; lock (LockInfo.Locks) { hasExistingLock = LockInfo.TryGetLock(requestData.Id, out existingLock); } if (hasExistingLock) { _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _errorHandler.ReturnConflict(_response); _response.Close(); return; } } } } else { // Suggested mode...might just be an extension fileName = requestData.SuggestedTarget; if (fileName.IndexOf('.') == 0) { fileName = Path.GetFileNameWithoutExtension(requestData.Id) + fileName; } fileName = MakeValidFileName(fileName); filePath = Path.Combine(WopiHandler.LocalStoragePath, fileName); //if file already exist if (File.Exists(filePath)) { int i = 0; while (File.Exists(filePath)) { i = i + 1; var filenamewithoutext = Path.GetFileNameWithoutExtension(fileName); if (!filenamewithoutext.EndsWith(i.ToString())) { if (filenamewithoutext.Substring(filenamewithoutext.Length - 1) == (i - 1).ToString()) { filenamewithoutext = filenamewithoutext.Substring(0, filenamewithoutext.Length - 1); } fileName = filenamewithoutext + i.ToString() + Path.GetExtension(filePath); } filePath = WopiHandler.LocalStoragePath + fileName; } } } try { File.WriteAllBytes(filePath, requestData.FileData); _response.ContentType = @"application / json"; string fileurl = String.Format(@"http://localhost:8080/wopi/files/{0}?access_token={1}", fileName, requestData.AccessToken); PutRelativeFileResponse putRelative = new PutRelativeFileResponse { Name = fileName, Url = fileurl }; DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(PutRelativeFileResponse)); MemoryStream msObj = new MemoryStream(); js.WriteObject(msObj, putRelative); msObj.Position = 0; StreamReader sr = new StreamReader(msObj); string json = sr.ReadToEnd(); var jsonResponse = Encoding.ASCII.GetBytes(json); _response.ContentLength64 = jsonResponse.Length; _response.OutputStream.Write(jsonResponse, 0, jsonResponse.Length); _response.StatusCode = (int)HttpStatusCode.OK; } catch (IOException) { _errorHandler.ReturnServerError(_response); } _response.Close(); } } }
public void HandlePutFileRequest(WopiRequest requestData) { lock (this) { // userId(user@polihub) will be passed from the policyHub application if (!_authorization.ValidateToken(requestData.AccessToken, "user@policyhub", requestData.Id)) { _errorHandler.ReturnInvalidToken(_response); _response.Close(); return; } if (!File.Exists(requestData.FullPath)) { _errorHandler.ReturnFileUnknown(_response); _response.Close(); return; } string newLock = requestData.LockId; LockInfo existingLock; bool hasExistingLock; lock (LockInfo.Locks) { hasExistingLock = LockInfo.TryGetLock(requestData.Id, out existingLock); } if (hasExistingLock && existingLock.Lock != newLock) { // lock mismatch/locked by another interface _errorHandler.ReturnLockMismatch(_response, existingLock.Lock); _response.AddHeader(WopiHeaders.Lock, existingLock.Lock); _response.AddHeader(WopiHeaders.LockFailureReason, "Lock mismatch/Locked by another interface"); _response.StatusCode = (int)HttpStatusCode.Conflict; _response.Close(); return; } FileInfo putTargetFileInfo = new FileInfo(requestData.FullPath); if (!hasExistingLock && putTargetFileInfo.Length != 0) { _response.AddHeader(WopiHeaders.Lock, newLock); _response.AddHeader(WopiHeaders.LockFailureReason, "PutFile on unlocked file with current size != 0"); _response.StatusCode = (int)HttpStatusCode.Conflict; _errorHandler.ReturnLockMismatch(_response, reason: "PutFile on unlocked file with current size != 0"); _response.Close(); return; } try { ResponseGenerator generator = new ResponseGenerator(putTargetFileInfo); generator.Save(requestData.FileData); _response.ContentLength64 = 0; _response.ContentType = @"text/html"; _response.StatusCode = (int)HttpStatusCode.OK; } catch (UnauthorizedAccessException) { _errorHandler.ReturnFileUnknown(_response); } catch (IOException) { _errorHandler.ReturnServerError(_response); } _response.Close(); } }
public async Task <HttpResponseMessage> ProcessPostActions(string file_id) { WopiRequest wopiRequest = new WopiRequest(this.Request, file_id); WopiResponse wopiResponse = null; try { if (await Authorize(wopiRequest)) { if (await WopiProof.Validate(wopiRequest)) { var filesPostOverride = WopiRequest.GetHttpRequestHeader(this.Request, WopiRequestHeaders.OVERRIDE); switch (filesPostOverride) { case "LOCK": var oldLock = WopiRequest.GetHttpRequestHeader(this.Request, WopiRequestHeaders.OLD_LOCK); if (oldLock != null) { wopiResponse = await UnlockAndRelock(new UnlockAndRelockRequest(this.Request, file_id)); } else { wopiResponse = await Lock(new LockRequest(this.Request, file_id)); } break; case "GET_LOCK": wopiResponse = await GetLock(new GetLockRequest(this.Request, file_id)); break; case "REFRESH_LOCK": wopiResponse = await RefreshLock(new RefreshLockRequest(this.Request, file_id)); break; case "UNLOCK": wopiResponse = await Unlock(new UnlockRequest(this.Request, file_id)); break; case "PUT_RELATIVE": var suggestedTarget = WopiRequest.GetHttpRequestHeader(this.Request, WopiRequestHeaders.SUGGESTED_TARGET); var relativeTarget = WopiRequest.GetHttpRequestHeader(this.Request, WopiRequestHeaders.RELATIVE_TARGET); if (suggestedTarget != null && relativeTarget != null) { // This really should be BadRequest, but the spec requires NotImplmented wopiResponse = new WopiResponse() { StatusCode = HttpStatusCode.NotImplemented }; } else { if (suggestedTarget != null) { wopiResponse = await PutRelativeFileSuggested(new PutRelativeFileSuggestedRequest(this.Request, file_id)); } else if (relativeTarget != null) { wopiResponse = await PutRelativeFileSpecific(new PutRelativeFileSpecificRequest(this.Request, file_id)); } else // Both are null { wopiResponse = new WopiResponse() { StatusCode = HttpStatusCode.BadRequest } }; } break; case "RENAME_FILE": wopiResponse = await RenameFile(new RenameFileRequest(this.Request, file_id)); break; case "PUT_USER_INFO": wopiResponse = await PutUserInfo(new PutUserInfoRequest(this.Request, file_id)); break; case "DELETE": wopiResponse = await DeleteFile(new DeleteFileRequest(this.Request, file_id)); break; default: wopiResponse = wopiRequest.ResponseServerError(string.Format("Invalid {0} header value: {1}", WopiRequestHeaders.OVERRIDE, filesPostOverride)); break; } } else { wopiResponse = wopiRequest.ResponseServerError("Proof validation failed"); } } else { wopiResponse = wopiRequest.ResponseUnauthorized(); } } catch (Exception ex) { wopiResponse = wopiRequest.ResponseServerError(ex.Message); } return(wopiResponse.ToHttpResponse()); }
public WopiJsonResponse Handle() { var requestData = WopiRequest.ParseRequest(_request); var newName = _request.Headers[WopiHeaders.RequestedName]; try { // if the file name is illegal then // return 400 // add response header WopiHeaders.InvalidFileNameError - describing the reason the rename operation could not be completed if (string.IsNullOrEmpty(newName)) { return(new WopiJsonResponse() { StatusCode = 400, Headers = new NameValueCollection { { WopiHeaders.InvalidFileNameError, "Invalid filename for rename" } }, }); } newName = IOHelper.Utf7Encode(newName); var documentId = Convert.ToInt64(requestData.Id); var document = FB.GetDocument(documentId); if (document == null) { return(new WopiJsonResponse() { StatusCode = 404, Json = new RenameResponse() }); } var currentLock = ""; if (LockHelper.IsLockMismatch(_request, document, out currentLock)) { return(new WopiJsonResponse() { StatusCode = 409, Json = new RenameResponse() }); } // rename the document: FB.RenameDocument(document, newName); } catch (Exception ex) { //return new WopiResponse() { ResponseType = WopiResponseType.ServerError, Message = ex.Message }; return(new WopiJsonResponse() { StatusCode = 500, Json = new { Message = ex.Message } }); } //return new WopiResponse() { ResponseType = WopiResponseType.Success }; return(new WopiJsonResponse() { StatusCode = 200, Json = new RenameResponse() { Name = newName } }); }
public WopiJsonResponse Handle() { var requestData = WopiRequest.ParseRequest(_request); var responseData = new PutRelativeResponse(); var documentId = Convert.ToInt64(requestData.Id); var relativeTarget = _request.Headers[WopiHeaders.RelativeTarget]; var suggestedTarget = _request.Headers[WopiHeaders.SuggestedTarget]; // make sure we don't have both headers present: if (!string.IsNullOrEmpty(relativeTarget) && !string.IsNullOrEmpty(suggestedTarget)) { return(new WopiJsonResponse() { StatusCode = 501, Json = new PutRelativeResponse() }); } var overwriteRelative = _request.Headers[WopiHeaders.OverwriteRelativeTarget]; string extension; if (!string.IsNullOrEmpty(relativeTarget)) { // check if we have a file matching the target name // and if so, return 409 conflict w/ lock response extension = relativeTarget.Substring(relativeTarget.LastIndexOf(".") + 1); responseData.Name = IOHelper.Utf7Encode(relativeTarget); // extension should already be here, we just need to get it for below var overwriteExisting = !string.IsNullOrEmpty(overwriteRelative) && overwriteRelative.ToLower().Equals("true"); var relativeDocument = FB.GetDocumentByNameAndExtension(responseData.Name, extension, documentId); // does this document already exist? if (relativeDocument != null) { // lock check - make sure the existing document isn't locked: var currentLock = ""; if (LockHelper.IsLockMismatch(_request, relativeDocument, out currentLock)) { return(new WopiJsonResponse() { StatusCode = 409, Json = responseData }); } // not locked - but the document exists, so make sure the overwrite existing header is set: if (!overwriteExisting) { return(new WopiJsonResponse() { StatusCode = 409, Json = responseData }); } } } else { // suggested mode: // save the file with whatever name we want, and return that name: extension = suggestedTarget.Substring(suggestedTarget.LastIndexOf(".") + 1); responseData.Name = "wopitest_putrelative." + extension; } var binary = IOHelper.StreamToBytes(_request.InputStream); var newDocumentId = FB.SaveNewDocument(binary, extension, responseData.Name, documentId); var newToken = FB.GetAccessToken(newDocumentId); responseData.Url = $"{Constants.WopiApiUrl}wopi/files/{newDocumentId}?access_token={newToken}"; return(new WopiJsonResponse() { StatusCode = 200, Json = responseData }); }
public WopiResponse Handle() { var requestData = WopiRequest.ParseRequest(_request); var documentId = Convert.ToInt64(requestData.Id); var document = FB.GetDocument(documentId); try { if (document == null) { return(new WopiResponse() { ResponseType = WopiResponseType.FileUnknown, Message = "File not found" }); } var newLock = _request.Headers[WopiHeaders.Lock]; var currentLockInfo = FB.GetLockInfo(document); var currentLock = !string.IsNullOrEmpty(currentLockInfo) ? Newtonsoft.Json.JsonConvert.DeserializeObject <LockInfo>(currentLockInfo) : null; if (currentLock == null) { return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch }); } lock (currentLock) { if (currentLock.Lock == newLock) { // There is a valid lock on the file and the existing lock matches the provided one FB.UpdateLockInfo(document, string.Empty); return(new WopiResponse() { ResponseType = WopiResponseType.Success }); } else { // The existing lock doesn't match the requested one. Return a lock mismatch error return(new WopiResponse() { ResponseType = WopiResponseType.LockMismatch, Message = currentLock.Lock }); } } } catch (Exception ex) { FB.UpdateLockInfo(document, ex.Message); return(new WopiResponse() { ResponseType = WopiResponseType.ServerError, Message = ex.Message }); } }