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: 2000-12-12 12:50:19 -0800 (Tue, 12 Dec 2000) $'
12
 * '$Revision: 598 $'
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

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

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

    
40
  static final int ALL = 1;
41
  static final int WRITE = 2;
42
  static final int READ = 4;
43

    
44
  private Connection conn;
45
  private String parserName;
46
  private Stack elementStack;
47

    
48
  private boolean	processingDTD;
49
  private String 	docname;
50
  private String 	doctype;
51
  private String 	systemid;
52

    
53
  private String resourceId;
54
  private Vector principalName;
55
  private int    permission;
56
  private String permType;
57
  private String permOrder;
58
  private String beginTime;
59
  private String endTime;
60
  private int    ticketCount;
61

    
62
  /**
63
   * Construct an instance of the AccessControlList class.
64
   * It is used by the permission check up from DBQuery and DocumentImpl
65
   *
66
   * @param conn the JDBC connection where acl data are loaded
67
   */
68
  public AccessControlList ( Connection conn )
69
  {
70
    this.conn = conn;
71
  }
72

    
73
  /**
74
   * Construct an instance of the AccessControlList class.
75
   * It parse acl file and loads acl data into db connection.
76
   *
77
   * @param conn the JDBC connection where acl data are loaded
78
   * @param docid the Accession# of the document with the acl data
79
   * @param acl the acl file containing acl data
80
   */
81
  public AccessControlList ( Connection conn, String docid, Reader acl )
82
                  throws SAXException, IOException, ClassNotFoundException 
83
  {
84
    // Get an instance of the parser
85
    MetaCatUtil util = new MetaCatUtil();
86
    String parserName = util.getOption("saxparser");
87

    
88
    this.conn = conn;
89
    this.parserName = parserName;
90
    this.processingDTD = false;
91
    this.elementStack = new Stack();
92
    
93
    this.principalName = new Vector();
94
    this.permission = 0;
95
    this.ticketCount = 0;
96

    
97
    // Initialize the parser and read the queryspec
98
    XMLReader parser = initializeParser();
99
    parser.parse(new InputSource(acl));
100

    
101
  }
102

    
103
  /**
104
   * Construct an instance of the AccessControlList class.
105
   * It parse acl file and loads acl data into db connection.
106
   *
107
   * @param conn the JDBC connection where acl data are loaded
108
   * @param docid the Accession# of the document with the acl data
109
   * @param aclfilename the name of acl file containing acl data
110
   */
111
  public AccessControlList( Connection conn, String docid, String aclfilename )
112
                  throws SAXException, IOException, ClassNotFoundException 
113
  {
114
    this(conn,docid,new FileReader(new File(aclfilename).toString()));
115
  }
116

    
117
  /**
118
   * Set up the SAX parser for reading the XML serialized ACL
119
   */
120
  private XMLReader initializeParser() throws SAXException 
121
  {
122
    XMLReader parser = null;
123

    
124
    // Get an instance of the parser
125
    parser = XMLReaderFactory.createXMLReader(parserName);
126

    
127
    // Turn off validation
128
    parser.setFeature("http://xml.org/sax/features/validation", false);
129
      
130
    // Set Handlers in the parser
131
    // Set the ContentHandler to this instance
132
    parser.setContentHandler((ContentHandler)this);
133

    
134
    // make a DBEntityResolver instance
135
    // Set the EntityReslover to DBEntityResolver instance
136
    EntityResolver eresolver = new DBEntityResolver(conn,this,null);
137
    parser.setEntityResolver((EntityResolver)eresolver);
138

    
139
    // Set the ErrorHandler to this instance
140
    parser.setErrorHandler((ErrorHandler)this);
141

    
142
    return parser;
143
  }
144
  
145
  /**
146
   * callback method used by the SAX Parser when the start tag of an 
147
   * element is detected. Used in this context to parse and store
148
   * the acl information in class variables.
149
   */
150
  public void startElement (String uri, String localName, 
151
                            String qName, Attributes atts) 
152
         throws SAXException 
