Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: Serhan AKIN $'
7
 *     '$Date: 2009-06-13 15:28:13 +0300  $'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23
package edu.ucsb.nceas.metacat.restservice;
24

    
25
import java.io.File;
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.io.OutputStream;
29
import java.io.PrintWriter;
30
import java.security.PrivateKey;
31
import java.security.cert.X509Certificate;
32
import java.util.Enumeration;
33
import java.util.Hashtable;
34
import java.util.Iterator;
35
import java.util.List;
36
import java.util.Map;
37
import java.util.Timer;
38

    
39
import javax.servlet.ServletContext;
40
import javax.servlet.http.HttpServletRequest;
41
import javax.servlet.http.HttpServletResponse;
42

    
43
import org.apache.commons.fileupload.FileUploadException;
44
import org.apache.commons.io.IOUtils;
45
import org.apache.log4j.Logger;
46
import org.dataone.client.auth.CertificateManager;
47
import org.dataone.mimemultipart.MultipartRequest;
48
import org.dataone.mimemultipart.MultipartRequestResolver;
49
import org.dataone.portal.PortalCertificateManager;
50
import org.dataone.service.exceptions.BaseException;
51
import org.dataone.service.exceptions.InvalidRequest;
52
import org.dataone.service.exceptions.ServiceFailure;
53
import org.dataone.service.types.v1.Group;
54
import org.dataone.service.types.v1.Person;
55
import org.dataone.service.types.v1.Session;
56
import org.dataone.service.types.v1.Subject;
57
import org.dataone.service.types.v1.SubjectInfo;
58

    
59
import edu.ucsb.nceas.metacat.MetacatHandler;
60
import edu.ucsb.nceas.metacat.properties.PropertyService;
61
import edu.ucsb.nceas.metacat.service.SessionService;
62
import edu.ucsb.nceas.metacat.util.RequestUtil;
63
import edu.ucsb.nceas.metacat.util.SessionData;
64
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
65
/**
66
 * 
67
 * Base class for handling D1 REST calls in Metacat
68
 * 
69
 * @author leinfelder
70
 */
