001/*
002 * (C) Copyright 2011-2017 Nuxeo SA (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 *     Arnaud Kervern
018 */
019package org.nuxeo.ecm.core.storage.sql;
020
021import static org.nuxeo.runtime.api.Framework.isBooleanPropertyTrue;
022
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.IOException;
026import java.net.URI;
027import java.net.URISyntaxException;
028import java.security.PrivateKey;
029import java.security.spec.InvalidKeySpecException;
030import java.util.Date;
031
032import javax.servlet.http.HttpServletRequest;
033
034import org.apache.http.client.utils.URIBuilder;
035import org.nuxeo.ecm.core.blob.ManagedBlob;
036
037import com.amazonaws.AmazonClientException;
038import com.amazonaws.auth.PEM;
039import com.amazonaws.auth.RSA;
040import com.amazonaws.services.cloudfront.CloudFrontUrlSigner;
041import com.amazonaws.services.cloudfront.util.SignerUtils.Protocol;
042import com.amazonaws.util.IOUtils;
043
044/**
045 * @since 9.1
046 */
047public class CloudFrontBinaryManager extends S3BinaryManager {
048
049    private static final String BASE_PROP = "cloudfront.";
050
051    public static final String PRIVATE_KEY_PROPERTY = BASE_PROP + "privKey";
052
053    public static final String PRIVATE_KEY_ID_PROPERTY = BASE_PROP + "privKeyId";
054
055    public static final String DISTRIB_DOMAIN_PROPERTY = BASE_PROP + "distribDomain";
056
057    public static final String PROTOCOL_PROPERTY = BASE_PROP + "protocol";
058
059    public static final String ENABLE_CF_ENCODING_FIX = "nuxeo.s3storage.cloudfront.fix.encoding";
060
061    protected String distributionDomain;
062
063    protected Protocol protocol;
064
065    protected PrivateKey privKey;
066
067    protected String privKeyId;
068
069    @Override
070    protected void setupCloudClient() throws IOException {
071        super.setupCloudClient();
072
073        protocol = Protocol.valueOf(getProperty(PROTOCOL_PROPERTY, "https"));
074        distributionDomain = getProperty(DISTRIB_DOMAIN_PROPERTY);
075        directDownload = Boolean.parseBoolean(getProperty(DIRECTDOWNLOAD_PROPERTY, Boolean.TRUE.toString()));
076
077        try {
078            String privateKeyPath = getProperty(PRIVATE_KEY_PROPERTY);
079            privKey = loadPrivateKey(privateKeyPath);
080            privKeyId = getProperty(PRIVATE_KEY_ID_PROPERTY);
081        } catch (InvalidKeySpecException e) {
082            throw new IOException(e);
083        }
084    }
085
086    @Override
087    protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException {
088        try {
089            URIBuilder uriBuilder = new URIBuilder(buildResourcePath(bucketNamePrefix + digest));
090            if (blob != null) {
091                uriBuilder.addParameter("response-content-type", getContentTypeHeader(blob));
092                uriBuilder.addParameter("response-content-disposition",
093                        getContentDispositionHeader(blob, servletRequest));
094            }
095
096            if (isBooleanPropertyTrue(ENABLE_CF_ENCODING_FIX)) {
097                String trimmedChars = " ";
098                uriBuilder.getQueryParams().stream().filter(s -> (s.getValue() != null && s.getValue().contains(trimmedChars))).forEach(
099                        s -> uriBuilder.setParameter(s.getName(), s.getValue().replace(trimmedChars, "")));
100            }
101
102            URI uri = uriBuilder.build();
103            if (privKey == null) {
104                return uri;
105            }
106
107            Date expiration = new Date();
108            expiration.setTime(expiration.getTime() + directDownloadExpire * 1000);
109
110            String signedURL = CloudFrontUrlSigner.getSignedURLWithCannedPolicy(uri.toString(), privKeyId, privKey,
111                    expiration);
112            return new URI(signedURL);
113        } catch (URISyntaxException e) {
114            throw new IOException(e);
115        }
116    }
117
118    private String buildResourcePath(String s3ObjectKey) {
119        return protocol != Protocol.http && protocol != Protocol.https
120                ? s3ObjectKey : protocol + "://" + distributionDomain + "/" + s3ObjectKey;
121    }
122
123    private static PrivateKey loadPrivateKey(String privateKeyPath) throws InvalidKeySpecException, IOException {
124        if (privateKeyPath == null) {
125            return null;
126        }
127
128        try (FileInputStream is = new FileInputStream(new File(privateKeyPath))) {
129            if (privateKeyPath.toLowerCase().endsWith(".pem")) {
130                return PEM.readPrivateKey(is);
131            }
132
133            if (privateKeyPath.toLowerCase().endsWith(".der")) {
134                return RSA.privateKeyFromPKCS8(IOUtils.toByteArray(is));
135            }
136
137            throw new AmazonClientException("Unsupported file type for private key");
138        }
139    }
140}