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.Headers.PROOF;
023import static org.nuxeo.wopi.Headers.PROOF_OLD;
024import static org.nuxeo.wopi.Headers.TIMESTAMP;
025
026import javax.ws.rs.Path;
027import javax.ws.rs.PathParam;
028import javax.ws.rs.core.Context;
029import javax.ws.rs.core.HttpHeaders;
030
031import org.nuxeo.ecm.core.api.Blob;
032import org.nuxeo.ecm.core.api.CoreSession;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.DocumentRef;
035import org.nuxeo.ecm.core.api.IdRef;
036import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
037import org.nuxeo.ecm.webengine.jaxrs.context.RequestContext;
038import org.nuxeo.ecm.webengine.model.WebContext;
039import org.nuxeo.ecm.webengine.model.WebObject;
040import org.nuxeo.ecm.webengine.model.impl.ModuleRoot;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.wopi.Constants;
043import org.nuxeo.wopi.FileInfo;
044import org.nuxeo.wopi.Helpers;
045import org.nuxeo.wopi.WOPIService;
046import org.nuxeo.wopi.exception.InternalServerErrorException;
047import org.nuxeo.wopi.exception.NotFoundException;
048import org.nuxeo.wopi.lock.LockHelper;
049
050/**
051 * @since 10.3
052 */
053@Path("/")
054@WebObject(type = "wopi")
055public class WOPIRoot extends ModuleRoot {
056
057    protected static final String THREAD_NAME_PREFIX = "WOPI_";
058
059    @Context
060    protected HttpHeaders httpHeaders;
061
062    @Path("/files/{fileId}")
063    public Object filesResource(@PathParam("fileId") FileInfo fileInfo) {
064        // prefix thread name for logging purpose
065        prefixThreadName();
066
067        // verify proof key
068        verifyProofKey();
069
070        // flag the request as originating from a WOPI client for locking policy purpose
071        LockHelper.flagWOPIRequest();
072        RequestContext.getActiveContext(request).addRequestCleanupHandler(req -> LockHelper.unflagWOPIRequest());
073
074        WebContext context = getContext();
075        context.setRepositoryName(fileInfo.repositoryName);
076        CoreSession session = context.getCoreSession();
077        DocumentModel doc = getDocument(session, fileInfo.docId);
078        Blob blob = getBlob(doc, fileInfo.xpath);
079        return newObject("wopiFiles", session, doc, blob, fileInfo.xpath);
080    }
081
082    protected void prefixThreadName() {
083        Thread currentThread = Thread.currentThread();
084        String threadName = currentThread.getName();
085        if (!threadName.startsWith(THREAD_NAME_PREFIX)) {
086            currentThread.setName(THREAD_NAME_PREFIX + threadName);
087        }
088    }
089
090    protected void verifyProofKey() {
091        String proofKeyHeader = Helpers.getHeader(httpHeaders, PROOF);
092        String oldProofKeyHeader = Helpers.getHeader(httpHeaders, PROOF_OLD);
093        String timestampHeader = Helpers.getHeader(httpHeaders, TIMESTAMP);
094        String accessToken = request.getParameter(Constants.ACCESS_TOKEN_PARAMETER);
095        String url = String.format("%s%s?%s", VirtualHostHelper.getServerURL(request),
096                request.getRequestURI().substring(1), request.getQueryString());
097        if (!Framework.getService(WOPIService.class)
098                      .verifyProofKey(proofKeyHeader, oldProofKeyHeader, url, accessToken, timestampHeader)) {
099            throw new InternalServerErrorException();
100        }
101    }
102
103    protected DocumentModel getDocument(CoreSession session, String fileId) {
104        DocumentRef ref = new IdRef(fileId);
105        if (!session.exists(ref)) {
106            throw new NotFoundException();
107        }
108        return session.getDocument(ref);
109    }
110
111    protected Blob getBlob(DocumentModel doc, String xpath) {
112        Blob blob = Helpers.getEditableBlob(doc, xpath);
113        if (blob == null) {
114            throw new NotFoundException();
115        }
116        return blob;
117    }
118
119}