153
  {
154
    BasicNode currentNode = new BasicNode(localName);
155
    if (atts != null) {
156
      int len = atts.getLength();
157
      for (int i = 0; i < len; i++) {
158
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
159
      }
160
    }
161
    if ( currentNode.getTagName().equals("resource") ) {
162
      permOrder = currentNode.getAttribute("order");
163
    }
164
    elementStack.push(currentNode); 
165
  }
166

    
167
  /**
168
   * callback method used by the SAX Parser when the text sequences of an 
169
   * xml stream are detected. Used in this context to parse and store
170
   * the acl information in class variables.
171
   */
172
  public void characters(char ch[], int start, int length)
173
         throws SAXException 
174
  {
175
    String inputString = new String(ch, start, length);
176
    BasicNode currentNode = (BasicNode)elementStack.peek(); 
177
    String currentTag = currentNode.getTagName();
178

    
179
    if (currentTag.equals("resourceIdentifier")) {
180
      resourceId = inputString;
181
    } else if (currentTag.equals("principalName")) {
182
      principalName.addElement(new String(inputString));
183
    } else if (currentTag.equals("permission")) {
184
      if ( inputString.trim().toUpperCase().equals("READ") ) {
185
        permission = permission | READ;
186
      } else if ( inputString.trim().toUpperCase().equals("WRITE") ) {
187
        permission = permission | WRITE;
188
      } else if ( inputString.trim().toUpperCase().equals("ALL") ) {
189
        permission = permission | ALL;
190
      }
191
    } else if (currentTag.equals("duration") && 
192
               beginTime == null && endTime == null ) {
193
      try {
194
        beginTime = inputString.substring(0, inputString.indexOf(" "));
195
        endTime = inputString.substring(inputString.indexOf(" ")+1);
196
      } catch (StringIndexOutOfBoundsException se) {
197
        beginTime = inputString;
198
      }
199
    } else if (currentTag.equals("ticketCount") && ticketCount == 0 ) {
200
      ticketCount = (new Integer(inputString.trim())).intValue();
201
    }
202
  }
203

    
204
  /**
205
   * callback method used by the SAX Parser when the end tag of an 
206
   * element is detected. Used in this context to parse and store
207
   * the acl information in class variables.
208
   */
209
  public void endElement (String uri, String localName, String qName)
210
         throws SAXException 
211
  {
212
    BasicNode leaving = (BasicNode)elementStack.pop(); 
213
    if ( leaving.getTagName().equals("allow") ) {
214
      
215
      if ( permission > 0 ) {
216

    
217
        // insert into db calculated permission for the list of principals
218
        try {
219
          insertPermissions("allowed");
220
        } catch (SQLException sqle) {
221
          throw new SAXException(sqle);
222
        }
223
      }
224

    
225
      // reset the allowed permission
226
      principalName = new Vector();
227
      permission = 0;
228
      beginTime = null;
229
      endTime = null;
230
      ticketCount = 0;
231
    
232
    } else if ( leaving.getTagName().equals("deny") ) {
233
      
234
      if ( permission > 0 ) {
235

    
236
        // insert into db calculated permission for the list of principals
237
        try {
238
          insertPermissions("denied");
239
        } catch (SQLException sqle) {
240
          throw new SAXException(sqle);
241
        }
242
      }
243

    
244
      // reset the denied permission
245
      principalName = new Vector();
246
      permission = 0;
247
      beginTime = null;
248
      endTime = null;
249
      ticketCount = 0;
250

    
251
    } else if ( leaving.getTagName().equals("resource") ) {
252
      // reset the resource identifier
253
      resourceId = null;
254
      permOrder = null;
255
    }
256

    
257
  }
258

    
259
  /** SAX Handler that receives notification of DOCTYPE. Sets the DTD */
260
  public void startDTD(String name, String publicId, String systemId) 
261
              throws SAXException {
262
    docname = name;
263
    doctype = publicId;
264
    systemid = systemId;
265
//    processingDTD = true;
266
  }
267

    
268
  /** 
269
   * SAX Handler that receives notification of the start of entities
270
   */
