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 669 jones
 *
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 555 bojilova
 */
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 652 bojilova
import java.util.Hashtable;
36
import java.net.URL;
37
import java.net.MalformedURLException;
38 555 bojilova
39
import org.xml.sax.Attributes;
40
import org.xml.sax.InputSource;
41 598 bojilova
import org.xml.sax.ContentHandler;
42
import org.xml.sax.EntityResolver;
43
import org.xml.sax.ErrorHandler;
44 555 bojilova
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 774 bojilova
import edu.ucsb.nceas.dbadapter.DBAdapter;
51
52 555 bojilova
/**
53
 * A Class that loads eml-access.xml file containing ACL for a metadata
54
 * document into relational DB. It extends DefaultHandler class to handle
55
 * SAX parsing events when processing the XML stream.
56
 */
57
public class AccessControlList extends DefaultHandler {
58
59 774 bojilova
  private static final int ALL = 1;
60
  private static final int WRITE = 2;
61
  private static final int READ = 4;
62
  private static String sysdate = MetaCatUtil.dbAdapter.getDateTimeFunction();
63
  private static String isnull = MetaCatUtil.dbAdapter.getIsNULLFunction();
64 555 bojilova
65 570 bojilova
  private Connection conn;
66 555 bojilova
  private String parserName;
67
  private Stack elementStack;
68 645 bojilova
  private String server;
69 555 bojilova
70 598 bojilova
  private boolean	processingDTD;
71 645 bojilova
  private String  user;
72
  private String  group;
73 638 bojilova
  private String  aclid;
74 598 bojilova
  private String 	docname;
75
  private String 	doctype;
76
  private String 	systemid;
77
78 665 bojilova
  private String docurl;
79
  private Vector resourceURL;
80
  private Vector resourceID;
81 660 bojilova
  private Vector principal;
82 570 bojilova
  private int    permission;
83
  private String permType;
84
  private String permOrder;
85 672 bojilova
  private String publicAcc;
86 570 bojilova
  private String beginTime;
87
  private String endTime;
88
  private int    ticketCount;
89 684 bojilova
  private int    serverCode = 1;
90 638 bojilova
91 555 bojilova
  /**
92
   * Construct an instance of the AccessControlList class.
93 688 bojilova
   * It is used by the permission check up from DBQuery or DocumentImpl
94
   * and from "getaccesscontrol" action
95 570 bojilova
   *
96 684 bojilova
   * @param conn the JDBC connection where acl info is get
97 570 bojilova
   */
98 684 bojilova
  public AccessControlList(Connection conn) throws SQLException
99 570 bojilova
  {
100 613 bojilova
    this.conn = conn;
101 570 bojilova
  }
102
103
  /**
104
   * Construct an instance of the AccessControlList class.
105 555 bojilova
   * It parse acl file and loads acl data into db connection.
106
   *
107
   * @param conn the JDBC connection where acl data are loaded
108 684 bojilova
   * @param aclid the Accession# of the document with the acl data
109 570 bojilova
   * @param acl the acl file containing acl data
110 684 bojilova
   * @param user the user connected to MetaCat servlet and owns the document
111
   * @param group the group to which user belongs
112
   * @param serverCode the serverid from xml_replication on which this document
113
   *        resides.
114 555 bojilova
   */
115 645 bojilova
  public AccessControlList(Connection conn, String aclid, Reader acl,
116 684 bojilova
                           String user, String group, int serverCode)
117 555 bojilova
                  throws SAXException, IOException, ClassNotFoundException
118
  {
119
    // Get an instance of the parser
120
    MetaCatUtil util = new MetaCatUtil();
121
    String parserName = util.getOption("saxparser");
122 645 bojilova
    this.server = util.getOption("server");
123 555 bojilova
124
    this.conn = conn;
125
    this.parserName = parserName;
126 598 bojilova
    this.processingDTD = false;
127 570 bojilova
    this.elementStack = new Stack();
128
129 645 bojilova
    this.user = user;
130
    this.group = group;
131 638 bojilova
    this.aclid = aclid;
132 665 bojilova
    this.resourceURL = new Vector();
133
    this.resourceID = new Vector();
134 660 bojilova
    this.principal = new Vector();
135 570 bojilova
    this.permission = 0;
136
    this.ticketCount = 0;
137 688 bojilova
    this.publicAcc = null;
138 684 bojilova
    this.serverCode = serverCode;
139 638 bojilova
140 555 bojilova
    // Initialize the parser and read the queryspec
141
    XMLReader parser = initializeParser();
142
    parser.parse(new InputSource(acl));
143
144
  }
145
146 570 bojilova
  /**
147
   * Construct an instance of the AccessControlList class.
148 688 bojilova
   * It parses eml-access file and loads acl data into db connection.
149
   * It is used from command line execution.
150 570 bojilova
   *
151
   * @param conn the JDBC connection where acl data are loaded
152
   * @param docid the Accession# of the document with the acl data
153
   * @param aclfilename the name of acl file containing acl data
154 688 bojilova
   * @param user the user connected to MetaCat servlet and owns the document
155
   * @param group the group to which user belongs
156 570 bojilova
   */
157 645 bojilova
  public AccessControlList( Connection conn, String aclid, String aclfilename,
158
                           String user, String group )
159 555 bojilova
                  throws SAXException, IOException, ClassNotFoundException
160
  {
161 645 bojilova
    this(conn, aclid, new FileReader(new File(aclfilename).toString()),
162 684 bojilova
         user, group, 1);
163 555 bojilova
  }
164 638 bojilova
165 660 bojilova
  /* Set up the SAX parser for reading the XML serialized ACL */
166 555 bojilova
  private XMLReader initializeParser() throws SAXException
167
  {
168
    XMLReader parser = null;
169
170
    // Get an instance of the parser
171
    parser = XMLReaderFactory.createXMLReader(parserName);
172
173 598 bojilova
    // Turn off validation
174 638 bojilova
    parser.setFeature("http://xml.org/sax/features/validation", true);
175 598 bojilova
176
    // Set Handlers in the parser
177 555 bojilova
    // Set the ContentHandler to this instance
178 598 bojilova
    parser.setContentHandler((ContentHandler)this);
179 555 bojilova
180 598 bojilova
    // make a DBEntityResolver instance
181
    // Set the EntityReslover to DBEntityResolver instance
182
    EntityResolver eresolver = new DBEntityResolver(conn,this,null);
183
    parser.setEntityResolver((EntityResolver)eresolver);
184
185 555 bojilova
    // Set the ErrorHandler to this instance
186 598 bojilova
    parser.setErrorHandler((ErrorHandler)this);
187 555 bojilova
188
    return parser;
189
  }
190
191
  /**
192 688 bojilova
   * Callback method used by the SAX Parser when beginning of the document
193 645 bojilova
   */
194
  public void startDocument() throws SAXException
195
  {
196
    //delete all previously submitted permissions @ relations
197
    //this happens only on UPDATE of the access file
198
    try {
199
      if ( aclid != null ) {
200
        //first delete all permissions for resources related to @aclid if any
201
        deletePermissionsForRelatedResources(aclid);
202
        //then delete all relations with docid of @aclid if any
203
        deleteRelations(aclid);
204
      }
205
    } catch (SQLException sqle) {
206
      throw new SAXException(sqle);
207
    }
208
  }
209
210
  /**
211 688 bojilova
   * Callback method used by the SAX Parser when the start tag of an
212 555 bojilova
   * element is detected. Used in this context to parse and store
213
   * the acl information in class variables.
214
   */
215
  public void startElement (String uri, String localName,
216
                            String qName, Attributes atts)
217
         throws SAXException
218
  {
219
    BasicNode currentNode = new BasicNode(localName);
220
    if (atts != null) {
221
      int len = atts.getLength();
222
      for (int i = 0; i < len; i++) {
223
        currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i));
224
      }
225
    }
