Project

General

Profile

1 555 bojilova
/**
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$'
11
 *     '$Date$'
12
 * '$Revision$'
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.SAXException;
25
import org.xml.sax.SAXParseException;
26
import org.xml.sax.XMLReader;
27
import org.xml.sax.helpers.XMLReaderFactory;
28
import org.xml.sax.helpers.DefaultHandler;
29
30
/**
31
 * A Class that loads eml-access.xml file containing ACL for a metadata
32
 * document into relational DB. It extends DefaultHandler class to handle
33
 * SAX parsing events when processing the XML stream.
34
 */
35
public class AccessControlList extends DefaultHandler {
36
37
  static final int ALL = 1;
38
  static final int WRITE = 2;
39
  static final int READ = 4;
40
41 570 bojilova
  private Connection conn;
42 555 bojilova
  private String parserName;
43
  private Stack elementStack;
44
45 570 bojilova
  private String resourceId;
46
  private Vector principalName;
47
  private int    permission;
48
  private String permType;
49
  private String permOrder;
50
  private String beginTime;
51
  private String endTime;
52
  private int    ticketCount;
53 555 bojilova
54
  /**
55
   * Construct an instance of the AccessControlList class.
56 570 bojilova
   * It is used by the permission check up from DBQuery and DocumentImpl
57
   *
58
   * @param conn the JDBC connection where acl data are loaded
59
   */
60
  public AccessControlList ( Connection conn )
61
  {
62
    this.conn = conn;
63
  }
64
65
  /**
66
   * Construct an instance of the AccessControlList class.
67 555 bojilova
   * It parse acl file and loads acl data into db connection.
68
   *
69
   * @param conn the JDBC connection where acl data are loaded
70
   * @param docid the Accession# of the document with the acl data
71 570 bojilova
   * @param acl the acl file containing acl data
72 555 bojilova
   */
73
  public AccessControlList ( Connection conn, String docid, Reader acl )
74
                  throws SAXException, IOException, ClassNotFoundException
75
  {
76
    // Get an instance of the parser
77
    MetaCatUtil util = new MetaCatUtil();
78
    String parserName = util.getOption("saxparser");
79
80
    this.conn = conn;
81
    this.parserName = parserName;
82 570 bojilova
    this.elementStack = new Stack();
83
84
    this.principalName = new Vector();
85
    this.permission = 0;
86
    this.ticketCount = 0;
87 555 bojilova
88
    // Initialize the parser and read the queryspec
89
    XMLReader parser = initializeParser();
90
    parser.parse(new InputSource(acl));
91
92
  }
93
94 570 bojilova
  /**
95
   * Construct an instance of the AccessControlList class.
96
   * It parse acl file and loads acl data into db connection.
97
   *
98
   * @param conn the JDBC connection where acl data are loaded
99
   * @param docid the Accession# of the document with the acl data
100
   * @param aclfilename the name of acl file containing acl data
101
   */
102 555 bojilova
  public AccessControlList( Connection conn, String docid, String aclfilename )
103
                  throws SAXException, IOException, ClassNotFoundException
104
  {
105
    this(conn,docid,new FileReader(new File(aclfilename).toString()));
106
  }
107
108
  /**
109
   * Set up the SAX parser for reading the XML serialized ACL
110
   */
111
  private XMLReader initializeParser() throws SAXException
112
  {
113
    XMLReader parser = null;
114
115
    // Get an instance of the parser
116
    parser = XMLReaderFactory.createXMLReader(parserName);
117
118
    // Set the ContentHandler to this instance
119
    parser.setContentHandler(this);
120
121
    // Set the ErrorHandler to this instance
122
    parser.setErrorHandler(this);
123
124
    return parser;
125
  }
126
127
  /**
128
   * callback method used by the SAX Parser when the start tag of an
129
   * element is detected. Used in this context to parse and store
130
   * the acl information in class variables.
131
   */
132
  public void startElement (String uri, String localName,
133
                            String qName, Attributes atts)
134
         throws SAXException
135
  {
136
    BasicNode currentNode = new BasicNode(localName);
137
    if (atts != null) {
138
      int len = atts.getLength();
139
      for (int i = 0; i < len; i++) {
140
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
141
      }
142
    }
143 570 bojilova
    if ( currentNode.getTagName().equals("resource") ) {
144
      permOrder = currentNode.getAttribute("order");
145
    }
146 555 bojilova
    elementStack.push(currentNode);
147
  }
148
149
  /**
150
   * callback method used by the SAX Parser when the text sequences of an
151
   * xml stream are detected. Used in this context to parse and store
152
   * the acl information in class variables.
153
   */
154
  public void characters(char ch[], int start, int length)
155
         throws SAXException
156
  {
157
    String inputString = new String(ch, start, length);
158
    BasicNode currentNode = (BasicNode)elementStack.peek();
159
    String currentTag = currentNode.getTagName();
160
161 570 bojilova
    if (currentTag.equals("resourceIdentifier")) {
162
      resourceId = inputString;
163
    } else if (currentTag.equals("principalName")) {
164
      principalName.addElement(new String(inputString));
165 555 bojilova
    } else if (currentTag.equals("permission")) {
166
      if ( inputString.trim().toUpperCase().equals("READ") ) {
167
        permission = permission | READ;
168
      } else if ( inputString.trim().toUpperCase().equals("WRITE") ) {
169
        permission = permission | WRITE;
170
      } else if ( inputString.trim().toUpperCase().equals("ALL") ) {
171
        permission = permission | ALL;
172
      }
173
    } else if (currentTag.equals("duration") &&
174 570 bojilova
               beginTime == null && endTime == null ) {
175 555 bojilova
      try {
176 570 bojilova
        beginTime = inputString.substring(0, inputString.indexOf(" "));
177
        endTime = inputString.substring(inputString.indexOf(" ")+1);
178 555 bojilova
      } catch (StringIndexOutOfBoundsException se) {
179 570 bojilova
        beginTime = inputString;
180 555 bojilova
      }
181 570 bojilova
    } else if (currentTag.equals("ticketCount") && ticketCount == 0 ) {
182
      ticketCount = (new Integer(inputString.trim())).intValue();
183 555 bojilova
    }
184
  }
185
186
  /**
187
   * callback method used by the SAX Parser when the end tag of an
188
   * element is detected. Used in this context to parse and store
189
   * the acl information in class variables.
190
   */
191
  public void endElement (String uri, String localName, String qName)
192
         throws SAXException
193
  {
194
    BasicNode leaving = (BasicNode)elementStack.pop();
195 570 bojilova
    if ( leaving.getTagName().equals("allow") ) {
196
197
      if ( permission > 0 ) {
198 555 bojilova
199 570 bojilova
        // insert into db calculated permission for the list of principals
200
        try {
201
          insertPermissions("allowed");
202
        } catch (SQLException sqle) {
203
          throw new SAXException(sqle);
204
        }
205
      }
206
207 555 bojilova
      // reset the allowed permission
208 570 bojilova
      principalName = new Vector();
209 555 bojilova
      permission = 0;
210 570 bojilova
      beginTime = null;
211
      endTime = null;
212
      ticketCount = 0;
213 555 bojilova
214 570 bojilova
    } else if ( leaving.getTagName().equals("deny") ) {
215
216
      if ( permission > 0 ) {
217 555 bojilova
218 570 bojilova
        // insert into db calculated permission for the list of principals
219
        try {
220
          insertPermissions("denied");
221
        } catch (SQLException sqle) {
222
          throw new SAXException(sqle);
223 555 bojilova
        }
224
      }
225
226 570 bojilova
      // reset the denied permission
227
      principalName = new Vector();
228
      permission = 0;
229
      beginTime = null;
230
      endTime = null;
231
      ticketCount = 0;
232 555 bojilova
233 570 bojilova
    } else if ( leaving.getTagName().equals("resource") ) {
234
      // reset the resource identifier
235
      resourceId = null;
236
      permOrder = null;
237
    }
238 555 bojilova
239
  }
240
241 570 bojilova
  /** Insert into db calculated permission for the list of principals */
242
  private void insertPermissions( String permType )
243 555 bojilova
          throws SQLException
244
  {
245
    PreparedStatement pstmt;
246
    //
247
    try {
248
      pstmt = conn.prepareStatement(
249
              "INSERT INTO xml_access " +
250 570 bojilova
              "(docid,principal_name,permission,perm_type,perm_order," +
251
              "begin_time,end_time,ticket_count) VALUES " +
252
              "(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?)");
253 555 bojilova
      // Bind the values to the query
254 570 bojilova
      pstmt.setString(1, resourceId);
255
      pstmt.setInt(3, permission);
256
      pstmt.setString(4, permType);
257
      pstmt.setString(5, permOrder);
258
      pstmt.setString(6, beginTime);
259
      pstmt.setString(7, endTime);
260
      if ( ticketCount > 0 ) {
261
        pstmt.setString(8, "" + ticketCount);
262
      } else {
263 555 bojilova
        pstmt.setString(8, "");
264 570 bojilova
      }
265
      for ( int i = 0; i < principalName.size(); i++ ) {
266
        pstmt.setString(2, (String)principalName.elementAt(i));
267
        pstmt.execute();
268
      }
269 555 bojilova
270 570 bojilova
    } catch (SQLException e) {
271
      throw new
272
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
273
    }
274
  }
275
276
  /** Check for @permission for @principal on @resourceId from db connection */
277
  public boolean hasPermission ( String permission, String principal,
278
                                 String resourceId )
279
                 throws SQLException
280
  {
281
    PreparedStatement pstmt;
282
    // check public access to @resourceId from xml_documents table
283
    if ( permission.equals("READ") ) {
284
      try {
285
        pstmt = conn.prepareStatement(
286
                "SELECT 'x' FROM xml_documents " +
287
                "WHERE docid LIKE ? AND public_access = 1");
288
        // Bind the values to the query
289
        pstmt.setString(1, principal);
290
291 555 bojilova
        pstmt.execute();
292 570 bojilova
        ResultSet rs = pstmt.getResultSet();
293
        boolean hasRow = rs.next();
294
        pstmt.close();
295
        if (hasRow) {
296
          return true;
297
        }
298
//System.out.println("Passed the check for public access");
299
300
      } catch (SQLException e) {
301
        throw new
302
        SQLException("Error checking document's public access: "
303
                      + e.getMessage());
304 555 bojilova
      }
305 570 bojilova
    }
306
307
    // since owner of resource has all permission on it,
308
    // check if @principal is owner of @resourceId in xml_documents table
309
    if ( principal != null ) {
310
      try {
311
        pstmt = conn.prepareStatement(
312
                "SELECT 'x' FROM xml_documents " +
313
                "WHERE docid LIKE ? AND user_owner LIKE ?");
314
        // Bind the values to the query
315
        pstmt.setString(1, resourceId);
316
        pstmt.setString(2, principal);
317 555 bojilova
318 570 bojilova
        pstmt.execute();
319
        ResultSet rs = pstmt.getResultSet();
320
        boolean hasRow = rs.next();
321
        pstmt.close();
322
        if (hasRow) {
323
          return true;
324 555 bojilova
        }
325 570 bojilova
//System.out.println("Passed the check for ownership");
326
327
      } catch (SQLException e) {
328
        throw new
329
        SQLException("AccessControlList.hasPermission(): " +
330
                     "Error checking document's ownership. " + e.getMessage());
331
      }
332
333
      // check @principal's @permission on @resourceId from xml_access table
334
      int accessValue = 0;
335
      int ticketCount = 0;
336
      String permOrder = "";
337
      try {
338
        pstmt = conn.prepareStatement(
339
                "SELECT permission, perm_order, ticket_count " +
340
                "FROM xml_access " +
341
                "WHERE docid LIKE ? " +
342
                "AND principal_name LIKE ? " +
343
                "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
344
                                "AND nvl(end_time,sysdate) " +
345
                "AND perm_type LIKE ?");
346
        // check if it is "denied" first
347
        // Bind the values to the query
348
        pstmt.setString(1, resourceId);
349
        pstmt.setString(2, principal);
350
        pstmt.setString(3, "denied");
351
352
        pstmt.execute();
353
        ResultSet rs = pstmt.getResultSet();
354
        boolean hasRows = rs.next();
355
        while ( hasRows ) {
356
          accessValue = rs.getInt(1);
357
          permOrder = rs.getString(2);
358
          ticketCount = rs.getInt(3);
359
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
360
               ( permOrder.equals("allow first") ) &&
361
               ( rs.wasNull() || ticketCount > 0 ) ) {
362
            if ( !rs.wasNull() && ticketCount > 0 ) {
363
              decreaseNumberOfAccess(accessValue,principal,resourceId,"denied");
364
            }
365
            pstmt.close();
366
            return false;
367
          }
368
          hasRows = rs.next();
369 555 bojilova
        }
370 570 bojilova
//System.out.println("Passed the check for denied access");
371 555 bojilova
372 570 bojilova
        // it is not denied then check if it is "allowed"
373
        // Bind the values to the query
374
        pstmt.setString(1, resourceId);
375
        pstmt.setString(2, principal);
376
        pstmt.setString(3, "allowed");
377
378 555 bojilova
        pstmt.execute();
379 570 bojilova
        rs = pstmt.getResultSet();
380
        hasRows = rs.next();
381
        while ( hasRows ) {
382
          accessValue = rs.getInt(1);
383
          ticketCount = rs.getInt(3);
384
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
385
               ( rs.wasNull() || ticketCount > 0 ) ) {
386
            if ( !rs.wasNull() && ticketCount > 0 ) {
387
              decreaseNumberOfAccess(accessValue,principal,resourceId,"allowed");
388
            }
389
            pstmt.close();
390
            return true;
391
          }
392
          hasRows = rs.next();
393
        }
394
//System.out.println("Passed the check for allowed access");
395
396
        // ???
397
        // here there could be a check for the group's permission like
398
        // selfrecursive call to hasPermission(conn,permission,group,recourceId)
399
        //
400
401
        pstmt.close();
402
        return false;
403
404
      } catch (SQLException e) {
405
        throw new
406
        SQLException("AccessControlList.hasPermission(): " +
407
                     "Error checking document's permission. " + e.getMessage());
408 555 bojilova
      }
409 570 bojilova
    }
