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-18 12:38:21 -0800 (Thu, 18 Jan 2001) $'
12
 * '$Revision: 672 $'
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 publicAcc;
82
  private String beginTime;
83
  private String endTime;
84
  private int    ticketCount;
85
  
86
  /**
87
   * Construct an instance of the AccessControlList class.
88
   * It is used by the permission check up from DBQuery and DocumentImpl
89
   *
90
   * @param conn the JDBC connection where acl data are loaded
91
   */
92
  public AccessControlList ( Connection conn ) throws SQLException
93
  {
94
    this.conn = conn;
95
  }
96

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

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

    
132
  }
133

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

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

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

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

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

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

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

    
230
    if (currentTag.equals("resourceIdentifier")) {
231

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

    
261
    } else if (currentTag.equals("principal")) {
262

    
263
      principal.addElement(inputString);
264

    
265
    } else if (currentTag.equals("permission")) {
266

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

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

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

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

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

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

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

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

    
353
    } else if ( leaving.getTagName().equals("resource") ) {
354

    
355
      // update public access for the list of resources
356
      try {
357
        updatePublicAccess(publicAcc);
358
      } catch (SQLException sqle) {
359
        throw new SAXException(sqle);
360
      }
361
      
362
      // reset the resource
363
      resourceID = new Vector();
364
      resourceURL = new Vector();
365
      permOrder = null;
366
      publicAcc = null;
367
    }
368

    
369
  }
370

    
371
  /** SAX Handler that receives notification of DOCTYPE. Sets the DTD */
372
  public void startDTD(String name, String publicId, String systemId) 
373
              throws SAXException {
374
    docname = name;
375
    doctype = publicId;
376
    systemid = systemId;
377
//    processingDTD = true;
378
  }
379

    
380
  /** 
381
   * SAX Handler that receives notification of the start of entities
382
   */
383
  public void startEntity(String name) throws SAXException {
384
    if (name.equals("[dtd]")) {
385
      processingDTD = true;
386
    }
387
  }
388

    
389
  /** 
390
   * SAX Handler that receives notification of the end of entities
391
   */
392
  public void endEntity(String name) throws SAXException {
393
    if (name.equals("[dtd]")) {
394
      processingDTD = false;
395
    }
396
  }
397

    
398
  /**
399
   * get the document name
400
   */
401
  public String getDocname() {
402
    return docname;
403
  }
404

    
405
  /**
406
   * get the document processing state
407
   */
408
  public boolean processingDTD() {
409
    return processingDTD;
410
  }
411
  
412
  /** Delete from db all permission for resources related to @aclid if any */
413
  private void deletePermissionsForRelatedResources(String aclid) 
414
          throws SQLException 
415
  {
416
    // delete all acl records for resources related to @aclid if any
417
    Statement stmt = conn.createStatement();
418
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'");
419
    stmt.close();
420
  }
421

    
422
  /**
423
   * Deletes all of the relations with a docid of @docid.
424
   * @param docid the docid to delete.
425
   */
426
  public void deleteRelations(String docid)
427
          throws SQLException 
428
  {
429
    Statement stmt = conn.createStatement();
430
    stmt.execute("DELETE FROM xml_relation WHERE docid='" + docid + "'");
431
    stmt.close();
432
  }
433

    
434
  /** Insert relationship into db for @aclid on @resourceURL  */
435
  private void insertRelation(String aclid, String resourceURL) 
436
          throws SQLException 
437
  {
438
    String aclURL = "metacat://" + server + "/?docid=" + aclid;
439
    
440
    // insert relationship 
441
    PreparedStatement pstmt;
442
    pstmt = conn.prepareStatement(
443
            "INSERT INTO xml_relation (docid,subject,relationship,object) " +
444
            "VALUES (?, ?, ?, ?)");
445
    pstmt.setString(1, aclid);
446
    pstmt.setString(2, aclURL);
447
    pstmt.setString(3, "isaclfor");
448
    pstmt.setString(4, resourceURL);
449

    
450
    pstmt.execute();
451
    pstmt.close();
452
  }