226 570 bojilova
    if ( currentNode.getTagName().equals("resource") ) {
227
      permOrder = currentNode.getAttribute("order");
228 672 bojilova
      publicAcc = currentNode.getAttribute("public");
229 570 bojilova
    }
230 555 bojilova
    elementStack.push(currentNode);
231
  }
232
233
  /**
234 688 bojilova
   * Callback method used by the SAX Parser when the text sequences of an
235 555 bojilova
   * xml stream are detected. Used in this context to parse and store
236
   * the acl information in class variables.
237
   */
238
  public void characters(char ch[], int start, int length)
239
         throws SAXException
240
  {
241
    String inputString = new String(ch, start, length);
242
    BasicNode currentNode = (BasicNode)elementStack.peek();
243
    String currentTag = currentNode.getTagName();
244
245 570 bojilova
    if (currentTag.equals("resourceIdentifier")) {
246 645 bojilova
247 665 bojilova
      // docid of the current resource
248
      String docid = getDocid(inputString);
249
      // URL string of the current resource
250
      // docurl is declared in the class
251
      try {
252
        docurl = (new URL(inputString)).toString();
253
      } catch (MalformedURLException murle) {
254
        throw new SAXException(murle.getMessage());
255
      }
256
      // collect them in Vector variables
257
      resourceID.addElement(docid);
258
      resourceURL.addElement(docurl);
259 684 bojilova
260
      // if it is the local server (originator of the document),
261
      // check for permission for @user on resource is needed
262 665 bojilova
      // @user must have permission "all" on it(docid)
263 684 bojilova
      if ( serverCode == 1 ) {
264
        boolean hasPermission = false;
265
        try {
266
          hasPermission = hasPermission("ALL",user,docid);
267
          if ( !hasPermission && group != null ) {
268
            hasPermission = hasPermission("ALL",group,docid);
269
          }
270
        } catch (SQLException e) {
271
          throw new SAXException(e.getMessage());
272 645 bojilova
        }
273 684 bojilova
        if ( !hasPermission ) {
274
          throw new SAXException(
275
          "Permission denied for setting access control on " + docid);
276
        }
277 638 bojilova
      }
278 665 bojilova
      // end of check for "all" perm on docid
279 645 bojilova
280 660 bojilova
    } else if (currentTag.equals("principal")) {
281 645 bojilova
282 665 bojilova
      principal.addElement(inputString);
283 645 bojilova
284 555 bojilova
    } else if (currentTag.equals("permission")) {
285 645 bojilova
286 555 bojilova
      if ( inputString.trim().toUpperCase().equals("READ") ) {
287
        permission = permission | READ;
288
      } else if ( inputString.trim().toUpperCase().equals("WRITE") ) {
289
        permission = permission | WRITE;
290
      } else if ( inputString.trim().toUpperCase().equals("ALL") ) {
291
        permission = permission | ALL;
292 665 bojilova
      } else {
293
        throw new SAXException("Unknown permission type: " + inputString);
294 555 bojilova
      }
295 645 bojilova
296 555 bojilova
    } else if (currentTag.equals("duration") &&
297 570 bojilova
               beginTime == null && endTime == null ) {
298 555 bojilova
      try {
299 570 bojilova
        beginTime = inputString.substring(0, inputString.indexOf(" "));
300
        endTime = inputString.substring(inputString.indexOf(" ")+1);
301 555 bojilova
      } catch (StringIndexOutOfBoundsException se) {
302 570 bojilova
        beginTime = inputString;
303 555 bojilova
      }
304 645 bojilova
305 570 bojilova
    } else if (currentTag.equals("ticketCount") && ticketCount == 0 ) {
306 665 bojilova
      try {
307
        ticketCount = (new Integer(inputString.trim())).intValue();
308
      } catch (NumberFormatException nfe) {
309
        throw new SAXException("Wrong integer format for:" + inputString);
310
      }
311 555 bojilova
    }
312
  }
