Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000-2011 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author: cjones $'
7
 *     '$Date: 2011-06-28 08:04:34 -0700 (Tue, 28 Jun 2011) $'
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

    
24
package edu.ucsb.nceas.metacat.dataone;
25

    
26
import java.io.File;
27
import java.io.FileInputStream;
28
import java.io.FileNotFoundException;
29
import java.io.FileOutputStream;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.sql.SQLException;
33
import java.util.ArrayList;
34
import java.util.Calendar;
35
import java.util.Date;
36
import java.util.Enumeration;
37
import java.util.Hashtable;
38
import java.util.List;
39
import java.util.Timer;
40
import java.util.TimerTask;
41
import java.util.Vector;
42

    
43
import javax.servlet.http.HttpServletRequest;
44

    
45
import org.apache.log4j.Logger;
46
import org.dataone.service.exceptions.InvalidRequest;
47
import org.dataone.service.exceptions.InvalidToken;
48
import org.dataone.service.exceptions.NotAuthorized;
49
import org.dataone.service.exceptions.NotFound;
50
import org.dataone.service.exceptions.NotImplemented;
51
import org.dataone.service.exceptions.ServiceFailure;
52
import org.dataone.service.types.AccessPolicy;
53
import org.dataone.service.types.AccessRule;
54
import org.dataone.service.types.Event;
55
import org.dataone.service.types.Identifier;
56
import org.dataone.service.types.Group;
57
import org.dataone.service.types.Log;
58
import org.dataone.service.types.LogEntry;
59
import org.dataone.service.types.NodeReference;
60
import org.dataone.service.types.Permission;
61
import org.dataone.service.types.Person;
62
import org.dataone.service.types.Session;
63
import org.dataone.service.types.Subject;
64
import org.dataone.service.types.SystemMetadata;
65

    
66
import edu.ucsb.nceas.metacat.EventLog;
67
import edu.ucsb.nceas.metacat.IdentifierManager;
68
import edu.ucsb.nceas.metacat.McdbDocNotFoundException;
69
import edu.ucsb.nceas.metacat.McdbException;
70
import edu.ucsb.nceas.metacat.MetacatHandler;
71
import edu.ucsb.nceas.metacat.client.InsufficientKarmaException;
72
import edu.ucsb.nceas.metacat.properties.PropertyService;
73
import edu.ucsb.nceas.utilities.ParseLSIDException;
74
import edu.ucsb.nceas.utilities.PropertyNotFoundException;
75

    
76
public abstract class D1NodeService {
77
	
78
  private static Logger logMetacat = Logger.getLogger(D1NodeService.class);
79

    
80
  /* reference to the metacat handler */
81
  private MetacatHandler handler;
82
  
83
  /* parameters set in the incoming request */
84
  private Hashtable<String, String[]> params;
85

    
86
	/* Methods common to CNCore and MNCore APIs */
87

    
88
	/**
89
	 * Return the log records associated with a given event between the start and 
90
	 * end dates listed given a particular Subject listed in the Session
91
	 * 
92
	 * @param session - the Session object containing the credentials for the Subject
93
	 * @param fromDate - the start date of the desired log records
94
	 * @param toDate - the end date of the desired log records
95
	 * @param event - restrict log records of a specific event type
96
	 * @param start - zero based offset from the first record in the 
97
	 *                set of matching log records. Used to assist with 
98
	 *                paging the response.
99
	 * @param count - maximum number of log records to return in the response. 
100
	 *                Used to assist with paging the response.
101
	 * 
102
	 * @return the desired log records
103
	 * 
104
	 * @throws InvalidToken
105
	 * @throws ServiceFailure
106
	 * @throws NotAuthorized
107
	 * @throws InvalidRequest
108
	 * @throws NotImplemented
109
	 */
110
	public Log getLogRecords(Session session, Date fromDate, Date toDate, 
111
      Event event, Integer start, Integer count) throws InvalidToken, ServiceFailure,
112
	    NotAuthorized, InvalidRequest, NotImplemented {
113

    
114

    
115
		Log log = new Log();
116
		Vector<LogEntry> logs = new Vector<LogEntry>();
117
		IdentifierManager im = IdentifierManager.getInstance();
118
		EventLog el = EventLog.getInstance();
119
		if (fromDate == null) {
120
			logMetacat.debug("setting fromdate from null");
121
			fromDate = new Date(1);
122
		}
123
		if (toDate == null) {
124
			logMetacat.debug("setting todate from null");
125
			toDate = new Date();
126
		}
127

    
128
		logMetacat.debug("fromDate: " + fromDate);
129
		logMetacat.debug("toDate: " + toDate);
130

    
131
		String report = el.getReport(null, null, null, null,
132
				new java.sql.Timestamp(fromDate.getTime()),
133
				new java.sql.Timestamp(toDate.getTime()), false);
134

    
135
		logMetacat.debug("report: " + report);
136

    
137
		String logEntry = "<logEntry>";
138
		String endLogEntry = "</logEntry>";
139
		int startIndex = 0;
140
		int foundIndex = report.indexOf(logEntry, startIndex);
141
		while (foundIndex != -1) {
142
			// parse out each entry
143
			int endEntryIndex = report.indexOf(endLogEntry, foundIndex);
144
			String entry = report.substring(foundIndex, endEntryIndex);
145
			logMetacat.debug("entry: " + entry);
146
			startIndex = endEntryIndex + endLogEntry.length();
147
			foundIndex = report.indexOf(logEntry, startIndex);
148

    
149
			String entryId = getLogEntryField("entryid", entry);
150
			String ipAddress = getLogEntryField("ipAddress", entry);
151
			String principal = getLogEntryField("principal", entry);
152
			String docid = getLogEntryField("docid", entry);
153
			String eventS = getLogEntryField("event", entry);
154
			String dateLogged = getLogEntryField("dateLogged", entry);
155

    
156
			LogEntry le = new LogEntry();
157

    
158
			Event e = Event.convert(eventS);
159
			if (e == null) { // skip any events that are not Dataone Crud events
160
				continue;
161
			}
162
			le.setEvent(e);
163
			Identifier entryid = new Identifier();
164
			entryid.setValue(entryId);
165
			le.setEntryId(entryid);
166
			Identifier identifier = new Identifier();
167
			try {
168
				logMetacat.debug("converting docid '" + docid + "' to a guid.");
169
				if (docid == null || docid.trim().equals("") || docid.trim().equals("null")) {
170
					continue;
171
				}
172
				docid = docid.substring(0, docid.lastIndexOf("."));
173
				identifier.setValue(im.getGUID(docid, im.getLatestRevForLocalId(docid)));
174
			} catch (Exception ex) { 
175
				// try to get the guid, if that doesn't
176
				// work, just use the local id
177
				// throw new ServiceFailure("1030",
178
				// "Error getting guid for localId " +
179
				// docid + ": " + ex.getMessage());\
180

    
181
				// skip it if the guid can't be found
182
				continue;
183
			}
184

    
185
			le.setIdentifier(identifier);
186
			le.setIpAddress(ipAddress);
187
			Calendar c = Calendar.getInstance();
188
			String year = dateLogged.substring(0, 4);
189
			String month = dateLogged.substring(5, 7);
190
			String date = dateLogged.substring(8, 10);
191
			logMetacat.debug("year: " + year + " month: " + month + " day: " + date);
192
			c.set(new Integer(year).intValue(), new Integer(month).intValue(),
193
					new Integer(date).intValue());
194
			Date logDate = c.getTime();
195
			le.setDateLogged(logDate);
196
			NodeReference memberNode = new NodeReference();
197
			memberNode.setValue(ipAddress);
198
			le.setMemberNode(memberNode);
199
			Subject princ = new Subject();
200
			princ.setValue(principal);
201
			le.setSubject(princ);
202
			le.setUserAgent("metacat/RESTService");
203

    
204
			if (event == null) {
205
				logs.add(le);
206
			}
207

    
208
			if (event != null
209
					&& e.toString().toLowerCase().trim().equals(
210
							event.toString().toLowerCase().trim())) {
211
				logs.add(le);
212
			}
213
		}
214

    
215
		log.setLogEntryList(logs);
216
		logMetacat.info("getLogRecords");
217
		return log;
218
	}
219
	
220
	/* End methods common to CNCore and MNCore APIs */
221

    
222
	/* Methods common to CNRead and MNRead APIs */
223
	
224
	/**
225
	 * Return the object identified by the given object identifier
226
	 * 
227
	 * @param session - the Session object containing the credentials for the Subject
228
	 * @param pid - the object identifier for the given object
229
	 * 
230
	 * TODO: The D1 Authorization API doesn't provide information on which 
231
	 * authentication system the Subject belongs to, and so it's not possible to
232
	 * discern which Person or Group is a valid KNB LDAP DN.  Fix this.
233
	 * 
234
	 * @return inputStream - the input stream of the given object
235
	 * 
236
	 * @throws InvalidToken
237
	 * @throws ServiceFailure
238
	 * @throws NotAuthorized
239
	 * @throws InvalidRequest
240
	 * @throws NotImplemented
241
	 */
242
	public InputStream get(Session session, Identifier pid) 
243
	  throws InvalidToken, ServiceFailure, NotAuthorized, NotFound, 
244
	  NotImplemented, InvalidRequest {
245
    
246
		InputStream inputStream = null; // bytes to be returned
247
    handler = new MetacatHandler(new Timer());
248
		boolean allowed = false;
249
		String localId; // the metacat docid for the pid
250
		Subject subject = session.getSubject(); // the requesting user
251
		List<Group> groupList; // the user's groups
252
		
253
		// set the public user as a fallback
254
		if ( subject == null ) {
255
			subject.setValue("Public");
256
			session.setSubject(subject);
257
			
258
		}
259
		
260
		// convert groups to a string array (for handler)
261
		groupList = session.getSubjectList().getGroupList();
262
		String[] groups = new String[groupList.size()];     
263
		
264
		// get the local docid from Metacat
265
		try {
266
	    localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
267
    
268
    } catch (McdbDocNotFoundException e) {
269
      throw new NotFound("1020", "The object specified by " + 
270
      		               pid.getValue() +
271
      		               "does not exist at this node.");
272
    }
273
    
274
    // check for authorization
275
		allowed = isAuthorized(session, pid, Permission.READ);
276
		
277
		// if the person is authorized, perform the read
278
		if ( allowed ) {
279
			
280
			for ( int i = 0; i > groupList.size(); i++ ) {
281
				groups[i] = groupList.get(i).getGroupName();
282
			  
283
			}
284
			
285
			// get the object bytes
286
			// TODO: stream to file to stream conversion throughout Metacat needs to
287
			// be resolved
288
      File tmpDir;
289
      try
290
      {
291
          tmpDir = new File(PropertyService.getProperty("application.tempDir"));
292
      }
293
      catch(PropertyNotFoundException pnfe)
294
      {
295
          logMetacat.error("D1NodeService.get(): " +
296
                  "application.tmpDir not found.  Using /tmp instead.");
297
          tmpDir = new File("/tmp");
298
      }
299
      
300
      Date d = new Date();
301
      final File outputFile = new File(tmpDir, "metacat.output." + d.getTime());
302
      FileOutputStream dataSink;
303
      try {
304
	      dataSink = new FileOutputStream(outputFile);
305
	      handler.readFromMetacat(localId, null, 
306
            dataSink, localId, "xml",
307
            subject.getValue(), 
308
            groups, true, params);
309

    
310
      } catch (FileNotFoundException e) {
311
        throw new ServiceFailure("1020", "The object specified by " + 
312
            pid.getValue() +
313
            "could not be returned due to a file read error: " +
314
            e.getMessage());
315
        
316
      } catch (PropertyNotFoundException e) {
317
        throw new ServiceFailure("1020", "The object specified by " + 
318
            pid.getValue() +
319
            "could not be returned due to an internal error: " +
320
            e.getMessage());
321
        
322
      } catch (ClassNotFoundException e) {
323
        throw new ServiceFailure("1020", "The object specified by " + 
324
            pid.getValue() +
325
            "could not be returned due to an internal error: " +
326
            e.getMessage());
327
        
328
      } catch (IOException e) {
329
        throw new ServiceFailure("1020", "The object specified by " + 
330
            pid.getValue() +
331
            "could not be returned due to a file read error: " +
332
            e.getMessage());
333
        
334
      } catch (SQLException e) {
335
        throw new ServiceFailure("1020", "The object specified by " + 
336
            pid.getValue() +
337
            "could not be returned due to a database error: " +
338
            e.getMessage());
339
        
340
      } catch (McdbException e) {
341
        throw new ServiceFailure("1020", "The object specified by " + 
342
            pid.getValue() +
343
            "could not be returned due to database error: " +
344
            e.getMessage());
345
        
346
      } catch (ParseLSIDException e) {
347
        throw new ServiceFailure("1020", "The object specified by " + 
348
            pid.getValue() +
349
            "could not be returned due to a parse error: " +
350
            e.getMessage());
351
        
352
      } catch (InsufficientKarmaException e) {
353
	      // TODO Auto-generated catch block
354
	      e.printStackTrace();
355
      }
356
      
357
      //set a timer to clean up the temp files
358
      Timer t = new Timer();
359
      TimerTask tt = new TimerTask() {
360
          @Override
361
          public void run()
362
          {
363
              outputFile.delete();
364
          }
365
      };
366
      t.schedule(tt, 20000); //schedule after 20 secs
367
      
368
      try {
369
	      inputStream = new FileInputStream(outputFile);
370
      } catch (FileNotFoundException e) {
371
        throw new ServiceFailure("1020", "The object specified by " + 
372
          pid.getValue() +
373
          "could not be returned due to a file read error: " +
374
          e.getMessage());
375
        
376
      }			
377
			
378
		}
379

    
380
		// if we fail to set the input stream
381
		if ( inputStream == null ) {
382
      throw new NotFound("1020", "The object specified by " + 
383
                         pid.getValue() +
384
                         "does not exist at this node.");
385
		}
386
		
387
		return inputStream;
388
	}
389

    
390
	/**
391
	 * Return the system metadata for a given object
392
	 * 
393
	 * @param session - the Session object containing the credentials for the Subject
394
	 * @param pid - the object identifier for the given object
395
	 * 
396
	 * @return inputStream - the input stream of the given system metadata object
397
	 * 
398
	 * @throws InvalidToken
399
	 * @throws ServiceFailure
400
	 * @throws NotAuthorized
401
	 * @throws NotFound
402
	 * @throws InvalidRequest
403
	 * @throws NotImplemented
404
	 */
405
	public SystemMetadata getSystemMetadata(Session session, Identifier pid)
406
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
407
    InvalidRequest, NotImplemented {
408

    
409
		if (!isAuthorized(session, pid, Permission.READ)) {
410
			throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());	
411
		}
412
		SystemMetadata systemMetadata = null;
413
		try {
414
			systemMetadata = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
415
		} catch (McdbDocNotFoundException e) {
416
			throw new NotFound("1420", "No record found for: " + pid.getValue());
417
		}
418
		
419
		return systemMetadata;
420
  }