271
  public void startEntity(String name) throws SAXException {
272
    if (name.equals("[dtd]")) {
273
      processingDTD = true;
274
    }
275
  }
276

    
277
  /** 
278
   * SAX Handler that receives notification of the end of entities
279
   */
280
  public void endEntity(String name) throws SAXException {
281
    if (name.equals("[dtd]")) {
282
      processingDTD = false;
283
    }
284
  }
285

    
286
  /**
287
   * get the document name
288
   */
289
  public String getDocname() {
290
    return docname;
291
  }
292

    
293
  /**
294
   * get the document processing state
295
   */
296
  public boolean processingDTD() {
297
    return processingDTD;
298
  }
299
  
300
  /** Insert into db calculated permission for the list of principals */
301
  private void insertPermissions( String permType ) 
302
          throws SQLException 
303
  {
304
    PreparedStatement pstmt;
305
    // 
306
    try {
307
      pstmt = conn.prepareStatement(
308
              "INSERT INTO xml_access " + 
309
              "(docid,principal_name,permission,perm_type,perm_order," +
310
              "begin_time,end_time,ticket_count) VALUES " +
311
              "(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?)");
312
      // Bind the values to the query
313
      pstmt.setString(1, resourceId);
314
      pstmt.setInt(3, permission);
315
      pstmt.setString(4, permType);
316
      pstmt.setString(5, permOrder);
317
      pstmt.setString(6, beginTime);
318
      pstmt.setString(7, endTime);
319
      if ( ticketCount > 0 ) {
320
        pstmt.setString(8, "" + ticketCount);
321
      } else {
322
        pstmt.setString(8, "");
323
      }
324
      for ( int i = 0; i < principalName.size(); i++ ) {
325
        pstmt.setString(2, (String)principalName.elementAt(i));
326
        pstmt.execute();
327
      }
328

    
329
    } catch (SQLException e) {
330
      throw new 
331
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
332
    }
333
  }
334

    
335
  /** Check for @permission for @principal on @resourceId from db connection */
336
  public boolean hasPermission ( String permission, String principal,
337
                                 String resourceId )
338
                 throws SQLException