71
public class D1ResourceHandler {
72

    
73
    /**HTTP Verb GET*/
74
    public static final byte GET = 1;
75
    /**HTTP Verb POST*/
76
    public static final byte POST = 2;
77
    /**HTTP Verb PUT*/
78
    public static final byte PUT = 3;
79
    /**HTTP Verb DELETE*/
80
    public static final byte DELETE = 4;
81
    /**HTTP Verb HEAD*/
82
    public static final byte HEAD = 5;
83

    
84
    /*
85
     * API Resources
86
     */
87
    protected static final String RESOURCE_BASE_URL = "d1";
88

    
89
    protected static final String RESOURCE_OBJECTS = "object";
90
    protected static final String RESOURCE_META = "meta";
91
    protected static final String RESOURCE_LOG = "log";
92
    
93
    protected static final String RESOURCE_QUERY = "query";
94
    
95
    protected static final String RESOURCE_IS_AUTHORIZED = "isAuthorized";
96
    protected static final String RESOURCE_ACCESS_RULES = "accessRules";
97

    
98
    
99
    /*
100
     * API Functions used as URL parameters
101
     */
102
    protected static final String FUNCTION_NAME_INSERT = "insert";
103
    protected static final String FUNCTION_NAME_UPDATE = "update";
104
    
105
    protected ServletContext servletContext;
106
    protected Logger logMetacat;
107
    protected MetacatHandler handler;
108
    protected HttpServletRequest request;
109
    protected HttpServletResponse response;
110

    
111
    protected Hashtable<String, String[]> params;
112
    protected Map<String, List<String>> multipartparams;
113
    
114
    // D1 certificate-based authentication
115
    protected Session session;
116

    
117
    /**Initializes new instance by setting servlet context,request and response*/
118
    public D1ResourceHandler(ServletContext servletContext,
119
            HttpServletRequest request, HttpServletResponse response) {
120
        this.servletContext = servletContext;
121
        this.request = request;
122
        this.response = response;
123
    }
124

    
125
    /**
126
     * This function is called from REST API servlet and handles each request 
127
     * 
128
     * @param httpVerb (GET, POST, PUT or DELETE)
129
     */
130
    public void handle(byte httpVerb) {
131
        logMetacat = Logger.getLogger(D1ResourceHandler.class);
132
        try {
133
  
134
        	// initialize the session - three options
135
        	// #1
136
        	// load session from certificate in request
137
            session = CertificateManager.getInstance().getSession(request);
138
            
139
            // #2
140
            if (session == null) {
141
	        	// check for session-based certificate from the portal
142
            	try {
143
		        	String configurationFileName = servletContext.getInitParameter("oa4mp:client.config.file");
144
		        	String configurationFilePath = servletContext.getRealPath(configurationFileName);
145
		        	PortalCertificateManager portalManager = new PortalCertificateManager(configurationFilePath);
146
		        	logMetacat.debug("Initialized the PortalCertificateManager using config file: " + configurationFilePath);
147
		        	X509Certificate certificate = portalManager.getCertificate(request);
148
		        	logMetacat.debug("Retrieved certificate: " + certificate);
149
			    	PrivateKey key = portalManager.getPrivateKey(request);
150
			    	logMetacat.debug("Retrieved key: " + key);
151
			    	if (certificate != null && key != null) {
152
			        	request.setAttribute("javax.servlet.request.X509Certificate", certificate);
153
			        	logMetacat.debug("Added certificate to the request: " + certificate.toString());
154
			    	}
155
			    	
156
		            // reload session from certificate that we jsut set in request
157
		            session = CertificateManager.getInstance().getSession(request);
158
            	} catch (Throwable t) {
159
            		// don't require configured OAuth4MyProxy
160
            		logMetacat.error(t.getMessage(), t);
161
            	}
162
            }
163
            
164
            // #3
165
            // last resort, check for Metacat sessionid
166
            if (session == null) {
167
	            SessionData sessionData = RequestUtil.getSessionData(request);
168
				if (sessionData != null) {
169
					// is it not the public session?
170
					if (!SessionService.getInstance().getPublicSession().getUserName().equals(sessionData.getUserName())) {
171
						session = new Session();
172
						String userName = sessionData.getUserName();
173
						String[] groupNames = sessionData.getGroupNames();
174
						Subject userSubject = new Subject();
175
						userSubject.setValue(userName);
176
						session.setSubject(userSubject);
177
						SubjectInfo subjectInfo = new SubjectInfo();
178
						Person person = new Person();
179
						person.setSubject(userSubject);
180
						if (groupNames != null && groupNames.length > 0) {
181
							for (String groupName: groupNames) {
182
								Group group = new Group();
183
								group.setGroupName(groupName);
184
								Subject groupSubject = new Subject();
185
								groupSubject.setValue(groupName);
186
								group.setSubject(groupSubject);
187
								subjectInfo.addGroup(group);
188
								person.addIsMemberOf(groupSubject);
189
							}
190
						}
191
						subjectInfo.addPerson(person);
192
						session.setSubjectInfo(subjectInfo);
193
					}
194
				}
195
            }
196
			
197
            // initialize the parameters
198
            params = new Hashtable<String, String[]>();
199
            initParams();
200

    
201
            // create the handler for interacting with Metacat
202
            Timer timer = new Timer();
203
            handler = new MetacatHandler(timer);
204

    
205
        } catch (Exception e) {
206
        	// TODO: more D1 structure when failing here
207
        	response.setStatus(400);
208
            printError("Incorrect resource!", response);
209
            logMetacat.error(e.getClass() + ": " + e.getMessage(), e);
210
        }
211
    }
212

    
213
    /**
214
     * subclasses should provide a more useful implementation
215
     * @return
216
     */
217
    protected boolean isD1Enabled() {
218
    	
219
    	return true;	
220
    }
221
    
222
    protected String parseTrailing(String resource, String token) {
223
    	// get the rest
224
        String extra = null;
225
        if (resource.indexOf(token) != -1) {
226
        	// what comes after the token?
227
            extra = resource.substring(resource.indexOf(token) + token.length());
228
            // remove the slash
229
            if (extra.startsWith("/")) {
230
            	extra = extra.substring(1);
231
            }
232
            // is there anything left?
233
            if (extra.length() == 0) {
234
            	extra = null;
235
            }
236
        }
237
        return extra;
238
    }
239

    
240
    /**
241
     * Parse string parameters from the mime multipart entity of the request.
242
     * Populates the multipartparams map
243
     * 
244
     * @throws IOException
245
     * @throws FileUploadException
246
     * @throws Exception
247
     */
248
    protected void collectMultipartParams() 
249
        throws IOException, FileUploadException, Exception {
250
        
251
        File tmpDir = getTempDirectory();
252
        MultipartRequest mr = null;
253

    
254
        // Read the incoming data from its Mime Multipart encoding
255
        logMetacat.debug("Parsing rights holder info from the mime multipart entity");
256

    
257
        // handle MMP inputs
258
        MultipartRequestResolver mrr = 
259
            new MultipartRequestResolver(tmpDir.getAbsolutePath(),1000000000, 0);
260

    
261
        mr = mrr.resolveMultipart(request);
262
        logMetacat.debug("Resolved the rights holder info from the mime multipart entity.");
263
                    
264
        // we only have params in this MMP entity
265
        multipartparams = mr.getMultipartParameters();
266
                
267
    }
268
    
269
    /**
270
     * Process the MMP request that includes files for each param
271
     * @return map of param key and the temp file that contains the encoded information
272
     * @throws ServiceFailure
273
     * @throws InvalidRequest
274
     */
275
    protected Map<String, File> collectMultipartFiles() 
276
        throws ServiceFailure, InvalidRequest {
277
    	
278
        // Read the incoming data from its Mime Multipart encoding
279
        logMetacat.debug("Disassembling MIME multipart form");
280
        
281
        // handle MMP inputs
282
        File tmpDir = getTempDirectory();
283
        logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
284
        MultipartRequestResolver mrr = 
285
        	new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
286
        MultipartRequest mr = null;
287
		    try {
288
		    	  mr = mrr.resolveMultipart(request);
289
            
290
		    } catch (Exception e) {
291
                throw new ServiceFailure("1202", 
292
                		"Could not resolve multipart files: " + e.getMessage());
293
		    }
294
        logMetacat.debug("resolved multipart request");
295
        Map<String, File> files = mr.getMultipartFiles();
296
        if (files == null) {
297
            throw new ServiceFailure("1202", "no multipart files found");
298
        }
299
        logMetacat.debug("got multipart files");
300
        
301
        if (files.keySet() == null) {
302
            logMetacat.error("No file keys in MMP request.");
303
            throw new ServiceFailure("1202", "No file keys found in MMP.");
304
        }
305
        
306
        multipartparams = mr.getMultipartParameters();
307

    
308
		    // for logging purposes, dump out the key-value pairs that constitute the request
309
		    // 3 types exist: request params, multipart params, and multipart files
310
        if (logMetacat.isDebugEnabled()) {
311
	        Iterator<String> it = files.keySet().iterator();
312
	        logMetacat.debug("iterating through files");
313
	        while (it.hasNext()) {
314
	            String key = it.next();
315
	            logMetacat.debug("files key: " + key);
316
	            logMetacat.debug("files value: " + files.get(key));
317
	        }
318
	        
319
	        it = multipartparams.keySet().iterator();
320
	        logMetacat.debug("iterating through multipartparams");
321
	        while (it.hasNext()) {
322
	            String key = (String)it.next();
323
	            logMetacat.debug("multipartparams key: " + key);
324
	            logMetacat.debug("multipartparams value: " + multipartparams.get(key));
325
	        }
326
	        
327
	        it = params.keySet().iterator();
328
	        logMetacat.debug("iterating through params");
329
	        while (it.hasNext()) {
330
	            String key = (String)it.next();
331
	            logMetacat.debug("param key: " + key);
332
	            logMetacat.debug("param value: " + params.get(key));
333
	        }
334
	        logMetacat.debug("done iterating the request...");
335
        }
336
        
337
        return files;
338
    }
339
    
340
		/**
341
     *  copies request parameters to a hashtable which is given as argument to 
342
     *  native metacathandler functions  
343
     */
344
    protected void initParams() {
345

    
346
        String name = null;
347
        String[] value = null;
348
        Enumeration paramlist = request.getParameterNames();
349
        while (paramlist.hasMoreElements()) {
350
            name = (String) paramlist.nextElement();
351
            value = request.getParameterValues(name);
352
            params.put(name, value);
353
        }
354
    }
355
    
356
    /**
357
     * Collect the multipart params from the request
358
     * @throws Exception 
359
     */
360
	protected void initMultipartParams() throws Exception {
361
		
362
		// Read the incoming data from its Mime Multipart encoding
363
		logMetacat.debug("Disassembling MIME multipart form");
364
	
365
		// handle MMP inputs
366
		File tmpDir = getTempDirectory();
367
		logMetacat.debug("temp dir: " + tmpDir.getAbsolutePath());
368
		MultipartRequestResolver mrr = 
369
			new MultipartRequestResolver(tmpDir.getAbsolutePath(), 1000000000, 0);
370
		MultipartRequest mr = mrr.resolveMultipart(request);
371
		
372
		multipartparams = mr.getMultipartParameters();
373
	}
374
   
375
    /**
376
     * locate the boundary marker for an MMP
377
     * @param is
378
     * @return
379
     * @throws IOException
380
     */
381
    protected static String[] findBoundaryString(InputStream is)
382
        throws IOException {
383
        String[] endResult = new String[2];
384
        String boundary = "";
385
        String searchString = "boundary=";
386
        byte[] b = new byte[1024];
387
        int numbytes = is.read(b, 0, 1024);
388

    
389
        while(numbytes != -1)
390
        {
391
            String s = new String(b, 0, numbytes);
392
            int searchStringIndex = s.indexOf(searchString);
393
            
394
            if(s.indexOf("\"", searchStringIndex + searchString.length() + 1) == -1)
395
            { //the end of the boundary is in the next byte array
396
                boundary = s.substring(searchStringIndex + searchString.length() + 1, s.length());
397
            }
398
            else if(!boundary.startsWith("--"))
399
            { //we can read the whole boundary from this byte array
400
                boundary = s.substring(searchStringIndex + searchString.length() + 1, 
401
                    s.indexOf("\"", searchStringIndex + searchString.length() + 1));
402
                boundary = "--" + boundary;
403
                endResult[0] = boundary;
404
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
405
                        s.length());
406
                break;
407
            }
408
            else
409
            { //we're now reading the 2nd byte array to get the rest of the boundary
410
                searchString = "\"";
411
                searchStringIndex = s.indexOf(searchString);
412
                boundary += s.substring(0, searchStringIndex);
413
                boundary = "--" + boundary;
414
                endResult[0] = boundary;
415
                endResult[1] = s.substring(s.indexOf("\"", searchStringIndex + searchString.length() + 1) + 1,
416
                        s.length());
417
                break;
418
            }
419
        }
420
        return endResult;
421
    }