421
	
422
	/* End methods common to CNRead and MNRead APIs */
423

    
424
	/* Methods common to CNAuthorization and MNAuthorization APIs */
425
  
426
	/**
427
	 * Set access for a given object using the object identifier and a Subject
428
	 * under a given Session.
429
	 * 
430
	 * @param session - the Session object containing the credentials for the Subject
431
	 * @param pid - the object identifier for the given object to apply the policy
432
	 * @param policy - the access policy to be applied
433
	 * 
434
	 * @return true if the application of the policy succeeds
435
	 * @throws InvalidToken
436
	 * @throws ServiceFailure
437
	 * @throws NotFound
438
	 * @throws NotAuthorized
439
	 * @throws NotImplemented
440
	 * @throws InvalidRequest
441
	 */
442
	public boolean setAccessPolicy(Session session, Identifier pid, 
443
    AccessPolicy accessPolicy) 
444
	  throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
445
	  NotImplemented, InvalidRequest {
446

    
447
		boolean success = false;
448
		
449
		// get the subject
450
		Subject subject = session.getSubject();
451
		// get the system metadata
452
		String guid = pid.getValue();
453
		
454
		// are we allowed to do this?
455
		if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
456
			throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + " on " + guid);	
457
		}
458
		
459
		SystemMetadata systemMetadata = null;