410
411
    return false;
412
  }
413 555 bojilova
414 570 bojilova
  /** decrease the number of access to @resourceId for @principal */
415
  private void decreaseNumberOfAccess(int permission, String principal,
416
                                      String resourceId, String permType)
417
               throws SQLException
418
  {
419
    PreparedStatement pstmt;
420
    pstmt = conn.prepareStatement(
421
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
422
            "WHERE docid LIKE ? " +
423
            "AND principal_name LIKE ? " +
424
            "AND permission LIKE ? " +
425
            "AND sysdate BETWEEN nvl(begin_time,sysdate) " +
426
                            "AND nvl(end_time,sysdate) " +
427
            "AND perm_type LIKE ?");
428
    // Bind the values to the query
429
    pstmt.setString(1, resourceId);
430
    pstmt.setString(2, principal);
431
    pstmt.setInt(3, permission);
432
    pstmt.setString(4, permType);
433
434
    pstmt.execute();
435
    pstmt.close();
436
  }
437
438
  // get the int value of READ, WRITE or ALL
439
  private int intValue ( String permission )
440
  {
441
    if ( permission.equals("READ") ) {
442
      return READ;
443
    } else if ( permission.equals("WRITE") ) {
444
      return WRITE;
445
    } else if ( permission.equals("ALL") ) {
446
      return ALL;
447 555 bojilova
    }
448 570 bojilova
449
    return -1;
450 555 bojilova
  }
451
}