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
076        try {
077            String privateKeyPath = getProperty(PRIVATE_KEY_PROPERTY);
078            privKey = loadPrivateKey(privateKeyPath);
079            privKeyId = getProperty(PRIVATE_KEY_ID_PROPERTY);
080        } catch (InvalidKeySpecException e) {
081            throw new IOException(e);
082        }
083    }
084
085    @Override
086    protected URI getRemoteUri(String digest, ManagedBlob blob, HttpServletRequest servletRequest) throws IOException {
087        try {
088            URIBuilder uriBuilder = new URIBuilder(buildResourcePath(bucketNamePrefix + digest));
089            if (blob != null) {
090                uriBuilder.addParameter("response-content-type", getContentTypeHeader(blob));
091                uriBuilder.addParameter("response-content-disposition",
092                        getContentDispositionHeader(blob, servletRequest));
093            }
094
095            if (isBooleanPropertyTrue(ENABLE_CF_ENCODING_FIX)) {
096                String trimmedChars = " ";
097                uriBuilder.getQueryParams().stream().filter(s -> s.getValue().contains(trimmedChars)).forEach(
098                        s -> uriBuilder.setParameter(s.getName(), s.getValue().replace(trimmedChars, "")));
099            }
100
101            URI uri = uriBuilder.build();
102            if (privKey == null) {
103                return uri;
104            }
105
106            Date expiration = new Date();
107            expiration.setTime(expiration.getTime() + directDownloadExpire * 1000);
108
109            String signedURL = CloudFrontUrlSigner.getSignedURLWithCannedPolicy(uri.toString(), privKeyId, privKey,
110                    expiration);
111            return new URI(signedURL);
112        } catch (URISyntaxException e) {
113            throw new IOException(e);
114        }
115    }
116
117    private String buildResourcePath(String s3ObjectKey) {
118        return protocol != Protocol.http && protocol != Protocol.https
119                ? s3ObjectKey : protocol + "://" + distributionDomain + "/" + s3ObjectKey;
120    }
121
122    private static PrivateKey loadPrivateKey(String privateKeyPath) throws InvalidKeySpecException, IOException {
123        if (privateKeyPath == null) {
124            return null;
125        }
126
127        try (FileInputStream is = new FileInputStream(new File(privateKeyPath))) {
128            if (privateKeyPath.toLowerCase().endsWith(".pem")) {
129                return PEM.readPrivateKey(is);
130            }
131
132            if (privateKeyPath.toLowerCase().endsWith(".der")) {
133                return RSA.privateKeyFromPKCS8(IOUtils.toByteArray(is));
134            }
135
136            throw new AmazonClientException("Unsupported file type for private key");
137        }
138    }
139}