460
		try {
461
			systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
462
		} catch (McdbDocNotFoundException e) {
463
			throw new NotFound("4400", "No record found for: " + guid);
464
		}
465
				
466
		// set the access policy
467
		systemMetadata.setAccessPolicy(accessPolicy);
468
		
469
		// update the metadata
470
		try {
471
			IdentifierManager.getInstance().updateSystemMetadata(systemMetadata);
472
			success = true;
473
		} catch (McdbDocNotFoundException e) {
474
			throw new ServiceFailure("4430", e.getMessage());
475
		}
476

    
477
		return success;
478
	}
479
	
480
	/**
481
	 * Test if the user identified by the provided token has authorization 
482
	 * for operation on the specified object.
483
	 * 
484
	 * @param session - the Session object containing the credentials for the Subject
485
	 * @param pid - The identifer of the resource for which access is being checked
486
	 * @param operation - The type of operation which is being requested for the given pid
487
	 *
488
	 * @return true if the operation is allowed
489
	 * 
490
	 * @throws ServiceFailure
491
	 * @throws InvalidToken
492
	 * @throws NotFound
493
	 * @throws NotAuthorized
494
	 * @throws NotImplemented
495
	 * @throws InvalidRequest
496
	 */
497
	public boolean isAuthorized(Session session, Identifier pid, Permission permission)
