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}