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: jones $'
11
 *     '$Date: 2001-01-18 11:52:00 -0800 (Thu, 18 Jan 2001) $'
12
 * '$Revision: 669 $'
13
 *
14
 * This program is free software; you can redistribute it and/or modify
15
 * it under the terms of the GNU General Public License as published by
16
 * the Free Software Foundation; either version 2 of the License, or
17
 * (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU General Public License
25
 * along with this program; if not, write to the Free Software
26
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
 */
28

    
29
package edu.ucsb.nceas.metacat;
30

    
31
import java.io.*;
32
import java.sql.*;
33
import java.util.Stack;
34
import java.util.Vector;
35
import java.util.Hashtable;
36
import java.net.URL;
37
import java.net.MalformedURLException;
38

    
39
import org.xml.sax.Attributes;
40
import org.xml.sax.InputSource;
41
import org.xml.sax.ContentHandler;
42
import org.xml.sax.EntityResolver;
43
import org.xml.sax.ErrorHandler;
44
import org.xml.sax.SAXException;
45
import org.xml.sax.SAXParseException;
46
import org.xml.sax.XMLReader;
47
import org.xml.sax.helpers.XMLReaderFactory;
48
import org.xml.sax.helpers.DefaultHandler;
49

    
50
/** 
51
 * A Class that loads eml-access.xml file containing ACL for a metadata
52
 * document into relational DB. It extends DefaultHandler class to handle
53
 * SAX parsing events when processing the XML stream.
54
 */
55
public class AccessControlList extends DefaultHandler {
56

    
57
  static final int ALL = 1;
58
  static final int WRITE = 2;
59
  static final int READ = 4;
60

    
61
  private Connection conn;
62
  private String parserName;
63
  private Stack elementStack;
64
  private String server;
65

    
66
  private boolean	processingDTD;
67
  private String  user;
68
  private String  group;
69
  private String  aclid;
70
  private String 	docname;
71
  private String 	doctype;
72
  private String 	systemid;
73

    
74
  private String docurl;
75
  private Vector resourceURL;
76
  private Vector resourceID;
77
  private Vector principal;
78
  private int    permission;
79
  private String permType;
80
  private String permOrder;
81
  private String beginTime;
82
  private String endTime;
83
  private int    ticketCount;
84
  
85
  /**
86
   * Construct an instance of the AccessControlList class.
87
   * It is used by the permission check up from DBQuery and DocumentImpl
88
   *
89
   * @param conn the JDBC connection where acl data are loaded
90
   */
91
  public AccessControlList ( Connection conn ) throws SQLException
92
  {
93
    this.conn = conn;
94
  }
95

    
96
  /**
97
   * Construct an instance of the AccessControlList class.
98
   * It parse acl file and loads acl data into db connection.
99
   *
100
   * @param conn the JDBC connection where acl data are loaded
101
   * @param docid the Accession# of the document with the acl data
102
   * @param acl the acl file containing acl data
103
   */
104
  public AccessControlList(Connection conn, String aclid, Reader acl,
105
                           String user, String group)
106
                  throws SAXException, IOException, ClassNotFoundException 
107
  {
108
    // Get an instance of the parser
109
    MetaCatUtil util = new MetaCatUtil();
110
    String parserName = util.getOption("saxparser");
111
    this.server = util.getOption("server");
112

    
113
    this.conn = conn;
114
    this.parserName = parserName;
115
    this.processingDTD = false;
116
    this.elementStack = new Stack();
117
    
118
    this.user = user;
119
    this.group = group;
120
    this.aclid = aclid;
121
    this.resourceURL = new Vector();
122
    this.resourceID = new Vector();
123
    this.principal = new Vector();
124
    this.permission = 0;
125
    this.ticketCount = 0;
126
    
127
    // Initialize the parser and read the queryspec
128
    XMLReader parser = initializeParser();
129
    parser.parse(new InputSource(acl));
130

    
131
  }
132

    
133
  /**
134
   * Construct an instance of the AccessControlList class.
135
   * It parse acl file and loads acl data into db connection.
136
   *
137
   * @param conn the JDBC connection where acl data are loaded
138
   * @param docid the Accession# of the document with the acl data
139
   * @param aclfilename the name of acl file containing acl data
140
   */
141
  public AccessControlList( Connection conn, String aclid, String aclfilename,
142
                           String user, String group )
143
                  throws SAXException, IOException, ClassNotFoundException 
144
  {
145
    this(conn, aclid, new FileReader(new File(aclfilename).toString()), 
146
         user, group);
147
  }
148
  
149
  /* Set up the SAX parser for reading the XML serialized ACL */
150
  private XMLReader initializeParser() throws SAXException 
151
  {
152
    XMLReader parser = null;
153

    
154
    // Get an instance of the parser
155
    parser = XMLReaderFactory.createXMLReader(parserName);
156

    
157
    // Turn off validation
158
    parser.setFeature("http://xml.org/sax/features/validation", true);
159
      
160
    // Set Handlers in the parser
161
    // Set the ContentHandler to this instance
162
    parser.setContentHandler((ContentHandler)this);
163

    
164
    // make a DBEntityResolver instance
165
    // Set the EntityReslover to DBEntityResolver instance
166
    EntityResolver eresolver = new DBEntityResolver(conn,this,null);
167
    parser.setEntityResolver((EntityResolver)eresolver);
168

    
169
    // Set the ErrorHandler to this instance
170
    parser.setErrorHandler((ErrorHandler)this);
171

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

    
216
  /**
217
   * callback method used by the SAX Parser when the text sequences of an 
218
   * xml stream are detected. Used in this context to parse and store
219
   * the acl information in class variables.
220
   */
221
  public void characters(char ch[], int start, int length)
222
         throws SAXException 
223
  {
224
    String inputString = new String(ch, start, length);
225
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
226
    String currentTag = currentNode.getTagName();
227

    
228
    if (currentTag.equals("resourceIdentifier")) {
229

    
230
      // docid of the current resource
231
      String docid = getDocid(inputString); 
232
      // URL string of the current resource
233
      // docurl is declared in the class
234
      try {
235
        docurl = (new URL(inputString)).toString();
236
      } catch (MalformedURLException murle) {
237
        throw new SAXException(murle.getMessage());
238
      }
239
      // collect them in Vector variables
240
      resourceID.addElement(docid);
241
      resourceURL.addElement(docurl);
242
      // check permissions for @user on the current resource first
243
      // @user must have permission "all" on it(docid)
244
      boolean hasPermission = false;
245
      try {
246
        hasPermission = hasPermission("ALL",user,docid);
247
        if ( !hasPermission && group != null ) {
248
          hasPermission = hasPermission("ALL",group,docid);
249
        }
250
      } catch (SQLException e) {
251
        throw new SAXException(e.getMessage());
252
      }
253
      if ( !hasPermission ) {
254
        throw new SAXException(
255
         "Permission denied for setting access control on " + docid);
256
      }
257
      // end of check for "all" perm on docid
258

    
259
    } else if (currentTag.equals("principal")) {
260

    
261
      principal.addElement(inputString);
262

    
263
    } else if (currentTag.equals("permission")) {
264

    
265
      if ( inputString.trim().toUpperCase().equals("READ") ) {
266
        permission = permission | READ;
267
      } else if ( inputString.trim().toUpperCase().equals("WRITE") ) {
268
        permission = permission | WRITE;
269
      } else if ( inputString.trim().toUpperCase().equals("ALL") ) {
270
        permission = permission | ALL;
271
      } else {
272
        throw new SAXException("Unknown permission type: " + inputString);
273
      }
274

    
275
    } else if (currentTag.equals("duration") && 
276
               beginTime == null && endTime == null ) {
277
      try {
278
        beginTime = inputString.substring(0, inputString.indexOf(" "));
279
        endTime = inputString.substring(inputString.indexOf(" ")+1);
280
      } catch (StringIndexOutOfBoundsException se) {
281
        beginTime = inputString;
282
      }
283

    
284
    } else if (currentTag.equals("ticketCount") && ticketCount == 0 ) {
285
      try {
286
        ticketCount = (new Integer(inputString.trim())).intValue();
287
      } catch (NumberFormatException nfe) {
288
        throw new SAXException("Wrong integer format for:" + inputString);
289
      }
290
    }
291
  }
292

    
293
  /**
294
   * callback method used by the SAX Parser when the end tag of an 
295
   * element is detected. Used in this context to parse and store
296
   * the acl information in class variables.
297
   */
298
  public void endElement (String uri, String localName, String qName)
299
         throws SAXException 
300
  {
301
    BasicNode leaving = (BasicNode)elementStack.pop(); 
302
    if ( leaving.getTagName().equals("resourceIdentifier") ) {
303
      
304
      try {
305
        // make a relationship for @aclid on the current resource(docurl)
306
        if ( aclid != null ) {
307
          insertRelation(aclid, docurl);
308
        }
309
      } catch (SQLException sqle) {
310
        throw new SAXException(sqle);
311
      }
312
      
313
    } else if ( leaving.getTagName().equals("allow") ) {
314
      
315
      if ( permission > 0 ) {
316

    
317
        // insert into db calculated permission for the list of principals
318
        try {
319
          insertPermissions("allowed");
320
        } catch (SQLException sqle) {
321
          throw new SAXException(sqle);
322
        }
323
      }
324

    
325
      // reset the allowed permission
326
      principal = new Vector();
327
      permission = 0;
328
      beginTime = null;
329
      endTime = null;
330
      ticketCount = 0;
331
    
332
    } else if ( leaving.getTagName().equals("deny") ) {
333
      
334
      if ( permission > 0 ) {
335

    
336
        // insert into db calculated permission for the list of principals
337
        try {
338
          insertPermissions("denied");
339
        } catch (SQLException sqle) {
340
          throw new SAXException(sqle);
341
        }
342
      }
343

    
344
      // reset the denied permission
345
      principal = new Vector();
346
      permission = 0;
347
      beginTime = null;
348
      endTime = null;
349
      ticketCount = 0;
350

    
351
    } else if ( leaving.getTagName().equals("resource") ) {
352
      // reset the resource identifier
353
      resourceID = new Vector();
354
      resourceURL = new Vector();
355
      permOrder = null;
356
    }
357

    
358
  }
359

    
360
  /** SAX Handler that receives notification of DOCTYPE. Sets the DTD */
361
  public void startDTD(String name, String publicId, String systemId) 
362
              throws SAXException {
363
    docname = name;
364
    doctype = publicId;
365
    systemid = systemId;
366
//    processingDTD = true;
367
  }
368

    
369
  /** 
370
   * SAX Handler that receives notification of the start of entities
371
   */
372
  public void startEntity(String name) throws SAXException {
373
    if (name.equals("[dtd]")) {
374
      processingDTD = true;
375
    }
376
  }
377

    
378
  /** 
379
   * SAX Handler that receives notification of the end of entities
380
   */
381
  public void endEntity(String name) throws SAXException {
382
    if (name.equals("[dtd]")) {
383
      processingDTD = false;
384
    }
385
  }
386

    
387
  /**
388
   * get the document name
389
   */
390
  public String getDocname() {
391
    return docname;
392
  }
393

    
394
  /**
395
   * get the document processing state
396
   */
397
  public boolean processingDTD() {
398
    return processingDTD;
399
  }
400
  
401
  /** Delete from db all permission for resources related to @aclid if any */
402
  private void deletePermissionsForRelatedResources(String aclid) 
403
          throws SQLException 
404
  {
405
    // delete all acl records for resources related to @aclid if any
406
    Statement stmt = conn.createStatement();
407
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'");
408
    stmt.close();
409
  }
410

    
411
  /**
412
   * Deletes all of the relations with a docid of @docid.
413
   * @param docid the docid to delete.
414
   */
415
  public void deleteRelations(String docid)
416
          throws SQLException 
417
  {
418
    Statement stmt = conn.createStatement();
419
    stmt.execute("DELETE FROM xml_relation WHERE docid='" + docid + "'");
420
    stmt.close();
421
  }
422

    
423
  /** Insert relationship into db for @aclid on @resourceURL  */
424
  private void insertRelation(String aclid, String resourceURL) 
425
          throws SQLException 
426
  {
427
    String aclURL = "metacat://" + server + "/?docid=" + aclid;
428
    
429
    // insert relationship 
430
    PreparedStatement pstmt;
431
    pstmt = conn.prepareStatement(
432
            "INSERT INTO xml_relation (docid,subject,relationship,object) " +
433
            "VALUES (?, ?, ?, ?)");
434
    pstmt.setString(1, aclid);
435
    pstmt.setString(2, aclURL);
436
    pstmt.setString(3, "isaclfor");
437
    pstmt.setString(4, resourceURL);
438

    
439
    pstmt.execute();
440
    pstmt.close();
441
  }
442

    
443
  /** Insert into db calculated permission for the list of principals */
444
  private void insertPermissions( String permType ) 
445
          throws SQLException 
446
  {
447
    PreparedStatement pstmt = null;
448
 
449
    try {
450
      pstmt = conn.prepareStatement(
451
              "INSERT INTO xml_access " + 
452
              "(docid, principal_name, permission, perm_type, perm_order," +
453
              "begin_time,end_time,ticket_count, accessfileid) VALUES " +
454
              "(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?,?)");
455
      // Bind the values to the query
456
      pstmt.setInt(3, permission);
457
      pstmt.setString(4, permType);
458
      pstmt.setString(5, permOrder);
459
      pstmt.setString(6, beginTime);
460
      pstmt.setString(7, endTime);
461
      pstmt.setString(9, aclid);
462
      if ( ticketCount > 0 ) {
463
        pstmt.setString(8, "" + ticketCount);
464
      } else {
465
        pstmt.setString(8, "");
466
      }
467
      for ( int i = 0; i < resourceID.size(); i++ ) {
468
        pstmt.setString(1, (String)resourceID.elementAt(i));
469
        for ( int j = 0; j < principal.size(); j++ ) {
470
          pstmt.setString(2, (String)principal.elementAt(j));
471
          pstmt.execute();
472
        }
473
      }
474

    
475
    } catch (SQLException e) {
476
      throw new 
477
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
478
    } finally {
479
      pstmt.close();
480
    }
481
  }
482

    
483
  /** Check for @permission for @principal on @resourceID from db connection */
484
  public boolean hasPermission ( String permission,
485
                                 String principal, String resourceID )
486
                 throws SQLException
487
  {
488
    PreparedStatement pstmt;
489
    // check public access to @resourceID from xml_documents table
490
    if ( permission.equals("READ") ) {
491
      try {
492
        pstmt = conn.prepareStatement(
493
                "SELECT 'x' FROM xml_documents " +
494
                "WHERE docid LIKE ? AND public_access = 1");
495
        // Bind the values to the query
496
        pstmt.setString(1, resourceID);
497

    
498
        pstmt.execute();
499
        ResultSet rs = pstmt.getResultSet();
500
        boolean hasRow = rs.next();
501
        pstmt.close();
502
        if (hasRow) {
503
          return true;
504
        }
505
//System.out.println("Passed the check for public access");      
506

    
507
      } catch (SQLException e) {
508
        throw new 
509
        SQLException("Error checking document's public access: "
510
                      + e.getMessage());
511
      }
512
    }
513
    
514
    // since owner of resource has all permission on it,
515
    // check if @principal is owner of @resourceID in xml_documents table
516
    if ( principal != null ) {
517
      try {
518
        pstmt = conn.prepareStatement(
519
                "SELECT 'x' FROM xml_documents " +
520
                "WHERE docid LIKE ? AND user_owner LIKE ?");
521
        // Bind the values to the query
522
        pstmt.setString(1, resourceID);
523
        pstmt.setString(2, principal);
524

    
525
        pstmt.execute();
526
        ResultSet rs = pstmt.getResultSet();
527
        boolean hasRow = rs.next();
528
        pstmt.close();
529
        if (hasRow) {
530
          return true;
531
        }
532
//System.out.println("Passed the check for ownership");      
533
     
534
      } catch (SQLException e) {
535
        throw new 
536
        SQLException("AccessControlList.hasPermission(): " +
537
                     "Error checking document's ownership. " + e.getMessage());
538
      }
539

    
540
      // check @principal's @permission on @resourceID from xml_access table
541
      int accessValue = 0;
542
      int ticketCount = 0;
543
      String permOrder = "";
544
      try {
545
        pstmt = conn.prepareStatement(
546
                "SELECT permission, perm_order, ticket_count " +
547
                "FROM xml_access " +
548
                "WHERE docid LIKE ? " + 
549
                "AND principal_name LIKE ? " +
550
                "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
551
                                "AND nvl(end_time,sysdate) " +
552
                "AND perm_type LIKE ?");
553
        // check if it is "denied" first
554
        // Bind the values to the query
555
        pstmt.setString(1, resourceID);
556
        pstmt.setString(2, principal);
557
        pstmt.setString(3, "denied");
558

    
559
        pstmt.execute();
560
        ResultSet rs = pstmt.getResultSet();
561
        boolean hasRows = rs.next();
562
        while ( hasRows ) {
563
          accessValue = rs.getInt(1);
564
          permOrder = rs.getString(2);
565
          ticketCount = rs.getInt(3);
566
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
567
               ( permOrder.equals("allowFirst") ) &&
568
               ( rs.wasNull() || ticketCount > 0 ) ) {
569
            if ( !rs.wasNull() && ticketCount > 0 ) {
570
              decreaseNumberOfAccess(accessValue,principal,resourceID,"denied");
571
            }
572
            pstmt.close();
573
            return false;
574
          }
575
          hasRows = rs.next();
576
        }
577
//System.out.println("Passed the check for denied access");      
578

    
579
        // it is not denied then check if it is "allowed"
580
        // Bind the values to the query
581
        pstmt.setString(1, resourceID);
582
        pstmt.setString(2, principal);
583
        pstmt.setString(3, "allowed");
584

    
585
        pstmt.execute();
586
        rs = pstmt.getResultSet();
587
        hasRows = rs.next();
588
        while ( hasRows ) {
589
          accessValue = rs.getInt(1);
590
          ticketCount = rs.getInt(3);
591
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
592
               ( rs.wasNull() || ticketCount > 0 ) ) {
593
            if ( !rs.wasNull() && ticketCount > 0 ) {
594
              decreaseNumberOfAccess(accessValue,principal,resourceID,"allowed");
595
            }
596
            pstmt.close();
597
            return true;
598
          }
599
          hasRows = rs.next();
600
        }
601
//System.out.println("Passed the check for allowed access");      
602

    
603
        // ???
604
        // here there could be a check for the group's permission like 
605
        // selfrecursive call to hasPermission(conn,permission,group,recourceId)
606
        //
607
      
608
        pstmt.close();
609
        return false;
610
  
611
      } catch (SQLException e) {
612
        throw new 
613
        SQLException("AccessControlList.hasPermission(): " +
614
                     "Error checking document's permission. " + e.getMessage());
615
      }
616
    }
617
    
618
    return false;
619
  }
620

    
621
  /** decrease the number of access to @resourceID for @principal */
622
  private void decreaseNumberOfAccess(int permission, String principal,
623
                                      String resourceID, String permType)
624
               throws SQLException
625
  {
626
    PreparedStatement pstmt;
627
    pstmt = conn.prepareStatement(
628
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
629
            "WHERE docid LIKE ? " +
630
            "AND principal_name LIKE ? " +
631
            "AND permission LIKE ? " +
632
            "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
633
                            "AND nvl(end_time,sysdate) " +
634
            "AND perm_type LIKE ?");
635
    // Bind the values to the query
636
    pstmt.setString(1, resourceID);
637
    pstmt.setString(2, principal);
638
    pstmt.setInt(3, permission);
639
    pstmt.setString(4, permType);
640

    
641
    pstmt.execute();
642
    pstmt.close();
643
  }
644
 
645
  // get the int value of READ, WRITE or ALL
646
  private int intValue ( String permission )
647
  {
648
    if ( permission.equals("READ") ) {
649
      return READ;
650
    } else if ( permission.equals("WRITE") ) {
651
      return WRITE;
652
    } else if ( permission.equals("ALL") ) {
653
      return ALL;
654
    }
655
    
656
    return -1;
657
  }
658
  
659
  // get docid from @url
660
  private String getDocid ( String url ) throws SAXException
661
  {
662
    MetaCatUtil util = new MetaCatUtil();
663
    try {
664
      URL urlobj = new URL(url);
665
      Hashtable urlParams = util.parseQuery(urlobj.getQuery());
666
      if ( urlParams.containsKey("docid") ) {
667
        return (String)urlParams.get("docid"); // return the docid value
668
      } else {
669
        throw new 
670
        SAXException("\"docid\" not specified within " + url);
671
      }
672
    } catch (MalformedURLException e) {
673
      throw new SAXException(e.getMessage());
674
    }
675
  }  
676

    
677
}
(1-1/43)