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 11:18:42 -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
    
251
    // get the local docid from Metacat
252
    try {
253
      localId = IdentifierManager.getInstance().getLocalId(pid.getValue());
254
    
255
    } catch (McdbDocNotFoundException e) {
256
      throw new NotFound("1020", "The object specified by " + 
257
                         pid.getValue() +
258
                         "does not exist at this node.");
259
    }
260
    
261
    // check for authorization
262
    allowed = isAuthorized(session, pid, Permission.READ);
263
    
264
    // if the person is authorized, perform the read
265
    if ( allowed ) {
266
      
267
      // get the object bytes
268
      // TODO: stream to file to stream conversion throughout Metacat needs to
269
      // be resolved
270
      File tmpDir;
271
      try
272
      {
273
          tmpDir = new File(PropertyService.getProperty("application.tempDir"));
274
      }
275
      catch(PropertyNotFoundException pnfe)
276
      {
277
          logMetacat.error("D1NodeService.get(): " +
278
                  "application.tmpDir not found.  Using /tmp instead.");
279
          tmpDir = new File("/tmp");
280
      }
281
      
282
      Date d = new Date();
283
      final File outputFile = new File(tmpDir, "metacat.output." + d.getTime());
284
      FileOutputStream dataSink;
285
      try {
286
        dataSink = new FileOutputStream(outputFile);
287
        handler.read(localId);
288

    
289
      } catch (FileNotFoundException e) {
290
        throw new ServiceFailure("1020", "The object specified by " + 
291
            pid.getValue() +
292
            "could not be returned due to a file read error: " +
293
            e.getMessage());
294
        
295
      } catch (PropertyNotFoundException e) {
296
        throw new ServiceFailure("1020", "The object specified by " + 
297
            pid.getValue() +
298
            "could not be returned due to an internal error: " +
299
            e.getMessage());
300
        
301
      } catch (ClassNotFoundException e) {
302
        throw new ServiceFailure("1020", "The object specified by " + 
303
            pid.getValue() +
304
            "could not be returned due to an internal error: " +
305
            e.getMessage());
306
        
307
      } catch (IOException e) {
308
        throw new ServiceFailure("1020", "The object specified by " + 
309
            pid.getValue() +
310
            "could not be returned due to a file read error: " +
311
            e.getMessage());
312
        
313
      } catch (SQLException e) {
314
        throw new ServiceFailure("1020", "The object specified by " + 
315
            pid.getValue() +
316
            "could not be returned due to a database error: " +
317
            e.getMessage());
318
        
319
      } catch (McdbException e) {
320
        throw new ServiceFailure("1020", "The object specified by " + 
321
            pid.getValue() +
322
            "could not be returned due to database error: " +
323
            e.getMessage());
324
        
325
      } catch (ParseLSIDException e) {
326
        throw new ServiceFailure("1020", "The object specified by " + 
327
            pid.getValue() +
328
            "could not be returned due to a parse error: " +
329
            e.getMessage());
330
        
331
      }
332
      
333
      //set a timer to clean up the temp files
334
      Timer t = new Timer();
335
      TimerTask tt = new TimerTask() {
336
          @Override
337
          public void run()
338
          {
339
              outputFile.delete();
340
          }
341
      };
342
      t.schedule(tt, 20000); //schedule after 20 secs
343
      
344
      try {
345
        inputStream = new FileInputStream(outputFile);
346
      } catch (FileNotFoundException e) {
347
        throw new ServiceFailure("1020", "The object specified by " + 
348
          pid.getValue() +
349
          "could not be returned due to a file read error: " +
350
          e.getMessage());
351
        
352
      }      
353
      
354
    }
355

    
356
    // if we fail to set the input stream
357
    if ( inputStream == null ) {
358
      throw new NotFound("1020", "The object specified by " + 
359
                         pid.getValue() +
360
                         "does not exist at this node.");
361
    }
362
    
363
    return inputStream;
364
  }
365

    
366
  /**
367
   * Return the system metadata for a given object
368
   * 
369
   * @param session - the Session object containing the credentials for the Subject
370
   * @param pid - the object identifier for the given object
371
   * 
372
   * @return inputStream - the input stream of the given system metadata object
373
   * 
374
   * @throws InvalidToken
375
   * @throws ServiceFailure
376
   * @throws NotAuthorized
377
   * @throws NotFound
378
   * @throws InvalidRequest
379
   * @throws NotImplemented
380
   */
381
  public SystemMetadata getSystemMetadata(Session session, Identifier pid)
382
    throws InvalidToken, ServiceFailure, NotAuthorized, NotFound,
383
    InvalidRequest, NotImplemented {
384

    
385
    if (!isAuthorized(session, pid, Permission.READ)) {
386
      throw new NotAuthorized("1400", Permission.READ + " not allowed on " + pid.getValue());  
387
    }
388
    SystemMetadata systemMetadata = null;
389
    try {
390
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(pid.getValue());
391
    } catch (McdbDocNotFoundException e) {
392
      throw new NotFound("1420", "No record found for: " + pid.getValue());
393
    }
394
    
395
    return systemMetadata;
396
  }
397
  
398
  /* End methods common to CNRead and MNRead APIs */
399

    
400
  /* Methods common to CNAuthorization and MNAuthorization APIs */
401
  