339
  {
340
    PreparedStatement pstmt;
341
    // check public access to @resourceId from xml_documents table
342
    if ( permission.equals("READ") ) {
343
      try {
344
        pstmt = conn.prepareStatement(
345
                "SELECT 'x' FROM xml_documents " +
346
                "WHERE docid LIKE ? AND public_access = 1");
347
        // Bind the values to the query
348
        pstmt.setString(1, principal);
349

    
350
        pstmt.execute();
351
        ResultSet rs = pstmt.getResultSet();
352
        boolean hasRow = rs.next();
353
        pstmt.close();
354
        if (hasRow) {
355
          return true;
356
        }
357
//System.out.println("Passed the check for public access");      
358

    
359
      } catch (SQLException e) {
360
        throw new 
361
        SQLException("Error checking document's public access: "
362
                      + e.getMessage());
363
      }
364
    }
365
    
366
    // since owner of resource has all permission on it,
367
    // check if @principal is owner of @resourceId in xml_documents table
368
    if ( principal != null ) {
369
      try {
370
        pstmt = conn.prepareStatement(
371
                "SELECT 'x' FROM xml_documents " +
372
                "WHERE docid LIKE ? AND user_owner LIKE ?");
373
        // Bind the values to the query
374
        pstmt.setString(1, resourceId);
375
        pstmt.setString(2, principal);
376

    
377
        pstmt.execute();
378
        ResultSet rs = pstmt.getResultSet();
379
        boolean hasRow = rs.next();
380
        pstmt.close();
381
        if (hasRow) {
382
          return true;
383
        }
384
//System.out.println("Passed the check for ownership");      
385
     
386
      } catch (SQLException e) {
387
        throw new 
388
        SQLException("AccessControlList.hasPermission(): " +
389
                     "Error checking document's ownership. " + e.getMessage());
390
      }
391

    
392
      // check @principal's @permission on @resourceId from xml_access table
393
      int accessValue = 0;
394
      int ticketCount = 0;
395
      String permOrder = "";
396
      try {
397
        pstmt = conn.prepareStatement(
398
                "SELECT permission, perm_order, ticket_count " +
399
                "FROM xml_access " +
400
                "WHERE docid LIKE ? " + 
401
                "AND principal_name LIKE ? " +
402
                "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
403
                                "AND nvl(end_time,sysdate) " +
404
                "AND perm_type LIKE ?");
405
        // check if it is "denied" first
406
        // Bind the values to the query
407
        pstmt.setString(1, resourceId);
408
        pstmt.setString(2, principal);
409
        pstmt.setString(3, "denied");
410

    
411
        pstmt.execute();
412
        ResultSet rs = pstmt.getResultSet();
413
        boolean hasRows = rs.next();
414
        while ( hasRows ) {
415
          accessValue = rs.getInt(1);
416
          permOrder = rs.getString(2);
417
          ticketCount = rs.getInt(3);
418
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
419
               ( permOrder.equals("allow first") ) &&
420
               ( rs.wasNull() || ticketCount > 0 ) ) {
421
            if ( !rs.wasNull() && ticketCount > 0 ) {
422
              decreaseNumberOfAccess(accessValue,principal,resourceId,"denied");
423
            }
424
            pstmt.close();
425
            return false;
426
          }
427
          hasRows = rs.next();
428
        }
429
//System.out.println("Passed the check for denied access");      
430

    
431
        // it is not denied then check if it is "allowed"
432
        // Bind the values to the query
433
        pstmt.setString(1, resourceId);
434
        pstmt.setString(2, principal);
435
        pstmt.setString(3, "allowed");
436

    
437
        pstmt.execute();
438
        rs = pstmt.getResultSet();
439
        hasRows = rs.next();
440
        while ( hasRows ) {
441
          accessValue = rs.getInt(1);
442
          ticketCount = rs.getInt(3);
443
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
444
               ( rs.wasNull() || ticketCount > 0 ) ) {
445
            if ( !rs.wasNull() && ticketCount > 0 ) {
446
              decreaseNumberOfAccess(accessValue,principal,resourceId,"allowed");
447
            }
448
            pstmt.close();
449
            return true;
450
          }
451
          hasRows = rs.next();
452
        }
453
//System.out.println("Passed the check for allowed access");      
454

    
455
        // ???
456
        // here there could be a check for the group's permission like 
457
        // selfrecursive call to hasPermission(conn,permission,group,recourceId)
458
        //
459
      
460
        pstmt.close();
461
        return false;
462
  
463
      } catch (SQLException e) {
464
        throw new 
465
        SQLException("AccessControlList.hasPermission(): " +
466
                     "Error checking document's permission. " + e.getMessage());
467
      }
468
    }
469
    
470
    return false;
471
  }
472

    
473
  /** decrease the number of access to @resourceId for @principal */
474
  private void decreaseNumberOfAccess(int permission, String principal,
475
                                      String resourceId, String permType)
476
               throws SQLException
477
  {
478
    PreparedStatement pstmt;
479
    pstmt = conn.prepareStatement(
480
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
481
            "WHERE docid LIKE ? " +
482
            "AND principal_name LIKE ? " +
483
            "AND permission LIKE ? " +
484
            "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
485
                            "AND nvl(end_time,sysdate) " +
486
            "AND perm_type LIKE ?");
487
    // Bind the values to the query
488
    pstmt.setString(1, resourceId);
489
    pstmt.setString(2, principal);
490
    pstmt.setInt(3, permission);
491
    pstmt.setString(4, permType);
492

    
493
    pstmt.execute();
494
    pstmt.close();
495
  }
496
 
497
  // get the int value of READ, WRITE or ALL
498
  private int intValue ( String permission )
499
  {
500
    if ( permission.equals("READ") ) {
501
      return READ;
502
    } else if ( permission.equals("WRITE") ) {
503
      return WRITE;
504
    } else if ( permission.equals("ALL") ) {
505
      return ALL;
506
    }
507
    
508
    return -1;
509
  }
510
}
(1-1/38)