| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| package com.cloud.bridge.service; | |
| import java.io.ByteArrayInputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.InputStream; | |
| import java.io.UnsupportedEncodingException; | |
| import java.security.SignatureException; | |
| import java.sql.SQLException; | |
| import java.util.Enumeration; | |
| import javax.servlet.ServletConfig; | |
| import javax.servlet.ServletException; | |
| import javax.servlet.http.HttpServlet; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import javax.xml.parsers.DocumentBuilder; | |
| import javax.xml.parsers.DocumentBuilderFactory; | |
| import javax.xml.bind.*; | |
| import org.apache.axis2.AxisFault; | |
| import org.apache.log4j.Logger; | |
| import org.w3c.dom.Document; | |
| import org.w3c.dom.NamedNodeMap; | |
| import org.w3c.dom.Node; | |
| import org.w3c.dom.NodeList; | |
| import com.cloud.bridge.io.MultiPartDimeInputStream; | |
| import com.cloud.bridge.model.SAcl; | |
| import com.cloud.bridge.model.UserCredentialsVO; | |
| import com.cloud.bridge.persist.dao.CloudStackConfigurationDao; | |
| import com.cloud.bridge.persist.dao.CloudStackConfigurationDaoImpl; | |
| import com.cloud.bridge.persist.dao.UserCredentialsDao; | |
| import com.cloud.bridge.persist.dao.UserCredentialsDaoImpl; | |
| import com.cloud.bridge.service.controller.s3.S3BucketAction; | |
| import com.cloud.bridge.service.controller.s3.S3ObjectAction; | |
| import com.cloud.bridge.service.controller.s3.ServiceProvider; | |
| import com.cloud.bridge.service.controller.s3.ServletAction; | |
| import com.cloud.bridge.service.core.s3.S3AccessControlList; | |
| import com.cloud.bridge.service.core.s3.S3AuthParams; | |
| import com.cloud.bridge.service.core.s3.S3Engine; | |
| import com.cloud.bridge.service.core.s3.S3Grant; | |
| import com.cloud.bridge.service.core.s3.S3MetaDataEntry; | |
| import com.cloud.bridge.service.core.s3.S3PutObjectRequest; | |
| import com.cloud.bridge.service.core.s3.S3PutObjectResponse; | |
| import com.cloud.bridge.service.exception.InvalidBucketName; | |
| import com.cloud.bridge.service.exception.PermissionDeniedException; | |
| import com.cloud.bridge.util.ConfigurationHelper; | |
| import com.cloud.bridge.util.HeaderParam; | |
| import com.cloud.bridge.util.RestAuth; | |
| import com.cloud.bridge.util.S3SoapAuth; | |
| import com.cloud.utils.component.ComponentLocator; | |
| import com.cloud.utils.db.DB; | |
| import com.cloud.utils.db.Transaction; | |
| import net.sf.ehcache.Cache; | |
| public class S3RestServlet extends HttpServlet { | |
| private static final long serialVersionUID = -6168996266762804877L; | |
| public static final String ENABLE_S3_API="enable.s3.api"; | |
| private static boolean isS3APIEnabled = false; | |
| public static final Logger logger = Logger.getLogger(S3RestServlet.class); | |
| protected final CloudStackConfigurationDao csDao = ComponentLocator.inject(CloudStackConfigurationDaoImpl.class); | |
| protected final UserCredentialsDao ucDao = ComponentLocator.inject(UserCredentialsDaoImpl.class); | |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) { | |
| processRequest( req, resp, "GET" ); | |
| } | |
| protected void doPost(HttpServletRequest req, HttpServletResponse resp) | |
| { | |
| // -> DIME requests are authenticated via the SOAP auth mechanism | |
| String type = req.getHeader( "Content-Type" ); | |
| if ( null != type && type.equalsIgnoreCase( "application/dime" )) | |
| processDimeRequest(req, resp); | |
| else processRequest( req, resp, "POST" ); | |
| } | |
| protected void doPut(HttpServletRequest req, HttpServletResponse resp) { | |
| processRequest( req, resp, "PUT" ); | |
| } | |
| protected void doHead(HttpServletRequest req, HttpServletResponse resp) { | |
| processRequest( req, resp, "HEAD" ); | |
| } | |
| protected void doOptions(HttpServletRequest req, HttpServletResponse resp) { | |
| processRequest( req, resp, "OPTIONS" ); | |
| } | |
| protected void doDelete( HttpServletRequest req, HttpServletResponse resp ) { | |
| processRequest( req, resp, "DELETE" ); | |
| } | |
| public void init( ServletConfig config ) throws ServletException { | |
| try{ | |
| ConfigurationHelper.preConfigureConfigPathFromServletContext(config.getServletContext()); | |
| // check if API is enabled | |
| String value = csDao.getConfigValue(ENABLE_S3_API); | |
| if(value != null) { | |
| isS3APIEnabled = Boolean.valueOf(value); | |
| } | |
| logger.info("S3Engine :: Configuration value is : " + value); | |
| }catch(Exception e){ | |
| throw new ServletException("Error initializing awsapi: " + e.getMessage()); | |
| } | |
| } | |
| /** | |
| * POST requests do not get authenticated on entry. The associated | |
| * access key and signature headers are embedded in the message not encoded | |
| * as HTTP headers. | |
| */ | |
| private void processRequest( HttpServletRequest request, HttpServletResponse response, String method ) | |
| { | |
| Transaction txn = Transaction.open("cloudbridge", Transaction.AWSAPI_DB, true); | |
| try { | |
| logRequest(request); | |
| // Our extensions to the S3 REST API for simple management actions | |
| // are conveyed with Request parameter "Action". | |
| // The present extensions are either to set up the user credentials | |
| // (see the cloud-bridge-register script for more detail) or | |
| // to report our version of this capability. | |
| // -> unauthenticated calls, should still be done over HTTPS | |
| String cloudAction = request.getParameter( "Action" ); | |
| if(!isS3APIEnabled){ | |
| throw new RuntimeException("Amazon S3 API is disabled."); | |
| } | |
| if (null != cloudAction) | |
| { | |
| if (cloudAction.equalsIgnoreCase( "SetUserKeys" )) { | |
| setUserKeys(request, response); | |
| return; | |
| } | |
| if (cloudAction.equalsIgnoreCase( "SetCertificate" )) | |
| // At present a noop | |
| return; | |
| if (cloudAction.equalsIgnoreCase( "CloudS3Version" )) { | |
| cloudS3Version(request, response); | |
| return; | |
| } | |
| } | |
| txn.start(); | |
| // -> authenticated calls | |
| if ( !((method.equalsIgnoreCase( "POST" ) && !(request.getQueryString().equalsIgnoreCase("delete"))) ) ){ | |
| S3AuthParams params = extractRequestHeaders( request ); | |
| authenticateRequest( request, params ); | |
| } | |
| ServletAction action = routeRequest(request); | |
| if ( action != null ) { | |
| action.execute(request, response); | |
| } | |
| else { | |
| response.setStatus(404); | |
| endResponse(response, "File not found"); | |
| } | |
| txn.close(); | |
| } | |
| catch( InvalidBucketName e) { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| response.setStatus(400); | |
| endResponse(response, "Invalid Bucket Name - " + e.toString()); | |
| } | |
| catch(PermissionDeniedException e) { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| response.setStatus(403); | |
| endResponse(response, "Access denied - " + e.toString()); | |
| } | |
| catch(Throwable e) { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| response.setStatus(404); | |
| endResponse(response, "Bad request"); | |
| } finally { | |
| try { | |
| response.flushBuffer(); | |
| } catch (IOException e) { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| } | |
| } | |
| } | |
| /** | |
| * Provide an easy way to determine the version of the implementation running. | |
| * | |
| * This is an unauthenticated REST call. | |
| */ | |
| private void cloudS3Version( HttpServletRequest request, HttpServletResponse response ) { | |
| String version = new String( "<?xml version=\"1.0\" encoding=\"utf-8\"?><CloudS3Version>1.04</CloudS3Version>" ); | |
| response.setStatus(200); | |
| endResponse(response, version); | |
| } | |
| /** | |
| * This request registers the user Cloud.com account holder to the S3 service. The Cloud.com | |
| * account holder saves his API access and secret keys with the S3 service so that | |
| * each rest call he makes can be verified was originated from him. The given API access | |
| * and secret key are saved into the "usercredentials" database table. | |
| * | |
| * This is an unauthenticated REST call. The only required parameters are 'accesskey' and | |
| * 'secretkey'. | |
| * | |
| * To verify that the given keys represent an existing account they are used to execute the | |
| * Cloud.com's listAccounts API function. If the keys do not represent a valid account the | |
| * listAccounts function will fail. | |
| * | |
| * A user can call this REST function any number of times, on each call the Cloud.com secret | |
| * key is simply over writes any previously stored value. | |
| * | |
| * As with all REST calls HTTPS should be used to ensure their security. | |
| */ | |
| @DB | |
| private void setUserKeys( HttpServletRequest request, HttpServletResponse response ) { | |
| String[] accessKey = null; | |
| String[] secretKey = null; | |
| try { | |
| // -> both these parameters are required | |
| accessKey = request.getParameterValues( "accesskey" ); | |
| if ( null == accessKey || 0 == accessKey.length ) { | |
| response.sendError(530, "Missing accesskey parameter" ); | |
| return; | |
| } | |
| secretKey = request.getParameterValues( "secretkey" ); | |
| if ( null == secretKey || 0 == secretKey.length ) { | |
| response.sendError(530, "Missing secretkey parameter" ); | |
| return; | |
| } | |
| } catch( Exception e ) { | |
| logger.error("SetUserKeys exception " + e.getMessage(), e); | |
| response.setStatus(500); | |
| endResponse(response, "SetUserKeys exception " + e.getMessage()); | |
| return; | |
| } | |
| try { | |
| // -> use the keys to see if the account actually exists | |
| //ServiceProvider.getInstance().getEC2Engine().validateAccount( accessKey[0], secretKey[0] ); | |
| //UserCredentialsDaoImpl credentialDao = new UserCredentialsDao(); | |
| Transaction txn = Transaction.open(Transaction.AWSAPI_DB); | |
| txn.start(); | |
| UserCredentialsVO user = new UserCredentialsVO(accessKey[0], secretKey[0]); | |
| user = ucDao.persist(user); | |
| txn.commit(); | |
| txn.close(); | |
| //credentialDao.setUserKeys( accessKey[0], secretKey[0] ); | |
| } catch( Exception e ) { | |
| logger.error("SetUserKeys " + e.getMessage(), e); | |
| response.setStatus(401); | |
| endResponse(response, e.toString()); | |
| return; | |
| } | |
| response.setStatus(200); | |
| endResponse(response, "User keys set successfully"); | |
| } | |
| /** | |
| * We are using the S3AuthParams class to hide where the header values are coming | |
| * from so that the authenticateRequest call can be made from several places. | |
| */ | |
| public static S3AuthParams extractRequestHeaders( HttpServletRequest request ) { | |
| S3AuthParams params = new S3AuthParams(); | |
| Enumeration headers = request.getHeaderNames(); | |
| if (null != headers) | |
| { | |
| while( headers.hasMoreElements()) | |
| { | |
| HeaderParam oneHeader = new HeaderParam(); | |
| String headerName = (String)headers.nextElement(); | |
| oneHeader.setName( headerName ); | |
| oneHeader.setValue( request.getHeader( headerName )); | |
| params.addHeader( oneHeader ); | |
| } | |
| } | |
| return params; | |
| } | |
| public static void authenticateRequest( HttpServletRequest request, S3AuthParams params ) | |
| throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException | |
| { | |
| RestAuth auth = new RestAuth(ServiceProvider.getInstance().getUseSubDomain()); | |
| String AWSAccessKey = null; | |
| String signature = null; | |
| String authorization = null; | |
| // [A] Is it an annonymous request? | |
| if (null == (authorization = params.getHeader( "Authorization" ))) { | |
| UserContext.current().initContext(); | |
| return; | |
| } | |
| // [B] Is it an authenticated request? | |
| int offset = authorization.indexOf( "AWS" ); | |
| if (-1 != offset) { | |
| String temp = authorization.substring( offset+3 ).trim(); | |
| offset = temp.indexOf( ":" ); | |
| AWSAccessKey = temp.substring( 0, offset ); | |
| signature = temp.substring( offset+1 ); | |
| } | |
| // [C] Calculate the signature from the request's headers | |
| auth.setDateHeader( request.getHeader( "Date" )); | |
| auth.setContentTypeHeader( request.getHeader( "Content-Type" )); | |
| auth.setContentMD5Header( request.getHeader( "Content-MD5" )); | |
| auth.setHostHeader( request.getHeader( "Host" )); | |
| auth.setQueryString( request.getQueryString()); | |
| auth.addUriPath( request.getRequestURI()); | |
| // -> are their any Amazon specific (i.e. 'x-amz-' ) headers? | |
| HeaderParam[] headers = params.getHeaders(); | |
| for( int i=0; null != headers && i < headers.length; i++ ) | |
| { | |
| String headerName = headers[i].getName(); | |
| String ignoreCase = headerName.toLowerCase(); | |
| if (ignoreCase.startsWith( "x-amz-" )) | |
| auth.addAmazonHeader( headerName + ":" + headers[i].getValue()); | |
| } | |
| UserInfo info = ServiceProvider.getInstance().getUserInfo(AWSAccessKey); | |
| if (info == null) throw new PermissionDeniedException("Unable to authenticate access key: " + AWSAccessKey); | |
| try { | |
| if (auth.verifySignature( request.getMethod(), info.getSecretKey(), signature )) { | |
| UserContext.current().initContext(AWSAccessKey, info.getSecretKey(), AWSAccessKey, info.getDescription(), request); | |
| return; | |
| } | |
| } catch (SignatureException e) { | |
| throw new PermissionDeniedException(e); | |
| } catch (UnsupportedEncodingException e) { | |
| throw new PermissionDeniedException(e); | |
| } | |
| throw new PermissionDeniedException("Invalid signature"); | |
| } | |
| private ServletAction routeRequest(HttpServletRequest request) | |
| { | |
| // URL routing for S3 REST calls. | |
| String pathInfo = request.getPathInfo(); | |
| String bucketName = null; | |
| String key = null; | |
| String serviceEndpoint = ServiceProvider.getInstance().getServiceEndpoint(); | |
| String host = request.getHeader("Host"); | |
| // Check for unrecognized forms of URI information in request | |
| if ( ( pathInfo == null ) || ( pathInfo.indexOf('/') != 0 ) ) | |
| if ( "POST".equalsIgnoreCase(request.getMethod()) ) | |
| // Case where request is POST operation with no pathinfo | |
| // This is the POST alternative to PUT described at s3.amazonaws.com API doc page 141 | |
| { | |
| return routePlainPostRequest (request); | |
| } | |
| // Irrespective of whether the requester is using subdomain or full host naming of path expressions | |
| // to buckets, wherever the request is made up of a service endpoint followed by a /, in AWS S3 this always | |
| // conveys a ListAllMyBuckets command | |
| if ( (serviceEndpoint.equalsIgnoreCase( host )) && (pathInfo.equalsIgnoreCase("/")) ) { | |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, "/"); | |
| return new S3BucketAction(); // for ListAllMyBuckets | |
| } | |
| // Because there is a leading / at position 0 of pathInfo, now subtract this to process the remainder | |
| pathInfo = pathInfo.substring(1); | |
| if (ServiceProvider.getInstance().getUseSubDomain()) | |
| { | |
| // -> verify the format of the bucket name | |
| int endPos = host.indexOf( ServiceProvider.getInstance().getMasterDomain()); | |
| if ( endPos > 0 ) | |
| { | |
| bucketName = host.substring(0, endPos); | |
| S3Engine.verifyBucketName( bucketName, false ); | |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); | |
| } | |
| else request.setAttribute(S3Constants.BUCKET_ATTR_KEY, ""); | |
| if (pathInfo == null || pathInfo.equalsIgnoreCase("/")) | |
| { | |
| return new S3BucketAction(); | |
| } | |
| else { | |
| String objectKey = pathInfo.substring(1); | |
| request.setAttribute(S3Constants.OBJECT_ATTR_KEY, objectKey); | |
| return new S3ObjectAction(); | |
| } | |
| } | |
| else | |
| { | |
| int endPos = pathInfo.indexOf('/'); // Subsequent / character? | |
| if (endPos < 1) | |
| { | |
| bucketName = pathInfo; | |
| S3Engine.verifyBucketName( bucketName, false ); | |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); | |
| return new S3BucketAction(); | |
| } | |
| else | |
| { | |
| bucketName = pathInfo.substring(0, endPos); | |
| key = pathInfo.substring(endPos + 1); | |
| S3Engine.verifyBucketName( bucketName, false ); | |
| if (!key.isEmpty()) | |
| { | |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); | |
| request.setAttribute(S3Constants.OBJECT_ATTR_KEY, pathInfo.substring(endPos + 1)); | |
| return new S3ObjectAction(); | |
| } | |
| else { | |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); | |
| return new S3BucketAction(); | |
| } | |
| } | |
| } | |
| } | |
| public static void endResponse(HttpServletResponse response, String content) { | |
| try { | |
| byte[] data = content.getBytes(); | |
| response.setContentLength(data.length); | |
| OutputStream os = response.getOutputStream(); | |
| os.write(data); | |
| os.close(); | |
| } catch(Throwable e) { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| } | |
| } | |
| public static void writeResponse(HttpServletResponse response, String content) throws IOException { | |
| byte[] data = content.getBytes(); | |
| OutputStream os = response.getOutputStream(); | |
| os.write(data); | |
| } | |
| public static void writeResponse(HttpServletResponse response, InputStream is) throws IOException { | |
| byte[] data = new byte[4096]; | |
| int length = 0; | |
| while((length = is.read(data)) > 0) { | |
| response.getOutputStream().write(data, 0, length); | |
| } | |
| } | |
| // Route for the case where request is POST operation with no pathinfo | |
| // This is the POST alternative to PUT described at s3.amazonaws.com API doc, Amazon Simple | |
| // Storage Service API Reference API Version 2006-03-01 page 141. | |
| // The purpose of the plain POST operation is to add an object to a specified bucket using HTML forms. | |
| private S3ObjectAction routePlainPostRequest (HttpServletRequest request) | |
| { | |
| // TODO - Remove the unnecessary fields below | |
| // Obtain the mandatory fields from the HTML form or otherwise fail with a logger message | |
| String keyString = request.getParameter("key"); | |
| String metatagString = request.getParameter("x-amz-meta-tag"); | |
| String bucketString = request.getParameter("Bucket"); | |
| String aclString = request.getParameter("acl"); | |
| String fileString = request.getParameter("file"); | |
| String accessKeyString = request.getParameter("AWSAccessKeyId"); | |
| String signatureString = request.getParameter("Signature"); | |
| // Obtain the discretionary fields from the HTML form | |
| String policyKeyString = request.getParameter("Policy"); | |
| String metauuidString = request.getParameter("x-amz-meta-uuid"); | |
| String redirectString = request.getParameter("redirect"); | |
| // if none of the above are null then ... | |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketString); | |
| request.setAttribute(S3Constants.OBJECT_ATTR_KEY, keyString); | |
| request.setAttribute(S3Constants.PLAIN_POST_ACCESS_KEY, accessKeyString); | |
| request.setAttribute(S3Constants.PLAIN_POST_SIGNATURE, signatureString); | |
| // -> authenticated calls | |
| try { | |
| // S3AuthParams params = extractRequestHeaders( request ); | |
| S3AuthParams params = new S3AuthParams(); | |
| HeaderParam headerParam1 = new HeaderParam("accessKey", accessKeyString); | |
| params.addHeader(headerParam1); | |
| HeaderParam headerParam2 = new HeaderParam("secretKey", signatureString); | |
| params.addHeader(headerParam2); | |
| authenticateRequest( request, params ); | |
| } | |
| catch (Exception e) | |
| { logger.warn("Authentication details insufficient"); } | |
| return new S3ObjectAction(); | |
| } | |
| /** | |
| * A DIME request is really a SOAP request that we are dealing with, and so its | |
| * authentication is the SOAP authentication approach. Since Axis2 does not handle | |
| * DIME messages we deal with them here. | |
| * | |
| * @param request | |
| * @param response | |
| */ | |
| private void processDimeRequest(HttpServletRequest request, HttpServletResponse response) { | |
| S3PutObjectRequest putRequest = null; | |
| S3PutObjectResponse putResponse = null; | |
| int bytesRead = 0; | |
| S3Engine engine = new S3Engine(); | |
| try { | |
| logRequest(request); | |
| MultiPartDimeInputStream ds = new MultiPartDimeInputStream( request.getInputStream()); | |
| // -> the first stream MUST be the SOAP party | |
| if (ds.nextInputStream()) | |
| { | |
| //logger.debug( "DIME msg [" + ds.getStreamType() + "," + ds.getStreamTypeFormat() + "," + ds.getStreamId() + "]" ); | |
| byte[] buffer = new byte[8192]; | |
| bytesRead = ds.read( buffer, 0, 8192 ); | |
| //logger.debug( "DIME SOAP Bytes read: " + bytesRead ); | |
| ByteArrayInputStream bis = new ByteArrayInputStream( buffer, 0, bytesRead ); | |
| putRequest = toEnginePutObjectRequest( bis ); | |
| } | |
| // -> we only need to support a DIME message with two bodyparts | |
| if (null != putRequest && ds.nextInputStream()) | |
| { | |
| InputStream is = ds.getInputStream(); | |
| putRequest.setData( is ); | |
| } | |
| // -> need to do SOAP level auth here, on failure return the SOAP fault | |
| StringBuffer xml = new StringBuffer(); | |
| String AWSAccessKey = putRequest.getAccessKey(); | |
| UserInfo info = ServiceProvider.getInstance().getUserInfo(AWSAccessKey); | |
| try | |
| { S3SoapAuth.verifySignature( putRequest.getSignature(), "PutObject", putRequest.getRawTimestamp(), AWSAccessKey, info.getSecretKey()); | |
| } catch( AxisFault e ) { | |
| String reason = e.toString(); | |
| int start = reason.indexOf( ".AxisFault:" ); | |
| if (-1 != start) reason = reason.substring( start+11 ); | |
| xml.append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" ); | |
| xml.append( "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" >\n" ); | |
| xml.append( "<soap:Body>\n" ); | |
| xml.append( "<soap:Fault>\n" ); | |
| xml.append( "<faultcode>" ).append( e.getFaultCode().toString()).append( "</faultcode>\n" ); | |
| xml.append( "<faultstring>" ).append( reason ).append( "</faultstring>\n" ); | |
| xml.append( "</soap:Fault>\n" ); | |
| xml.append( "</soap:Body></soap:Envelope>" ); | |
| endResponse(response, xml.toString()); | |
| return; | |
| } | |
| // -> PutObject S3 Bucket Policy would be done in the engine.handleRequest() call | |
| UserContext.current().initContext( AWSAccessKey, info.getSecretKey(), AWSAccessKey, "S3 DIME request", request ); | |
| putResponse = engine.handleRequest( putRequest ); | |
| xml.append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" ); | |
| xml.append( "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:tns=\"http://s3.amazonaws.com/doc/2006-03-01/\">" ); | |
| xml.append( "<soap:Body>" ); | |
| xml.append( "<tns:PutObjectResponse>" ); | |
| xml.append( "<tns:PutObjectResponse>" ); | |
| xml.append( "<tns:ETag>\"").append( putResponse.getETag()).append( "\"</tns:ETag>" ); | |
| xml.append( "<tns:LastModified>").append( DatatypeConverter.printDateTime(putResponse.getLastModified())).append( "</tns:LastModified>" ); | |
| xml.append( "</tns:PutObjectResponse></tns:PutObjectResponse>" ); | |
| xml.append( "</soap:Body></soap:Envelope>" ); | |
| endResponse(response, xml.toString()); | |
| } | |
| catch(PermissionDeniedException e) { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| response.setStatus(403); | |
| endResponse(response, "Access denied"); | |
| } | |
| catch(Throwable e) | |
| { | |
| logger.error("Unexpected exception " + e.getMessage(), e); | |
| } | |
| finally | |
| { | |
| } | |
| } | |
| /** | |
| * Convert the SOAP XML we extract from the DIME message into our local object. | |
| * Here Axis2 is not parsing the SOAP for us. I tried to use the Amazon PutObject | |
| * parser but it keep throwing exceptions. | |
| * | |
| * @param putObjectInline | |
| * @return | |
| * @throws Exception | |
| */ | |
| public static S3PutObjectRequest toEnginePutObjectRequest( InputStream is ) throws Exception | |
| { | |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | |
| dbf.setNamespaceAware( true ); | |
| DocumentBuilder db = dbf.newDocumentBuilder(); | |
| Document doc = db.parse( is ); | |
| Node parent = null; | |
| Node contents = null; | |
| NodeList children = null; | |
| String temp = null; | |
| String element = null; | |
| int count = 0; | |
| S3PutObjectRequest request = new S3PutObjectRequest(); | |
| // [A] Pull out the simple nodes first | |
| NodeList part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Bucket" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setBucketName( contents.getFirstChild().getNodeValue()); | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Key" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setKey( contents.getFirstChild().getNodeValue()); | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "ContentLength" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| { | |
| String length = contents.getFirstChild().getNodeValue(); | |
| if (null != length) request.setContentLength( Long.decode( length )); | |
| } | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "AWSAccessKeyId" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setAccessKey( contents.getFirstChild().getNodeValue()); | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Signature" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setSignature( contents.getFirstChild().getNodeValue()); | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Timestamp" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setRawTimestamp( contents.getFirstChild().getNodeValue()); | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "StorageClass" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setStorageClass( contents.getFirstChild().getNodeValue()); | |
| } | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Credential" ); | |
| if (null != part) | |
| { | |
| if (null != (contents = part.item( 0 ))) | |
| request.setCredential( contents.getFirstChild().getNodeValue()); | |
| } | |
| // [B] Get a list of all 'Metadata' elements | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Metadata" ); | |
| if (null != part) | |
| { | |
| count = part.getLength(); | |
| S3MetaDataEntry[] metaEntry = new S3MetaDataEntry[ count ]; | |
| for( int i=0; i < count; i++ ) | |
| { | |
| parent = part.item(i); | |
| metaEntry[i] = new S3MetaDataEntry(); | |
| // -> get a list of all the children elements of the 'Metadata' parent element | |
| if (null != (children = parent.getChildNodes())) | |
| { | |
| int numChildren = children.getLength(); | |
| for( int j=0; j < numChildren; j++ ) | |
| { | |
| contents = children.item( j ); | |
| element = contents.getNodeName().trim(); | |
| if ( element.endsWith( "Name" )) | |
| { | |
| temp = contents.getFirstChild().getNodeValue(); | |
| if (null != temp) metaEntry[i].setName( temp ); | |
| } | |
| else if (element.endsWith( "Value" )) | |
| { | |
| temp = contents.getFirstChild().getNodeValue(); | |
| if (null != temp) metaEntry[i].setValue( temp ); | |
| } | |
| } | |
| } | |
| } | |
| request.setMetaEntries( metaEntry ); | |
| } | |
| // [C] Get a list of all Grant elements in an AccessControlList | |
| part = getElement( doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Grant" ); | |
| if (null != part) | |
| { | |
| S3AccessControlList engineAcl = new S3AccessControlList(); | |
| count = part.getLength(); | |
| for( int i=0; i < count; i++ ) | |
| { | |
| parent = part.item(i); | |
| S3Grant engineGrant = new S3Grant(); | |
| // -> get a list of all the children elements of the 'Grant' parent element | |
| if (null != (children = parent.getChildNodes())) | |
| { | |
| int numChildren = children.getLength(); | |
| for( int j=0; j < numChildren; j++ ) | |
| { | |
| contents = children.item( j ); | |
| element = contents.getNodeName().trim(); | |
| if ( element.endsWith( "Grantee" )) | |
| { | |
| NamedNodeMap attbs = contents.getAttributes(); | |
| if (null != attbs) | |
| { | |
| Node type = attbs.getNamedItemNS( "http://www.w3.org/2001/XMLSchema-instance", "type" ); | |
| if ( null != type ) | |
| temp = type.getFirstChild().getNodeValue().trim(); | |
| else temp = null; | |
| if ( null != temp && temp.equalsIgnoreCase( "CanonicalUser" )) | |
| { | |
| engineGrant.setGrantee(SAcl.GRANTEE_USER); | |
| engineGrant.setCanonicalUserID( getChildNodeValue( contents, "ID" )); | |
| } | |
| else throw new UnsupportedOperationException( "Missing http://www.w3.org/2001/XMLSchema-instance:type value" ); | |
| } | |
| } | |
| else if (element.endsWith( "Permission" )) | |
| { | |
| temp = contents.getFirstChild().getNodeValue().trim(); | |
| if (temp.equalsIgnoreCase("READ" )) engineGrant.setPermission(SAcl.PERMISSION_READ); | |
| else if (temp.equalsIgnoreCase("WRITE" )) engineGrant.setPermission(SAcl.PERMISSION_WRITE); | |
| else if (temp.equalsIgnoreCase("READ_ACP" )) engineGrant.setPermission(SAcl.PERMISSION_READ_ACL); | |
| else if (temp.equalsIgnoreCase("WRITE_ACP" )) engineGrant.setPermission(SAcl.PERMISSION_WRITE_ACL); | |
| else if (temp.equalsIgnoreCase("FULL_CONTROL")) engineGrant.setPermission(SAcl.PERMISSION_FULL); | |
| else throw new UnsupportedOperationException( "Unsupported permission: " + temp ); | |
| } | |
| } | |
| engineAcl.addGrant( engineGrant ); | |
| } | |
| } | |
| request.setAcl( engineAcl ); | |
| } | |
| return request; | |
| } | |
| /** | |
| * Have to deal with XML with and without namespaces. | |
| */ | |
| public static NodeList getElement( Document doc, String namespace, String tagName ) | |
| { | |
| NodeList part = doc.getElementsByTagNameNS( namespace, tagName ); | |
| if (null == part || 0 == part.getLength()) part = doc.getElementsByTagName( tagName ); | |
| return part; | |
| } | |
| /** | |
| * Looking for the value of a specific child of the given parent node. | |
| * | |
| * @param parent | |
| * @param childName | |
| * @return | |
| */ | |
| private static String getChildNodeValue( Node parent, String childName ) | |
| { | |
| NodeList children = null; | |
| Node element = null; | |
| if (null != (children = parent.getChildNodes())) | |
| { | |
| int numChildren = children.getLength(); | |
| for( int i=0; i < numChildren; i++ ) | |
| { | |
| if (null != (element = children.item( i ))) | |
| { | |
| // -> name may have a namespace on it | |
| String name = element.getNodeName().trim(); | |
| if ( name.endsWith( childName )) | |
| { | |
| String value = element.getFirstChild().getNodeValue(); | |
| if (null != value) value = value.trim(); | |
| return value; | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| private void logRequest(HttpServletRequest request) { | |
| if(logger.isInfoEnabled()) { | |
| logger.info("Request method: " + request.getMethod()); | |
| logger.info("Request contextPath: " + request.getContextPath()); | |
| logger.info("Request pathInfo: " + request.getPathInfo()); | |
| logger.info("Request pathTranslated: " + request.getPathTranslated()); | |
| logger.info("Request queryString: " + request.getQueryString()); | |
| logger.info("Request requestURI: " + request.getRequestURI()); | |
| logger.info("Request requestURL: " + request.getRequestURL()); | |
| logger.info("Request servletPath: " + request.getServletPath()); | |
| Enumeration headers = request.getHeaderNames(); | |
| if(headers != null) { | |
| while(headers.hasMoreElements()) { | |
| Object headerName = headers.nextElement(); | |
| logger.info("Request header " + headerName + ":" + request.getHeader((String)headerName)); | |
| } | |
| } | |
| Enumeration params = request.getParameterNames(); | |
| if(params != null) { | |
| while(params.hasMoreElements()) { | |
| Object paramName = params.nextElement(); | |
| logger.info("Request parameter " + paramName + ":" + | |
| request.getParameter((String)paramName)); | |
| } | |
| } | |
| logger.info( "- End of request -" ); | |
| } | |
| } | |
| } |