453

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

    
487
    } catch (SQLException e) {
488
      throw new 
489
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
490
    }
491
  }
492

    
493
  /** Update into db public read access for the list of resources */
494
  private void updatePublicAccess(String publicAcc) 
495
          throws SQLException 
496
  {
497
    try {
498
      PreparedStatement pstmt;
499
      pstmt = conn.prepareStatement(
500
              "UPDATE xml_documents SET public_access = ?" +
501
              " WHERE docid LIKE ?");
502
      // Bind the values to the query
503
      if ( publicAcc.toUpperCase().equals("YES") ) {
504
        pstmt.setInt(1, 1);
505
      } else {
506
        pstmt.setInt(1, 0);
507
      }
508
      for ( int i = 0; i < resourceID.size(); i++ ) {
509
        pstmt.setString(2, (String)resourceID.elementAt(i));
510
        pstmt.execute();
511
      }
512
      pstmt.close();
513

    
514
    } catch (SQLException e) {
515
      throw new 
516
      SQLException("AccessControlList.updatePublicAccess(): " + e.getMessage());
517
    }
518
  }
519

    
520
  /** Check for @permission for @principal on @resourceID from db connection */
521
  public boolean hasPermission ( String permission,
522
                                 String principal, String resourceID )
523
                 throws SQLException
524
  {
525
    PreparedStatement pstmt;
526
    // check public access to @resourceID from xml_documents table
527
    if ( permission.equals("READ") ) {
528
      try {
529
        pstmt = conn.prepareStatement(
530
                "SELECT 'x' FROM xml_documents " +
531
                "WHERE docid LIKE ? AND public_access = 1");
532
        // Bind the values to the query
533
        pstmt.setString(1, resourceID);
534

    
535
        pstmt.execute();
536
        ResultSet rs = pstmt.getResultSet();
537
        boolean hasRow = rs.next();
538
        pstmt.close();
539
        if (hasRow) {
540
          return true;
541
        }
542
//System.out.println("Passed the check for public access");      
543

    
544
      } catch (SQLException e) {
545
        throw new 
546
        SQLException("Error checking document's public access: "
547
                      + e.getMessage());
548
      }
549
    }
550
    
551
    // since owner of resource has all permission on it,
552
    // check if @principal is owner of @resourceID in xml_documents table
553
    if ( principal != null ) {
554
      try {
555
        pstmt = conn.prepareStatement(
556
                "SELECT 'x' FROM xml_documents " +
557
                "WHERE docid LIKE ? AND user_owner LIKE ?");
558
        // Bind the values to the query
559
        pstmt.setString(1, resourceID);
560
        pstmt.setString(2, principal);
561

    
562
        pstmt.execute();
563
        ResultSet rs = pstmt.getResultSet();
564
        boolean hasRow = rs.next();
565
        pstmt.close();
566
        if (hasRow) {
567
          return true;
568
        }
569
//System.out.println("Passed the check for ownership");      
570
     
571
      } catch (SQLException e) {
572
        throw new 
573
        SQLException("AccessControlList.hasPermission(): " +
574
                     "Error checking document's ownership. " + e.getMessage());
575
      }
576

    
577
      // check @principal's @permission on @resourceID from xml_access table
578
      int accessValue = 0;
579
      int ticketCount = 0;
580
      String permOrder = "";
581
      try {
582
        pstmt = conn.prepareStatement(
583
                "SELECT permission, perm_order, ticket_count " +
584
                "FROM xml_access " +
585
                "WHERE docid LIKE ? " + 
586
                "AND principal_name LIKE ? " +
587
                "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
588
                                "AND nvl(end_time,sysdate) " +
589
                "AND perm_type LIKE ?");
590
        // check if it is "denied" first
591
        // Bind the values to the query
592
        pstmt.setString(1, resourceID);
593
        pstmt.setString(2, principal);
594
        pstmt.setString(3, "denied");
595

    
596
        pstmt.execute();
597
        ResultSet rs = pstmt.getResultSet();
598
        boolean hasRows = rs.next();
599
        while ( hasRows ) {
600
          accessValue = rs.getInt(1);
601
          permOrder = rs.getString(2);
602
          ticketCount = rs.getInt(3);
603
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
604
               ( permOrder.equals("allowFirst") ) &&
605
               ( rs.wasNull() || ticketCount > 0 ) ) {
606
            if ( !rs.wasNull() && ticketCount > 0 ) {
607
              decreaseNumberOfAccess(accessValue,principal,resourceID,"denied");
608
            }
609
            pstmt.close();
610
            return false;
611
          }
612
          hasRows = rs.next();
613
        }
614
//System.out.println("Passed the check for denied access");      
615

    
616
        // it is not denied then check if it is "allowed"
617
        // Bind the values to the query
618
        pstmt.setString(1, resourceID);
619
        pstmt.setString(2, principal);
620
        pstmt.setString(3, "allowed");
621

    
622
        pstmt.execute();
623
        rs = pstmt.getResultSet();
624
        hasRows = rs.next();
625
        while ( hasRows ) {
626
          accessValue = rs.getInt(1);
627
          ticketCount = rs.getInt(3);
628
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
629
               ( rs.wasNull() || ticketCount > 0 ) ) {
630
            if ( !rs.wasNull() && ticketCount > 0 ) {
631
              decreaseNumberOfAccess(accessValue,principal,resourceID,"allowed");
632
            }
633
            pstmt.close();
634
            return true;
635
          }
636
          hasRows = rs.next();
637
        }
638
//System.out.println("Passed the check for allowed access");      
639

    
640
        // ???
641
        // here there could be a check for the group's permission like 
642
        // selfrecursive call to hasPermission(conn,permission,group,recourceId)
643
        //
644
      
645
        pstmt.close();
646
        return false;
647
  
648
      } catch (SQLException e) {
649
        throw new 
650
        SQLException("AccessControlList.hasPermission(): " +
651
                     "Error checking document's permission. " + e.getMessage());
652
      }