313
314
  /**
315 688 bojilova
   * Callback method used by the SAX Parser when the end tag of an
316 555 bojilova
   * element is detected. Used in this context to parse and store
317
   * the acl information in class variables.
318
   */
319
  public void endElement (String uri, String localName, String qName)
320
         throws SAXException
321
  {
322 688 bojilova
    BasicNode leaving = (BasicNode)elementStack.pop();
323
    String leavingTagName = leaving.getTagName();
324
325
    if ( leavingTagName.equals("resourceIdentifier") ) {
326 570 bojilova
327 638 bojilova
      try {
328 665 bojilova
        // make a relationship for @aclid on the current resource(docurl)
329 638 bojilova
        if ( aclid != null ) {
330 665 bojilova
          insertRelation(aclid, docurl);
331 638 bojilova
        }
332
      } catch (SQLException sqle) {
333
        throw new SAXException(sqle);
334
      }
335
336 688 bojilova
    } else if ( leavingTagName.equals("allow") ||
337
                leavingTagName.equals("deny")    ) {
338 638 bojilova
339 570 bojilova
      if ( permission > 0 ) {
340 555 bojilova
341 570 bojilova
        // insert into db calculated permission for the list of principals
342
        try {
343 688 bojilova
          insertPermissions(leavingTagName);
344 570 bojilova
        } catch (SQLException sqle) {
345
          throw new SAXException(sqle);
346
        }
347
      }
348
349 688 bojilova
      // reset the allow/deny permission
350 660 bojilova
      principal = new Vector();
351 555 bojilova
      permission = 0;
352 570 bojilova
      beginTime = null;
353
      endTime = null;
354
      ticketCount = 0;
355 555 bojilova
356 688 bojilova
    } else if ( leavingTagName.equals("resource") ) {
357 555 bojilova
358 688 bojilova
      // update public access for the list of resources
359
      if ( publicAcc != null ) {
360 570 bojilova
        try {
361 688 bojilova
          updatePublicAccess(publicAcc);
362 570 bojilova
        } catch (SQLException sqle) {
363
          throw new SAXException(sqle);
364 555 bojilova
        }
365
      }
366 672 bojilova
367
      // reset the resource
368 665 bojilova
      resourceID = new Vector();
369
      resourceURL = new Vector();
370 570 bojilova
      permOrder = null;
371 672 bojilova
      publicAcc = null;
372 570 bojilova
    }
373 555 bojilova
374
  }
375 598 bojilova
376 688 bojilova
  /**
377
    * SAX Handler that receives notification of DOCTYPE. Sets the DTD.
378
    * @param name name of the DTD
379
    * @param publicId Public Identifier of the DTD
380
    * @param systemId System Identifier of the DTD
381
    */
382 598 bojilova
  public void startDTD(String name, String publicId, String systemId)
383
              throws SAXException {
384
    docname = name;
385
    doctype = publicId;
386
    systemid = systemId;
387
//    processingDTD = true;
388
  }
389
390
  /**
391 688 bojilova
   * SAX Handler that receives notification of the start of entities.
392
   * @param name name of the entity
393 598 bojilova
   */
394
  public void startEntity(String name) throws SAXException {
395
    if (name.equals("[dtd]")) {
396
      processingDTD = true;
397
    }
398
  }
399
400
  /**
401 688 bojilova
   * SAX Handler that receives notification of the end of entities.
402
   * @param name name of the entity
403 598 bojilova
   */
404
  public void endEntity(String name) throws SAXException {
405
    if (name.equals("[dtd]")) {
406
      processingDTD = false;
407
    }
408
  }
409
410
  /**
411 688 bojilova
   * Get the document name.
412 598 bojilova
   */
413
  public String getDocname() {
414
    return docname;
415
  }
416
417
  /**
418 688 bojilova
   * Get the document processing state.
419 598 bojilova
   */
420
  public boolean processingDTD() {
421
    return processingDTD;
422
  }
423
424 688 bojilova
  /* Delete from db all permission for resources related to @aclid if any.*/
425 645 bojilova
  private void deletePermissionsForRelatedResources(String aclid)
426 638 bojilova
          throws SQLException
427
  {
428 645 bojilova
    // delete all acl records for resources related to @aclid if any
429 638 bojilova
    Statement stmt = conn.createStatement();
430 645 bojilova
    stmt.execute("DELETE FROM xml_access WHERE accessfileid = '" + aclid + "'");
431 638 bojilova
    stmt.close();
432
  }
