Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that loads eml-access.xml file containing ACL 
4
 *             for a metadata document into relational DB
5
 *  Copyright: 2000 Regents of the University of California and the
6
 *             National Center for Ecological Analysis and Synthesis
7
 *    Authors: Jivka Bojilova
8
 *    Release: @release@
9
 *
10
 *   '$Author: bojilova $'
11
 *     '$Date: 2001-01-09 17:37:56 -0800 (Tue, 09 Jan 2001) $'
12
 * '$Revision: 652 $'
13
 */
14

    
15
package edu.ucsb.nceas.metacat;
16

    
17
import java.io.*;
18
import java.sql.*;
19
import java.util.Stack;
20
import java.util.Vector;
21
import java.util.Hashtable;
22
import java.net.URL;
23
import java.net.MalformedURLException;
24

    
25
import org.xml.sax.Attributes;
26
import org.xml.sax.InputSource;
27
import org.xml.sax.ContentHandler;
28
import org.xml.sax.EntityResolver;
29
import org.xml.sax.ErrorHandler;
30
import org.xml.sax.SAXException;
31
import org.xml.sax.SAXParseException;
32
import org.xml.sax.XMLReader;
33
import org.xml.sax.helpers.XMLReaderFactory;
34
import org.xml.sax.helpers.DefaultHandler;
35

    
36
/** 
37
 * A Class that loads eml-access.xml file containing ACL for a metadata
38
 * document into relational DB. It extends DefaultHandler class to handle
39
 * SAX parsing events when processing the XML stream.
40
 */