402
  /**
403
   * Set access for a given object using the object identifier and a Subject
404
   * under a given Session.
405
   * 
406
   * @param session - the Session object containing the credentials for the Subject
407
   * @param pid - the object identifier for the given object to apply the policy
408
   * @param policy - the access policy to be applied
409
   * 
410
   * @return true if the application of the policy succeeds
411
   * @throws InvalidToken
412
   * @throws ServiceFailure
413
   * @throws NotFound
414
   * @throws NotAuthorized
415
   * @throws NotImplemented
416
   * @throws InvalidRequest
417
   */
418
  public boolean setAccessPolicy(Session session, Identifier pid, 
419
    AccessPolicy accessPolicy) 
420
    throws InvalidToken, ServiceFailure, NotFound, NotAuthorized, 
421
    NotImplemented, InvalidRequest {
422

    
423
    boolean success = false;
424
    
425
    // get the subject
426
    Subject subject = session.getSubject();
427
    // get the system metadata
428
    String guid = pid.getValue();
429
    
430
    // are we allowed to do this?
431
    if (!isAuthorized(session, pid, Permission.CHANGE_PERMISSION)) {
432
      throw new NotAuthorized("4420", "not allowed by " + subject.getValue() + " on " + guid);  
433
    }
434
    
435
    SystemMetadata systemMetadata = null;
436
    try {
437
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
438
    } catch (McdbDocNotFoundException e) {
439
      throw new NotFound("4400", "No record found for: " + guid);
440
    }
441
        
442
    // set the access policy
443
    systemMetadata.setAccessPolicy(accessPolicy);
444
    
445
    // update the metadata
446
    try {
447
      IdentifierManager.getInstance().updateSystemMetadata(systemMetadata);
448
      success = true;
449
    } catch (McdbDocNotFoundException e) {
450
      throw new ServiceFailure("4430", e.getMessage());
451
    }
452

    
453
    return success;
454
  }
455
  
456
  /**
457
   * Test if the user identified by the provided token has authorization 
458
   * for operation on the specified object.
459
   * 
460
   * @param session - the Session object containing the credentials for the Subject
461
   * @param pid - The identifer of the resource for which access is being checked
462
   * @param operation - The type of operation which is being requested for the given pid
463
   *
464
   * @return true if the operation is allowed
465
   * 
466
   * @throws ServiceFailure
467
   * @throws InvalidToken
468
   * @throws NotFound
469
   * @throws NotAuthorized
470
   * @throws NotImplemented
471
   * @throws InvalidRequest
472
   */
473
  public boolean isAuthorized(Session session, Identifier pid, Permission permission)
474
    throws ServiceFailure, InvalidToken, NotFound, NotAuthorized,
475
    NotImplemented, InvalidRequest {
476

    
477
    boolean allowed = false;
478
    
479
    // get the subjects from the session
480
    List<Subject> subjects = new ArrayList<Subject>();
481
    subjects.add(session.getSubject());
482
    for (Person p: session.getSubjectList().getPersonList()) {
483
      subjects.add(p.getSubject());
484
    }
485
    for (Group g: session.getSubjectList().getGroupList()) {
486
      subjects.add(g.getSubject());
487
    }
488
    
489
    // get the system metadata
490
    String guid = pid.getValue();
491
    SystemMetadata systemMetadata = null;
492
    try {
493
      systemMetadata = IdentifierManager.getInstance().getSystemMetadata(guid);
494
    } catch (McdbDocNotFoundException e) {
495
      throw new NotFound("1800", "No record found for: " + guid);
496
    }
497
    List<AccessRule> allows = systemMetadata.getAccessPolicy().getAllowList();
498
    for (AccessRule accessRule: allows) {
499
      for (Subject subject: subjects) {
500
        if (accessRule.getSubjectList().contains(subject)) {
501
          allowed = accessRule.getPermissionList().contains(permission);
502
          if (allowed) {
503
            break;
504
          }
505
        }
506
      }
507
    }
508
    
509
    // TODO: throw or return?
510
    if (!allowed) {
511
      throw new NotAuthorized("1820", permission + "not allowed on " + guid);
512
    }
513
    return allowed;
514
  }
515

    
516
  /* End methods common to CNAuthorization and MNAuthorization APIs */
517
  
518
  /**
519
   * parse a logEntry and get the relevant field from it
520
   * 
521
   * @param fieldname
522
   * @param entry
523
   * @return
524
   */
525
  private String getLogEntryField(String fieldname, String entry) {
526
    String begin = "<" + fieldname + ">";
527
    String end = "</" + fieldname + ">";
528
    // logMetacat.debug("looking for " + begin + " and " + end +
529
    // " in entry " + entry);
530
    String s = entry.substring(entry.indexOf(begin) + begin.length(), entry
531
        .indexOf(end));
532
    logMetacat.debug("entry " + fieldname + " : " + s);
533
    return s;
534
  }
535

    
536
  /**
537
   * set the params for this service from an HttpServletRequest param list
538
   * 
539
   * @param request - the HTTP Servlet Request with the listed parameters
540
   */
541
  public void setParamsFromRequest(HttpServletRequest request) {
542
      
543
    @SuppressWarnings("unchecked")
544
    Enumeration<String> paramlist = request.getParameterNames();
545
    while (paramlist.hasMoreElements()) {
546
      String name = (String) paramlist.nextElement();
547
      String[] value = (String[])request.getParameterValues(name);
548
      params.put(name, value);
549
        
550
    }
551
  }
552
  
553

    
554
}
(3-3/8)