422
    
423
    /**
424
     * return the directory where temp files are stored
425
     * @return
426
     */
427
    protected static File getTempDirectory()
428
    {
429
        File tmpDir = null;
430
        Logger logMetacat = Logger.getLogger(D1ResourceHandler.class);
431
        try {
432
            tmpDir = new File(PropertyService.getProperty("application.tempDir"));
433
        }
434
        catch(PropertyNotFoundException pnfe) {
435
            logMetacat.error("D1ResourceHandler.writeMMPPartstoFiles: " +
436
                    "application.tmpDir not found.  Using /tmp instead.");
437
            tmpDir = new File("/tmp");
438
        }
439
        return tmpDir;
440
    }
441
    
442
    /**
443
     * Prints xml response
444
     * @param message Message to be displayed
445
     * @param response Servlet response that xml message will be printed 
446
     * */
447
    protected void printError(String message, HttpServletResponse response) {
448
        try {
449
            logMetacat.error("D1ResourceHandler: Printing error to servlet response: " + message);
450
            PrintWriter out = response.getWriter();
451
            response.setContentType("text/xml");
452
            out.println("<?xml version=\"1.0\"?>");
453
            out.println("<error>");
454
            out.println(message);
455
            out.println("</error>");
456
            out.close();
457
        } catch (IOException e) {
458
            e.printStackTrace();
459
        }
460
    }
461
    
462
    /**
463
     * serialize a D1 exception using jibx
464
     * @param e
465
     * @param out
466
     */
467
    protected void serializeException(BaseException e, OutputStream out) {
468
        // TODO: Use content negotiation to determine which return format to use
469
        response.setContentType("text/xml");
470
        response.setStatus(e.getCode());
471
        
472
        logMetacat.error("D1ResourceHandler: Serializing exception with code " + e.getCode() + ": " + e.getMessage());
473
        e.printStackTrace();
474
        
475
        try {
476
            IOUtils.write(e.serialize(BaseException.FMT_XML), out);
477
        } catch (IOException e1) {
478
            logMetacat.error("Error writing exception to stream. " 
479
                    + e1.getMessage());
480
        }
481
    }
482

    
483
}
(2-2/5)