433
434 688 bojilova
  /* Delete all of the relations with a docid of @docid from db connection.*/
435
  private void deleteRelations(String docid)
436 638 bojilova
          throws SQLException
437
  {
438 645 bojilova
    Statement stmt = conn.createStatement();
439
    stmt.execute("DELETE FROM xml_relation WHERE docid='" + docid + "'");
440
    stmt.close();
441
  }
442
443 688 bojilova
  /* Insert relationship for @aclid on @resourceURL into db connection.*/
444 645 bojilova
  private void insertRelation(String aclid, String resourceURL)
445
          throws SQLException
446
  {
447
    String aclURL = "metacat://" + server + "/?docid=" + aclid;
448 638 bojilova
449
    // insert relationship
450
    PreparedStatement pstmt;
451
    pstmt = conn.prepareStatement(
452
            "INSERT INTO xml_relation (docid,subject,relationship,object) " +
453
            "VALUES (?, ?, ?, ?)");
454
    pstmt.setString(1, aclid);
455
    pstmt.setString(2, aclURL);
456
    pstmt.setString(3, "isaclfor");
457
    pstmt.setString(4, resourceURL);
458
459
    pstmt.execute();
460
    pstmt.close();
461
  }
462
463 688 bojilova
  /* Insert into db calculated permission for the list of principals */
464 570 bojilova
  private void insertPermissions( String permType )
465 555 bojilova
          throws SQLException
466
  {
467 672 bojilova
    PreparedStatement pstmt;
468 638 bojilova
469 555 bojilova
    try {
470
      pstmt = conn.prepareStatement(
471
              "INSERT INTO xml_access " +
472 645 bojilova
              "(docid, principal_name, permission, perm_type, perm_order," +
473
              "begin_time,end_time,ticket_count, accessfileid) VALUES " +
474
              "(?,?,?,?,?,to_date(?,'mm/dd/yy'),to_date(?,'mm/dd/yy'),?,?)");
475 555 bojilova
      // Bind the values to the query
476 570 bojilova
      pstmt.setInt(3, permission);
477
      pstmt.setString(4, permType);
478
      pstmt.setString(5, permOrder);
479
      pstmt.setString(6, beginTime);
480
      pstmt.setString(7, endTime);
481 645 bojilova
      pstmt.setString(9, aclid);
482 570 bojilova
      if ( ticketCount > 0 ) {
483
        pstmt.setString(8, "" + ticketCount);
484
      } else {
485 555 bojilova
        pstmt.setString(8, "");
486 570 bojilova
      }
487 688 bojilova
      String docid;
488
      String prName;
489 665 bojilova
      for ( int i = 0; i < resourceID.size(); i++ ) {
490 688 bojilova
        docid = (String)resourceID.elementAt(i);
491
        pstmt.setString(1, docid);
492 665 bojilova
        for ( int j = 0; j < principal.size(); j++ ) {
493 688 bojilova
          prName = (String)principal.elementAt(j);
494
          pstmt.setString(2, prName);
495 665 bojilova
          pstmt.execute();
496 688 bojilova
497
          // check if there are conflict with permission's order
498
          String permOrderOpos = permOrder;
499
          int perm = getPermissions(permission, prName, docid, permOrder);
500
          if (  perm != 0 ) {
501
            if ( permOrder.equals("allowFirst") ) {
502
              permOrderOpos = "denyFirst";
503
            } else if ( permOrder.equals("denyFirst") ) {
504
              permOrderOpos = "allowFirst";
505
            }
506
            throw new SQLException("Permission(s) " + txtValue(perm) +
507
                      " for \"" + prName + "\" on document #" + docid +
508
                      " has/have been used with \"" + permOrderOpos + "\"");
509
          }
510 665 bojilova
        }
511 570 bojilova
      }
512 672 bojilova
      pstmt.close();
513 555 bojilova
514 570 bojilova
    } catch (SQLException e) {
515
      throw new
516
      SQLException("AccessControlList.insertPermissions(): " + e.getMessage());
517 672 bojilova
    }
518
  }
519
520 688 bojilova
  /* Update into db public "read" access for the list of resources. */
521 672 bojilova
  private void updatePublicAccess(String publicAcc)
522
          throws SQLException
523
  {
524
    try {
525
      PreparedStatement pstmt;
526
      pstmt = conn.prepareStatement(
527
              "UPDATE xml_documents SET public_access = ?" +
528 765 bojilova
              " WHERE docid = ?");
529 672 bojilova
      // Bind the values to the query
530 682 bojilova
      if ( publicAcc == null ) {
531
        pstmt.setString(1, null);
532
      } else if ( publicAcc.toUpperCase().equals("YES") ) {
533 672 bojilova
        pstmt.setInt(1, 1);
534
      } else {
535
        pstmt.setInt(1, 0);
536
      }
537
      for ( int i = 0; i < resourceID.size(); i++ ) {
538
        pstmt.setString(2, (String)resourceID.elementAt(i));
539
        pstmt.execute();
540
      }
541 667 berkley
      pstmt.close();
542 672 bojilova
543
    } catch (SQLException e) {
544
      throw new
545
      SQLException("AccessControlList.updatePublicAccess(): " + e.getMessage());
546 570 bojilova
    }
547
  }