41
public class AccessControlList extends DefaultHandler {
42

    
43
  static final int ALL = 1;
44
  static final int WRITE = 2;
45
  static final int READ = 4;
46

    
47
  private Connection conn;
48
  private String parserName;
49
  private Stack elementStack;
50
  private String server;
51

    
52
  private boolean	processingDTD;
53
  private String  user;
54
  private String  group;
55
  private String  aclid;
56
  private String 	docname;
57
  private String 	doctype;
58
  private String 	systemid;
59

    
60
  private String resourceURL;
61
  private String resourceId;
62
  private Vector principalName;
63
  private int    permission;
64
  private String permType;
65
  private String permOrder;
66
  private String beginTime;
67
  private String endTime;
68
  private int    ticketCount;
69
  
70
  /**
71
   * Construct an instance of the AccessControlList class.
72
   * It is used by the permission check up from DBQuery and DocumentImpl
73
   *
74
   * @param conn the JDBC connection where acl data are loaded
75
   */
76
  public AccessControlList ( Connection conn ) throws SQLException
77
  {
78
    this.conn = conn;
79
  }
80

    
81
  /**
82
   * Construct an instance of the AccessControlList class.
83
   * It parse acl file and loads acl data into db connection.
84
   *
85
   * @param conn the JDBC connection where acl data are loaded
86
   * @param docid the Accession# of the document with the acl data
87
   * @param acl the acl file containing acl data
88
   */
89
  public AccessControlList(Connection conn, String aclid, Reader acl,
90
                           String user, String group)
91
                  throws SAXException, IOException, ClassNotFoundException 
92
  {
93
    // Get an instance of the parser
94
    MetaCatUtil util = new MetaCatUtil();
95
    String parserName = util.getOption("saxparser");
96
    this.server = util.getOption("server");
97

    
98
    this.conn = conn;
99
    this.parserName = parserName;
100
    this.processingDTD = false;
101
    this.elementStack = new Stack();
102
    
103
    this.user = user;
104
    this.group = group;
105
    this.aclid = aclid;
106
    this.principalName = new Vector();
107
    this.permission = 0;
108
    this.ticketCount = 0;
109
    
110
    // Initialize the parser and read the queryspec
111
    XMLReader parser = initializeParser();
112
    parser.parse(new InputSource(acl));
113

    
114
  }
115

    
116
  /**
117
   * Construct an instance of the AccessControlList class.
118
   * It parse acl file and loads acl data into db connection.
119
   *
120
   * @param conn the JDBC connection where acl data are loaded
121
   * @param docid the Accession# of the document with the acl data
122
   * @param aclfilename the name of acl file containing acl data
123
   */
124
  public AccessControlList( Connection conn, String aclid, String aclfilename,
125
                           String user, String group )
126
                  throws SAXException, IOException, ClassNotFoundException 
127
  {
128
    this(conn, aclid, new FileReader(new File(aclfilename).toString()), 
129
         user, group);
130
  }
131
  
132
  /**
133
   * Set up the SAX parser for reading the XML serialized ACL
134
   */
135
  private XMLReader initializeParser() throws SAXException 
136
  {
137
    XMLReader parser = null;
138

    
139
    // Get an instance of the parser
140
    parser = XMLReaderFactory.createXMLReader(parserName);
141

    
142
    // Turn off validation
143
    parser.setFeature("http://xml.org/sax/features/validation", true);
144
      
145
    // Set Handlers in the parser
146
    // Set the ContentHandler to this instance
147
    parser.setContentHandler((ContentHandler)this);
148

    
149
    // make a DBEntityResolver instance
150
    // Set the EntityReslover to DBEntityResolver instance
151
    EntityResolver eresolver = new DBEntityResolver(conn,this,null);
152
    parser.setEntityResolver((EntityResolver)eresolver);
153

    
154
    // Set the ErrorHandler to this instance
155
    parser.setErrorHandler((ErrorHandler)this);
156

    
157
    return parser;
158
  }
159
  
160
  /**
161
   * callback method used by the SAX Parser when beginning of the document
162
   */
163
  public void startDocument() throws SAXException 
164
  {
165
    //delete all previously submitted permissions @ relations
166
    //this happens only on UPDATE of the access file
167
    try {
168
      if ( aclid != null ) {
169
        //first delete all permissions for resources related to @aclid if any
170
        deletePermissionsForRelatedResources(aclid);
171
        //then delete all relations with docid of @aclid if any
172
        deleteRelations(aclid);
173
      }
174
    } catch (SQLException sqle) {
175
      throw new SAXException(sqle);
176
    }
177
  }
178
  
179
  /**
180
   * callback method used by the SAX Parser when the start tag of an 
181
   * element is detected. Used in this context to parse and store
182
   * the acl information in class variables.
183
   */
184
  public void startElement (String uri, String localName, 
185
                            String qName, Attributes atts) 
186
         throws SAXException 
187
  {
188
    BasicNode currentNode = new BasicNode(localName);
189
    if (atts != null) {
190
      int len = atts.getLength();
191
      for (int i = 0; i < len; i++) {
192
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
193
      }
194
    }
195
    if ( currentNode.getTagName().equals("resource") ) {
196
      permOrder = currentNode.getAttribute("order");
197
    }
198
    elementStack.push(currentNode); 
199
  }
200

    
201
  /**
202
   * callback method used by the SAX Parser when the text sequences of an 
203
   * xml stream are detected. Used in this context to parse and store
204
   * the acl information in class variables.
205
   */
206
  public void characters(char ch[], int start, int length)
207
         throws SAXException 
208
  {
209
    String inputString = new String(ch, start, length);
210
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
211
    String currentTag = currentNode.getTagName();
212

    
213
    if (currentTag.equals("resourceIdentifier")) {
214

    
215
      resourceURL = inputString;
216
      resourceId = getDocid(inputString);
217
      // check permissions for @user on resourceId first
218
      // @user must have permission "all" on resourceId
219
      boolean hasPermission = false;
220
      try {
221
        hasPermission = hasPermission("ALL",user,resourceId);
222
        if ( !hasPermission && group != null ) {
223
          hasPermission = hasPermission("ALL",group,resourceId);
224
        }
225
      } catch (SQLException e) {
226
        throw new SAXException(e.getMessage());
227
      }
228
      if ( !hasPermission ) {
229
        throw new SAXException(
230
         "Permission denied for setting access control on " + resourceId);
231
      }
232
      // end of check for "all" perm on resourceId
233

    
234
    } else if (currentTag.equals("principalName")) {
235

    
236
      principalName.addElement(new String(inputString));
237

    
238
    } else if (currentTag.equals("permission")) {
239

    
240
      if ( inputString.trim().toUpperCase().equals("READ") ) {
241
        permission = permission | READ;
242
      } else if ( inputString.trim().toUpperCase().equals("WRITE") ) {
243
        permission = permission | WRITE;
244
      } else if ( inputString.trim().toUpperCase().equals("ALL") ) {
245
        permission = permission | ALL;
246
      }
247

    
248
    } else if (currentTag.equals("duration") && 
249
               beginTime == null && endTime == null ) {
250
      try {
251
        beginTime = inputString.substring(0, inputString.indexOf(" "));
252
        endTime = inputString.substring(inputString.indexOf(" ")+1);
253
      } catch (StringIndexOutOfBoundsException se) {
254
        beginTime = inputString;
255
      }
256

    
257
    } else if (currentTag.equals("ticketCount") && ticketCount == 0 ) {
258
      ticketCount = (new Integer(inputString.trim())).intValue();
259
    }
260
  }
261

    
262
  /**
263
   * callback method used by the SAX Parser when the end tag of an 
264
   * element is detected. Used in this context to parse and store
265
   * the acl information in class variables.
266
   */
267
  public void endElement (String uri, String localName, String qName)
268
         throws SAXException 
269
  {
270
    BasicNode leaving = (BasicNode)elementStack.pop(); 
271
    if ( leaving.getTagName().equals("resourceIdentifier") ) {
272
      
273
      try {
274
        // make a relationship for @aclid on @resourceId
275
        if ( aclid != null ) {
276
          insertRelation(aclid, resourceURL);
277
        }
278
      } catch (SQLException sqle) {
279
        throw new SAXException(sqle);
280
      }
281
      
282
    } else if ( leaving.getTagName().equals("allow") ) {
283
      
284
      if ( permission > 0 ) {
285

    
286
        // insert into db calculated permission for the list of principals
287
        try {
288
          insertPermissions("allowed");
289
        } catch (SQLException sqle) {
290
          throw new SAXException(sqle);
291
        }
292
      }
293

    
294
      // reset the allowed permission
295
      principalName = new Vector();
296
      permission = 0;
297
      beginTime = null;
298
      endTime = null;
299
      ticketCount = 0;
300
    
301
    } else if ( leaving.getTagName().equals("deny") ) {
302
      
303
      if ( permission > 0 ) {
304

    
305
        // insert into db calculated permission for the list of principals
306
        try {
307
          insertPermissions("denied");
308
        } catch (SQLException sqle) {
309
          throw new SAXException(sqle);
310
        }
311
      }
312

    
313
      // reset the denied permission
314
      principalName = new Vector();
315
      permission = 0;
316
      beginTime = null;
317
      endTime = null;
318
      ticketCount = 0;
319

    
320
    } else if ( leaving.getTagName().equals("resource") ) {
321
      // reset the resource identifier
322
      resourceId = null;
323
      permOrder = null;
324
    }
325

    
326
  }
327

    
328
  /** SAX Handler that receives notification of DOCTYPE. Sets the DTD */
329
  public void startDTD(String name, String publicId, String systemId) 
330
              throws SAXException {
331
    docname = name;
332
    doctype = publicId;
333
    systemid = systemId;
334
//    processingDTD = true;
335
  }
336

    
337
  /** 
338
   * SAX Handler that receives notification of the start of entities
339
   */
340
  public void startEntity(String name) throws SAXException {
341
    if (name.equals("[dtd]")) {
342
      processingDTD = true;
343
    }
344
  }
345

    
346
  /** 
347
   * SAX Handler that receives notification of the end of entities
348
   */
349
  public void endEntity(String name) throws SAXException {
350
    if (name.equals("[dtd]")) {
351
      processingDTD = false;
352
    }
353
  }
354

    
355
  /**
356
   * get the document name
357
   */
358
  public String getDocname() {
359
    return docname;
360
  }
361

    
362
  /**
363
   * get the document processing state
364
   */
365
  public boolean processingDTD() {
366
    return processingDTD;
367
  }
368
  
369
  /** Delete from db all permission for resources related to @aclid if any */
370
  private void deletePermissionsForRelatedResources(String aclid) 
371
          throws SQLException 
372
  {
373
    // delete all acl records for resources related to @aclid if any
374
    Statement stmt = conn.createStatement();
375
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'");
376
    stmt.close();
377
  }
378

    
379
  /**
380
   * Deletes all of the relations with a docid of @docid.
381
   * @param docid the docid to delete.
382
   */
383
  public void deleteRelations(String docid)
384
          throws SQLException 
385
  {
386
    Statement stmt = conn.createStatement();
387
    stmt.execute("DELETE FROM xml_relation WHERE docid='" + docid + "'");
388
    stmt.close();
389
  }
390

    
391
  /** Insert relationship into db for @aclid on @resourceURL  */
392
  private void insertRelation(String aclid, String resourceURL) 
393
          throws SQLException 
394
  {
395
    String aclURL = "metacat://" + server + "/?docid=" + aclid;
396
    
397
    // insert relationship 
398
    PreparedStatement pstmt;
399
    pstmt = conn.prepareStatement(
400
            "INSERT INTO xml_relation (docid,subject,relationship,object) " +
401
            "VALUES (?, ?, ?, ?)");
402
    pstmt.setString(1, aclid);
403
    pstmt.setString(2, aclURL);
404
    pstmt.setString(3, "isaclfor");
405
    pstmt.setString(4, resourceURL);
406

    
407
    pstmt.execute();
408
    pstmt.close();
409
  }
410

    
411
  /** Insert into db calculated permission for the list of principals */
412
  private void insertPermissions( String permType ) 
413
          throws SQLException 
414
  {
415
    PreparedStatement pstmt;
416
 
417
    try {
418
      pstmt = conn.prepareStatement(
419
              "INSERT INTO xml_access " + 
420
              "(docid, principal_name, permission, perm_type, perm_order," +
421
              "begin_time,end_time,ticket_count, accessfileid) VALUES " +
422
              "(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?,?)");
423
      // Bind the values to the query
424
      pstmt.setString(1, resourceId);
425
      pstmt.setInt(3, permission);
426
      pstmt.setString(4, permType);
427
      pstmt.setString(5, permOrder);
428
      pstmt.setString(6, beginTime);
429
      pstmt.setString(7, endTime);
430
      pstmt.setString(9, aclid);
431
      if ( ticketCount > 0 ) {
432
        pstmt.setString(8, "" + ticketCount);
433
      } else {
434
        pstmt.setString(8, "");
435
      }
436
      for ( int i = 0; i < principalName.size(); i++ ) {
437
        pstmt.setString(2, (String)principalName.elementAt(i));
438
        pstmt.execute();
439
      }
440

    
441
    } catch (SQLException e) {
442
      throw new 
443
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
444
    }
445
  }
446

    
447
  /** Check for @permission for @principal on @resourceId from db connection */
448
  public boolean hasPermission ( String permission,
449
                                 String principal, String resourceId )
450
                 throws SQLException
451
  {
452
    PreparedStatement pstmt;
453
    // check public access to @resourceId from xml_documents table
454
    if ( permission.equals("READ") ) {
455
      try {
456
        pstmt = conn.prepareStatement(
457
                "SELECT 'x' FROM xml_documents " +
458
                "WHERE docid LIKE ? AND public_access = 1");
459
        // Bind the values to the query
460
        pstmt.setString(1, resourceId);
461

    
462
        pstmt.execute();
463
        ResultSet rs = pstmt.getResultSet();
464
        boolean hasRow = rs.next();
465
        pstmt.close();
466
        if (hasRow) {
467
          return true;
468
        }
469
//System.out.println("Passed the check for public access");      
470

    
471
      } catch (SQLException e) {
472
        throw new 
473
        SQLException("Error checking document's public access: "
474
                      + e.getMessage());
475
      }
476
    }
477
    
478
    // since owner of resource has all permission on it,
479
    // check if @principal is owner of @resourceId in xml_documents table
480
    if ( principal != null ) {
481
      try {
482
        pstmt = conn.prepareStatement(
483
                "SELECT 'x' FROM xml_documents " +
484
                "WHERE docid LIKE ? AND user_owner LIKE ?");
485
        // Bind the values to the query
486
        pstmt.setString(1, resourceId);
487
        pstmt.setString(2, principal);
488

    
489
        pstmt.execute();
490
        ResultSet rs = pstmt.getResultSet();
491
        boolean hasRow = rs.next();
492
        pstmt.close();
493
        if (hasRow) {
494
          return true;
495
        }
496
//System.out.println("Passed the check for ownership");      
497
     
498
      } catch (SQLException e) {
499
        throw new 
500
        SQLException("AccessControlList.hasPermission(): " +
501
                     "Error checking document's ownership. " + e.getMessage());
502
      }
503

    
504
      // check @principal's @permission on @resourceId from xml_access table
505
      int accessValue = 0;
506
      int ticketCount = 0;
507
      String permOrder = "";
508
      try {
509
        pstmt = conn.prepareStatement(
510
                "SELECT permission, perm_order, ticket_count " +
511
                "FROM xml_access " +
512
                "WHERE docid LIKE ? " + 
513
                "AND principal_name LIKE ? " +
514
                "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
515
                                "AND nvl(end_time,sysdate) " +
516
                "AND perm_type LIKE ?");
517
        // check if it is "denied" first
518
        // Bind the values to the query
519
        pstmt.setString(1, resourceId);
520
        pstmt.setString(2, principal);
521
        pstmt.setString(3, "denied");
522

    
523
        pstmt.execute();
524
        ResultSet rs = pstmt.getResultSet();
525
        boolean hasRows = rs.next();
526
        while ( hasRows ) {
527
          accessValue = rs.getInt(1);
528
          permOrder = rs.getString(2);
529
          ticketCount = rs.getInt(3);
530
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
531
               ( permOrder.equals("allowFirst") ) &&
532
               ( rs.wasNull() || ticketCount > 0 ) ) {
533
            if ( !rs.wasNull() && ticketCount > 0 ) {
534
              decreaseNumberOfAccess(accessValue,principal,resourceId,"denied");
535
            }
536
            pstmt.close();
537
            return false;
538
          }
539
          hasRows = rs.next();
540
        }
541
//System.out.println("Passed the check for denied access");      
542

    
543
        // it is not denied then check if it is "allowed"
544
        // Bind the values to the query
545
        pstmt.setString(1, resourceId);
546
        pstmt.setString(2, principal);
547
        pstmt.setString(3, "allowed");
548

    
549
        pstmt.execute();
550
        rs = pstmt.getResultSet();
551
        hasRows = rs.next();
552
        while ( hasRows ) {
553
          accessValue = rs.getInt(1);
554
          ticketCount = rs.getInt(3);
555
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
556
               ( rs.wasNull() || ticketCount > 0 ) ) {
557
            if ( !rs.wasNull() && ticketCount > 0 ) {
558
              decreaseNumberOfAccess(accessValue,principal,resourceId,"allowed");
559
            }
560
            pstmt.close();
561
            return true;
562
          }
563
          hasRows = rs.next();
564
        }
565
//System.out.println("Passed the check for allowed access");      
566

    
567
        // ???
568
        // here there could be a check for the group's permission like 
569
        // selfrecursive call to hasPermission(conn,permission,group,recourceId)
570
        //
571
      
572
        pstmt.close();
573
        return false;
574
  
575
      } catch (SQLException e) {
576
        throw new 
577
        SQLException("AccessControlList.hasPermission(): " +
578
                     "Error checking document's permission. " + e.getMessage());
579
      }
580
    }