498
	  throws ServiceFailure, InvalidToken, NotFound, NotAuthorized,
499
	  NotImplemented, InvalidRequest {
500

    
501
		boolean allowed = false;
502
		
503
		// get the subjects from the session
504
		List<Subject> subjects = new ArrayList<Subject>();
505
		subjects.add(session.getSubject());
506
		for (Person p: session.getSubjectList().getPersonList()) {
507
			subjects.add(p.getSubject());
508
		}
509
		for (Group g: session.getSubjectList().getGroupList()) {
510
			subjects.add(g.getSubject());
511
		}
512
		
513
		// get the system metadata
514
		String guid = pid.getValue();
515
		SystemMetadata systemMetadata = null;
516
		try {
517
			systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
518
		} catch (McdbDocNotFoundException e) {
519
			throw new NotFound("1800", "No record found for: " + guid);
520
		}
521
		List<AccessRule> allows = systemMetadata.getAccessPolicy().getAllowList();
522
		for (AccessRule accessRule: allows) {
523
			for (Subject subject: subjects) {
524
				if (accessRule.getSubjectList().contains(subject)) {
525
					allowed = accessRule.getPermissionList().contains(permission);
526
					if (allowed) {
527
						break;
528
					}
529
				}
530
			}
531
		}
532
		
533
		// TODO: throw or return?
534
		if (!allowed) {
535
			throw new NotAuthorized("1820", permission + "not allowed on " + guid);
536
		}
537
		return allowed;
538
	}
539

    
540
	/* End methods common to CNAuthorization and MNAuthorization APIs */
541
	
542
	/**
543
	 * parse a logEntry and get the relevant field from it
544
	 * 
545
	 * @param fieldname
546
	 * @param entry
547
	 * @return
548
	 */
549
	private String getLogEntryField(String fieldname, String entry) {
550
		String begin = "<" + fieldname + ">";
551
		String end = "</" + fieldname + ">";
552
		// logMetacat.debug("looking for " + begin + " and " + end +
553
		// " in entry " + entry);
554
		String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
555
				.indexOf(end));
556
		logMetacat.debug("entry " + fieldname + " : " + s);
557
		return s;
558
	}
559

    
560
  /**
561
   * set the params for this service from an HttpServletRequest param list
562
   */
563
  public void setParamsFromRequest(HttpServletRequest request) {
564
      
565
  	@SuppressWarnings("unchecked")
566
    Enumeration<String> paramlist = request.getParameterNames();
567
    while (paramlist.hasMoreElements()) {
568
      String name = (String) paramlist.nextElement();
569
      String[] value = (String[])request.getParameterValues(name);
570
      params.put(name, value);
571
        
572
    }
573
  }
574
  
575

    
576
}
(3-3/8)