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}