548
549 688 bojilova
  /* Get permissions with permission order different than @permOrder. */
550
  private int getPermissions(int permission, String principal,
551
                             String docid, String permOrder)
552
          throws SQLException
553
  {
554
    PreparedStatement pstmt;
555
    pstmt = conn.prepareStatement(
556
            "SELECT permission FROM xml_access " +
557 765 bojilova
            "WHERE docid = ? " +
558
            "AND principal_name = ? " +
559
            "AND perm_order NOT = ?");
560 688 bojilova
    pstmt.setString(1, docid);
561
    pstmt.setString(2, principal);
562
    pstmt.setString(3, permOrder);
563
    pstmt.execute();
564
    ResultSet rs = pstmt.getResultSet();
565
    boolean hasRow = rs.next();
566
    int perm = 0;
567
    while ( hasRow ) {
568
      perm = rs.getInt(1);
569
      perm = permission & perm;
570
      if ( perm != 0 ) {
571
        pstmt.close();
572
        return perm;
573
      }
574
      hasRow = rs.next();
575
    }
576
    pstmt.close();
577
    return 0;
578
  }
579
580
  /* Get the int value of READ, WRITE or ALL. */
581
  private int intValue ( String permission )
582
  {
583
    if ( permission.equals("READ") ) {
584
      return READ;
585
    } else if ( permission.equals("WRITE") ) {
586
      return WRITE;
587
    } else if ( permission.equals("ALL") ) {
588
      return ALL;
589
    }
590
591
    return -1;
592
  }
593
594
  /* Get the text value of READ, WRITE or ALL. */
595
  private String txtValue ( int permission )
596
  {
597
    StringBuffer txtPerm = new StringBuffer("\"");
598
    if ( (permission & READ) != 0 ) {
599
      txtPerm.append("read");
600
    }
601
    if ( (permission & WRITE) != 0 ) {
602
      if ( txtPerm.length() > 0 ) txtPerm.append(",");
603
      txtPerm.append("write");
604
    }
605
    if ( (permission & ALL) != 0 ) {
606
      if ( txtPerm.length() > 0 ) txtPerm.append(",");
607
      txtPerm.append("all");
608
    }
609
610
    return txtPerm.append("\"").toString();
611
  }
612
613
  /* Read docid from @url. */
614
  private String getDocid ( String url ) throws SAXException
615
  {
616
    MetaCatUtil util = new MetaCatUtil();
617
    try {
618
      URL urlobj = new URL(url);
619
      Hashtable urlParams = util.parseQuery(urlobj.getQuery());
620
      if ( urlParams.containsKey("docid") ) {
621
        return (String)urlParams.get("docid"); // return the docid value
622
      } else {
623
        throw new
624
        SAXException("\"docid\" not specified within " + url);
625
      }
626
    } catch (MalformedURLException e) {
627
      throw new SAXException(e.getMessage());
628
    }
629
  }
630
631
632
  /**
633
    * Check from db connection if @principal has @permission on @resourceID.
634
    * @param permission permission type to check for
635
    * @param principal name of principal to check for @permission
636
    * @param resourceID resource identifier to check on
637
    */
638 607 bojilova
  public boolean hasPermission ( String permission,
639 665 bojilova
                                 String principal, String resourceID )
640 570 bojilova
                 throws SQLException
