001/*
002 * (C) Copyright 2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Thomas Roger
018 */
019
020package org.nuxeo.wopi.jaxrs;
021
022import static org.nuxeo.wopi.Constants.WOPI_BASE_URL_PROPERTY;
023import static org.nuxeo.wopi.Headers.PROOF;
024import static org.nuxeo.wopi.Headers.PROOF_OLD;
025import static org.nuxeo.wopi.Headers.TIMESTAMP;
026
027import javax.ws.rs.Path;
028import javax.ws.rs.PathParam;
029import javax.ws.rs.core.Context;
030import javax.ws.rs.core.HttpHeaders;
031
032import org.nuxeo.ecm.core.api.Blob;
033import org.nuxeo.ecm.core.api.CoreSession;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.DocumentRef;
036import org.nuxeo.ecm.core.api.IdRef;
037import org.nuxeo.ecm.platform.web.common.RequestContext;
038import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
039import org.nuxeo.ecm.webengine.model.WebContext;
040import org.nuxeo.ecm.webengine.model.WebObject;
041import org.nuxeo.ecm.webengine.model.impl.ModuleRoot;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.wopi.Constants;
044import org.nuxeo.wopi.FileInfo;
045import org.nuxeo.wopi.Helpers;
046import org.nuxeo.wopi.WOPIService;
047import org.nuxeo.wopi.exception.InternalServerErrorException;
048import org.nuxeo.wopi.exception.NotFoundException;
049import org.nuxeo.wopi.lock.LockHelper;
050
051/**
052 * @since 10.3
053 */
054@Path("/")
055@WebObject(type = "wopi")
056public class WOPIRoot extends ModuleRoot {
057
058    protected static final String THREAD_NAME_PREFIX = "WOPI_";
059
060    @Context
061    protected HttpHeaders httpHeaders;
062
063    @Path("/files/{fileId}")
064    public Object filesResource(@PathParam("fileId") FileInfo fileInfo) {
065        // prefix thread name for logging purpose
066        prefixThreadName();
067
068        // verify proof key
069        verifyProofKey();
070
071        // flag the request as originating from a WOPI client for locking policy purpose
072        LockHelper.flagWOPIRequest();
073        RequestContext.getActiveContext(request).addRequestCleanupHandler(req -> LockHelper.unflagWOPIRequest());
074
075        WebContext context = getContext();
076        context.setRepositoryName(fileInfo.repositoryName);
077        CoreSession session = context.getCoreSession();
078        DocumentModel doc = getDocument(session, fileInfo.docId);
079        Blob blob = getBlob(doc, fileInfo.xpath);
080        return newObject("wopiFiles", session, doc, blob, fileInfo.xpath);
081    }
082
083    protected void prefixThreadName() {
084        Thread currentThread = Thread.currentThread();
085        String threadName = currentThread.getName();
086        if (!threadName.startsWith(THREAD_NAME_PREFIX)) {
087            currentThread.setName(THREAD_NAME_PREFIX + threadName);
088        }
089    }
090
091    protected void verifyProofKey() {
092        String proofKeyHeader = Helpers.getHeader(httpHeaders, PROOF);
093        String oldProofKeyHeader = Helpers.getHeader(httpHeaders, PROOF_OLD);
094        String timestampHeader = Helpers.getHeader(httpHeaders, TIMESTAMP);
095        String accessToken = request.getParameter(Constants.ACCESS_TOKEN_PARAMETER);
096        String wopiBaseURL = Framework.getProperty(WOPI_BASE_URL_PROPERTY, VirtualHostHelper.getBaseURL(request));
097        // remove /nuxeo/ from the request URI
098        String requestURI = request.getRequestURI().substring(request.getContextPath().length() + 1);
099        String url = String.format("%s%s?%s", wopiBaseURL, requestURI, request.getQueryString());
100        if (!Framework.getService(WOPIService.class)
101                      .verifyProofKey(proofKeyHeader, oldProofKeyHeader, url, accessToken, timestampHeader)) {
102            throw new InternalServerErrorException();
103        }
104    }
105
106    protected DocumentModel getDocument(CoreSession session, String fileId) {
107        DocumentRef ref = new IdRef(fileId);
108        if (!session.exists(ref)) {
109            throw new NotFoundException();
110        }
111        return session.getDocument(ref);
112    }
113
114    protected Blob getBlob(DocumentModel doc, String xpath) {
115        Blob blob = Helpers.getEditableBlob(doc, xpath);
116        if (blob == null) {
117            throw new NotFoundException();
118        }
119        return blob;
120    }
121
122}