653
    }
654
    
655
    return false;
656
  }
657

    
658
  /** decrease the number of access to @resourceID for @principal */
659
  private void decreaseNumberOfAccess(int permission, String principal,
660
                                      String resourceID, String permType)
661
               throws SQLException
662
  {
663
    PreparedStatement pstmt;
664
    pstmt = conn.prepareStatement(
665
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
666
            "WHERE docid LIKE ? " +
667
            "AND principal_name LIKE ? " +
668
            "AND permission LIKE ? " +
669
            "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
670
                            "AND nvl(end_time,sysdate) " +
671
            "AND perm_type LIKE ?");
672
    // Bind the values to the query
673
    pstmt.setString(1, resourceID);
674
    pstmt.setString(2, principal);
675
    pstmt.setInt(3, permission);
676
    pstmt.setString(4, permType);
677

    
678
    pstmt.execute();
679
    pstmt.close();
680
  }
681
 
682
  // get the int value of READ, WRITE or ALL
683
  private int intValue ( String permission )
684
  {
685
    if ( permission.equals("READ") ) {
686
      return READ;
687
    } else if ( permission.equals("WRITE") ) {
688
      return WRITE;
689
    } else if ( permission.equals("ALL") ) {
690
      return ALL;
691
    }
692
    
693
    return -1;
694
  }
695
  
696
  // get docid from @url
697
  private String getDocid ( String url ) throws SAXException
698
  {
699
    MetaCatUtil util = new MetaCatUtil();
700
    try {
701
      URL urlobj = new URL(url);
702
      Hashtable urlParams = util.parseQuery(urlobj.getQuery());
703
      if ( urlParams.containsKey("docid") ) {
704
        return (String)urlParams.get("docid"); // return the docid value
705
      } else {
706
        throw new 
707
        SAXException("\"docid\" not specified within " + url);
708
      }
709
    } catch (MalformedURLException e) {
710
      throw new SAXException(e.getMessage());
711
    }
712
  }  
713

    
714
}
(1-1/43)