641
  {
642
    PreparedStatement pstmt;
643 665 bojilova
    // check public access to @resourceID from xml_documents table
644 570 bojilova
    if ( permission.equals("READ") ) {
645
      try {
646 607 bojilova
        pstmt = conn.prepareStatement(
647 570 bojilova
                "SELECT 'x' FROM xml_documents " +
648 765 bojilova
                "WHERE docid = ? AND public_access = 1");
649 570 bojilova
        // Bind the values to the query
650 665 bojilova
        pstmt.setString(1, resourceID);
651 570 bojilova
652 555 bojilova
        pstmt.execute();
653 570 bojilova
        ResultSet rs = pstmt.getResultSet();
654
        boolean hasRow = rs.next();
655
        pstmt.close();
656
        if (hasRow) {
657
          return true;
658
        }
659
//System.out.println("Passed the check for public access");
660
661
      } catch (SQLException e) {
662
        throw new
663 688 bojilova
        SQLException("AccessControlList.hasPermission(). " +
664
                     "Error checking public access for document #"+resourceID+
665
                     ". " + e.getMessage());
666 555 bojilova
      }
667 570 bojilova
    }
668
669
    // since owner of resource has all permission on it,
670 665 bojilova
    // check if @principal is owner of @resourceID in xml_documents table
671 570 bojilova
    if ( principal != null ) {
672
      try {
673 607 bojilova
        pstmt = conn.prepareStatement(
674 570 bojilova
                "SELECT 'x' FROM xml_documents " +
675 765 bojilova
                "WHERE docid = ? AND user_owner = ?");
676 570 bojilova
        // Bind the values to the query
677 665 bojilova
        pstmt.setString(1, resourceID);
678 570 bojilova
        pstmt.setString(2, principal);
679 555 bojilova
680 570 bojilova
        pstmt.execute();
681
        ResultSet rs = pstmt.getResultSet();
682
        boolean hasRow = rs.next();
683
        pstmt.close();
684
        if (hasRow) {
685
          return true;
686 555 bojilova
        }
687 570 bojilova
//System.out.println("Passed the check for ownership");
688
689
      } catch (SQLException e) {
690
        throw new
691 688 bojilova
        SQLException("AccessControlList.hasPermission(). " +
692
                     "Error checking ownership for " + principal +
693
                     " on document #" + resourceID + ". " + e.getMessage());
694 570 bojilova
      }
695
696 665 bojilova
      // check @principal's @permission on @resourceID from xml_access table
697 570 bojilova
      int accessValue = 0;
698
      int ticketCount = 0;
699
      String permOrder = "";
700
      try {
701 607 bojilova
        pstmt = conn.prepareStatement(
702 570 bojilova
                "SELECT permission, perm_order, ticket_count " +
703
                "FROM xml_access " +
704 765 bojilova
                "WHERE docid = ? " +
705
                "AND principal_name = ? " +
706
                "AND perm_type = ? " +
707 774 bojilova
                "AND " + sysdate +
708
                " BETWEEN " + isnull + "(begin_time," + sysdate + ") " +
709
                     "AND " + isnull + "(end_time," + sysdate + ")");
710 688 bojilova
        // check if it is "deny" with "allowFirst" first
711 570 bojilova
        // Bind the values to the query
712 665 bojilova
        pstmt.setString(1, resourceID);
713 570 bojilova
        pstmt.setString(2, principal);
714 688 bojilova
        pstmt.setString(3, "deny");
715 570 bojilova
716
        pstmt.execute();
717
        ResultSet rs = pstmt.getResultSet();
718
        boolean hasRows = rs.next();
719
        while ( hasRows ) {
720
          accessValue = rs.getInt(1);
721
          permOrder = rs.getString(2);
722
          ticketCount = rs.getInt(3);
723
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
724 638 bojilova
               ( permOrder.equals("allowFirst") ) &&
725 570 bojilova
               ( rs.wasNull() || ticketCount > 0 ) ) {
726
            if ( !rs.wasNull() && ticketCount > 0 ) {
727 688 bojilova
              decreaseNumberOfAccess(accessValue,principal,resourceID,"deny","allowFirst");
728 570 bojilova
            }
729
            pstmt.close();
730
            return false;
731
          }
732
          hasRows = rs.next();
733 555 bojilova
        }
734 688 bojilova
//System.out.println("Passed the check for \"deny\" access with \"allowFirst\"");
735 555 bojilova
736 688 bojilova
        // it is not denied then check if it is "allow"
737 570 bojilova
        // Bind the values to the query
738 665 bojilova
        pstmt.setString(1, resourceID);
739 570 bojilova
        pstmt.setString(2, principal);
740 688 bojilova
        pstmt.setString(3, "allow");
741 570 bojilova
742 555 bojilova
        pstmt.execute();
743 570 bojilova
        rs = pstmt.getResultSet();
744
        hasRows = rs.next();
745
        while ( hasRows ) {
746
          accessValue = rs.getInt(1);
747 688 bojilova
          permOrder = rs.getString(2);
748 570 bojilova
          ticketCount = rs.getInt(3);
749
          if ( ( accessValue & intValue(permission) )==intValue(permission) &&
750
               ( rs.wasNull() || ticketCount > 0 ) ) {
751
            if ( !rs.wasNull() && ticketCount > 0 ) {
752 688 bojilova
              decreaseNumberOfAccess(accessValue,principal,resourceID,"allow",permOrder);
753 570 bojilova
            }
754
            pstmt.close();
755
            return true;
756
          }
757
          hasRows = rs.next();
758
        }
759 688 bojilova
//System.out.println("Passed the check for \"allow\" access");
760 570 bojilova
761 688 bojilova
        // it is not allowed then check if it is "deny" with "denyFirst"
762
        // Bind the values to the query
763
        pstmt.setString(1, resourceID);
764
        pstmt.setString(2, principal);
765
        pstmt.setString(3, "deny");
766
767
        pstmt.execute();
768
        rs = pstmt.getResultSet();
769
        hasRows = rs.next();
770
        while ( hasRows ) {
771
          accessValue = rs.getInt(1);
772
          permOrder = rs.getString(2);
773
          ticketCount = rs.getInt(3);
774
          if ( ( accessValue & intValue(permission) ) == intValue(permission) &&
775
               ( permOrder.equals("denyFirst") ) &&
776
               ( rs.wasNull() || ticketCount > 0 ) ) {
777
            if ( !rs.wasNull() && ticketCount > 0 ) {
778
              decreaseNumberOfAccess(accessValue,principal,resourceID,"deny","denyFirst");
779
            }
780
            pstmt.close();
781
            return false;
782
          }
783
          hasRows = rs.next();
784
        }
785
//System.out.println("Passed the check for \"deny\" access wirh \"denyFirst\"");
786 570 bojilova
787
        pstmt.close();
788
        return false;
789
790
      } catch (SQLException e) {
791
        throw new
792 688 bojilova
        SQLException("AccessControlList.hasPermission(). " +
793
                     "Error checking " + permission + " permission for " +
794
                     principal + " on document #" + resourceID + ". " +
795
                     e.getMessage());
796 555 bojilova
      }
797 570 bojilova
    }
798
799
    return false;
800
  }
801 555 bojilova
802 688 bojilova
  /* Decrease the number of access to @resourceID for @principal in db. */
803 570 bojilova
  private void decreaseNumberOfAccess(int permission, String principal,
804 688 bojilova
                                      String resourceID, String permType,
805
                                      String permOrder)