581
    
582
    return false;
583
  }
584

    
585
  /** decrease the number of access to @resourceId for @principal */
586
  private void decreaseNumberOfAccess(int permission, String principal,
587
                                      String resourceId, String permType)
588
               throws SQLException
589
  {
590
    PreparedStatement pstmt;
591
    pstmt = conn.prepareStatement(
592
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
593
            "WHERE docid LIKE ? " +
594
            "AND principal_name LIKE ? " +
595
            "AND permission LIKE ? " +
596
            "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
597
                            "AND nvl(end_time,sysdate) " +
598
            "AND perm_type LIKE ?");
599
    // Bind the values to the query
600
    pstmt.setString(1, resourceId);
601
    pstmt.setString(2, principal);
602
    pstmt.setInt(3, permission);
603
    pstmt.setString(4, permType);
604

    
605
    pstmt.execute();
606
    pstmt.close();
607
  }
608
 
609
  // get the int value of READ, WRITE or ALL
610
  private int intValue ( String permission )
611
  {
612
    if ( permission.equals("READ") ) {
613
      return READ;
614
    } else if ( permission.equals("WRITE") ) {
615
      return WRITE;
616
    } else if ( permission.equals("ALL") ) {
617
      return ALL;
618
    }
619
    
620
    return -1;
621
  }
622
  
623
  // get docid from @url
624
  private String getDocid ( String url ) throws SAXException
625
  {
626
    MetaCatUtil util = new MetaCatUtil();
627
    try {
628
      URL urlobj = new URL(url);
629
      Hashtable urlParams = util.parseQuery(urlobj.getQuery());
630
      if ( urlParams.containsKey("docid") ) {
631
        return (String)urlParams.get("docid"); // return the docid value
632
      } else {
633
        throw new 
634
        SAXException("\"docid\" not specified within " + url);
635
      }
636
    } catch (MalformedURLException e) {
637
      throw new SAXException(e.getMessage());
638
    }
639
  }  
640

    
641
}
(1-1/43)