806 570 bojilova
               throws SQLException
807
  {
808
    PreparedStatement pstmt;
809
    pstmt = conn.prepareStatement(
810
            "UPDATE xml_access SET ticket_count = ticket_count - 1 " +
811 765 bojilova
            "WHERE docid = ? " +
812
            "AND principal_name = ? " +
813
            "AND permission = ? " +
814
            "AND perm_type = ? " +
815
            "AND perm_order = ? " +
816 774 bojilova
            "AND " + sysdate +
817
            " BETWEEN " + isnull + "(begin_time," + sysdate + ") " +
818
                 "AND " + isnull + "(end_time," + sysdate + ")");
819 570 bojilova
    // Bind the values to the query
820 665 bojilova
    pstmt.setString(1, resourceID);
821 570 bojilova
    pstmt.setString(2, principal);
822
    pstmt.setInt(3, permission);
823
    pstmt.setString(4, permType);
824 688 bojilova
    pstmt.setString(5, permOrder);
825 570 bojilova
826
    pstmt.execute();
827
    pstmt.close();
828
  }
829
830 688 bojilova
831
  /**
832
    * Get Access Control List information for document from db connetion.
833
    * User or Group should have permissions for reading
834
    * access control information for a document specified by @docid.
835
    * @param docid document identifier which acl info to get
836
    * @param user name of user connected to Metacat system
837
    * @param group name of user's group to which user belongs
838
    */
839
  public String getACL(String docid, String user, String group)
840
          throws SQLException
841 570 bojilova
  {
842 688 bojilova
    StringBuffer output = new StringBuffer();
843
    StringBuffer outTemp = new StringBuffer();
844
    MetaCatUtil util = new MetaCatUtil();
845
    String accDoctype = util.getOption("accessdoctype");
846
    String server = util.getOption("server");
847
    String docurl = "metacat://" + server + "/?docid=" + docid;
848
    String systemID;
849
    boolean isOwned = false;
850
    boolean hasPermission = false;
851
    String publicAcc;
852 570 bojilova
853 688 bojilova
    String acfid = "";
854
    String acfid_prev = "";
855
    String principal;
856
    Vector principalArr = new Vector();
857
    int permission;
858
    int perm_prev = -1;
859
    String permType;
860
    String permOrder = "";
861
    String permOrder_prev = "";
862
    String beginTime = "";
863
    String begin_prev = "";
864
    String endTime = "";
865
    String end_prev = "";
866
    int ticketCount = -1;
867
    int ticket_prev = -1;
868
869 638 bojilova
    try {
870 688 bojilova
871
      isOwned = isOwned(docid, user);
872
      systemID = getSystemID(accDoctype);
873
      publicAcc = getPublicAccess(docid);
874
875
      output.append("<?xml version=\"1.0\"?>\n");
876
      output.append("<!DOCTYPE acl PUBLIC \"" + accDoctype + "\" \"" +
877
                    systemID + "\">\n");
878
      output.append("<acl authSystem=\"\">\n");
879
880
      PreparedStatement pstmt;
881
      pstmt = conn.prepareStatement(
882
              "SELECT distinct accessfileid, principal_name, permission, " +
883
              "perm_type, perm_order, to_char(begin_time,'mm/dd/yyyy'), " +
884
              "to_char(end_time,'mm/dd/yyyy'), ticket_count " +
885 765 bojilova
              "FROM xml_access WHERE docid = ? " +
886 688 bojilova
              "ORDER BY accessfileid, perm_order, perm_type, permission");
887
      // Bind the values to the query
888
      pstmt.setString(1, docid);
889
      pstmt.execute();
890
      ResultSet rs = pstmt.getResultSet();
891
      boolean hasRows = rs.next();
892
      while (hasRows) {
893
894
        acfid = rs.getString(1);
895
        principal = rs.getString(2);
896
        permission = rs.getInt(3);
897
        permType = rs.getString(4);
898
        permOrder = rs.getString(5);
899
        beginTime = rs.getString(6);
900
        endTime = rs.getString(7);
901
        ticketCount = rs.getInt(8);
902
903
        // if @docid is not owned by @user, only ACL info from that
904
        // access files to which @user/@group has "read" permission
905
        // is extracted
906
        if ( !isOwned ) {
907
          if ( !acfid.equals(acfid_prev) ) {
908
            acfid_prev = acfid;
909
            hasPermission = hasPermission("READ",user,acfid);
910
            if ( !hasPermission && group != null ) {
911
              hasPermission = hasPermission("READ",group,acfid);
912
            }
913
          }
914
          if ( !hasPermission ) {
915
            rs.next();
916
            continue;
917
          }
918
        }
919
920
        // open <resource> tag
921
        if ( !permOrder.equals(permOrder_prev) ) {
922
          // close </resource> tag if any was opened
923
          output.append(outTemp.toString());
924
          outTemp = new StringBuffer();
925
          if ( !permOrder_prev.equals("") ) {
926
            output.append("  </resource>\n");
927
          }
928
          output.append("  <resource order=\"" + permOrder + "\" public=\"" +
929
                        publicAcc + "\">\n");
930
          output.append("    <resourceIdentifier>" + docurl +
931
                        "</resourceIdentifier>\n");
932
          permOrder_prev = permOrder;
933
        }
934
935
        // close </allow> or </deny> tag then open new one
936
        if ( permission != perm_prev ||
937
             (endTime == null) && (end_prev != null) ||
938
             (beginTime == null) && (begin_prev != null) ||
939
             endTime != null && !endTime.equals(end_prev)  ||
940
             beginTime != null && !beginTime.equals(begin_prev) ||
941
             ticketCount != ticket_prev )  {
942
          output.append(outTemp.toString());
943
          outTemp = new StringBuffer();
944
          principalArr.removeAllElements();
945
          output.append("    <" + permType + ">\n");
946
        }
947
948
        // put all principals here for the same
949
        // permission, duration and ticket_count
950
        if ( !principalArr.contains(principal) ) {
951
          principalArr.addElement(principal);
952
          output.append("      <principal>" + principal + "</principal>\n");
953
        }
954
955
        // prepare <permission> tags, <duration> and <ticketCount>
956
        // if any to put within <allow> (<deny>) by next cicle
957
        if ( permission != perm_prev ||
958
             (endTime == null) && (end_prev != null) ||
959
             (beginTime == null) && (begin_prev != null) ||
960
             endTime != null && !endTime.equals(end_prev)  ||
961
             beginTime != null && !beginTime.equals(begin_prev) ||
962
             ticketCount != ticket_prev )  {
963
          if ( (permission & READ) != 0 ) {
964
            outTemp.append("      <permission>read</permission>\n");
965
          }
966
          if ( (permission & WRITE) != 0 ) {
967
            outTemp.append("      <permission>write</permission>\n");
968
          }
969
          if ( (permission & ALL) != 0 ) {
970
            outTemp.append("      <permission>all</permission>\n");
971
          }
972
          if ( (beginTime != null) || (endTime != null) ) {
973
            outTemp.append("      <duration>" + beginTime + " " + endTime +
974
                          "</duration>\n");
975
          }
976
          if ( ticketCount > 0 ) {
977
            outTemp.append("      <ticketCount>" + ticketCount +
978
                          "</ticketCount>\n");
979
          }
980
          outTemp.append("    </" + permType + ">\n");
981
          perm_prev = permission;
982
          ticket_prev = ticketCount;
983
          begin_prev = beginTime;
984
          end_prev = endTime;
985
        }
986
987
        hasRows = rs.next();
988 652 bojilova
      }
989 688 bojilova
990
      // close <allow> or <deny> if anything left in outTemp var
991
      output.append(outTemp.toString());
992
993
      // If there are no any acl info for @docid accessible by @user/@group,
994
      // extract only the following information
995
      if ( permOrder.equals("") ) {
996
        output.append("  <resource public=\"" + publicAcc + "\">\n");
997
        output.append("    <resourceIdentifier>" + docurl +
998
                      "</resourceIdentifier>\n");
999
      }
1000
1001
      // always close them
1002
      output.append("  </resource>\n");
1003
      output.append("</acl>\n");
1004
1005
      pstmt.close();
1006
1007
      return output.toString();
1008
1009
    } catch (SQLException e) {
1010
      throw new
1011
      SQLException("AccessControlList.getACL(). " + e.getMessage());
1012 638 bojilova
    }
1013 688 bojilova
  }
1014
1015
  /* Check if @user is owner of @docid from db conn. */
1016
  private boolean isOwned(String docid, String user) throws SQLException {
1017
1018
    PreparedStatement pstmt;
1019
    pstmt = conn.prepareStatement("SELECT 'x' FROM xml_documents " +
1020 765 bojilova
                                  "WHERE docid = ? " +
1021
                                  "AND user_owner = ?");
1022 688 bojilova
    pstmt.setString(1, docid);
1023
    pstmt.setString(2, user);
1024
    pstmt.execute();
1025
    ResultSet rs = pstmt.getResultSet();
1026
    boolean hasRow = rs.next();
1027
1028
    return hasRow;
1029
  }
1030 652 bojilova
1031 688 bojilova
  /* Get the flag for public "read" access for @docid from db conn. */
1032
  private String getPublicAccess(String docid) throws SQLException {
1033
1034
    int publicAcc = 0;
1035
    PreparedStatement pstmt;
1036
    pstmt = conn.prepareStatement("SELECT public_access FROM xml_documents " +
1037 765 bojilova
                                  "WHERE docid = ?");
1038 688 bojilova
    pstmt.setString(1, docid);
1039
    pstmt.execute();
1040
    ResultSet rs = pstmt.getResultSet();
1041
    boolean hasRow = rs.next();
1042
    if ( hasRow ) {
1043
      publicAcc = rs.getInt(1);
1044
    }
1045
1046
    return (publicAcc == 1) ? "yes" : "no";
1047
  }
1048
1049
  /* Get SystemID for @publicID from Metacat DB Catalog. */
1050
  private String getSystemID(String publicID) throws SQLException {
1051
1052
    String systemID = "";
1053
    PreparedStatement pstmt;
1054
    pstmt = conn.prepareStatement("SELECT system_id FROM xml_catalog " +
1055
                                  "WHERE entry_type = 'DTD' " +
1056 765 bojilova
                                  "AND public_id = ?");
1057 688 bojilova
    pstmt.setString(1, publicID);
1058
    pstmt.execute();
1059
    ResultSet rs = pstmt.getResultSet();
1060
    boolean hasRow = rs.next();
1061
    if ( hasRow ) {
1062
      systemID = rs.getString(1);
1063
    }
1064
1065
    return systemID;
1066
  }
1067
1068 555 bojilova
}