Project

General

Profile

« Previous | Next » 

Revision 2245

Added by sgarg over 19 years ago

Merging in changes made in branch 'dataaccess' by Jing Tao.

View differences:

src/edu/ucsb/nceas/metacat/SubTree.java
34 34
import java.sql.SQLException;
35 35
import java.sql.ResultSet;
36 36

  
37
import org.xml.sax.SAXException;
37 38

  
38 39
/**
39 40
 * A Class that represents an XML Subtree
......
46 47
  protected long   startNodeId = -1;
47 48
  protected long   endNodeId =   -1;
48 49
  private Stack  subTreeNodeStack = null;
49
    
50

  
50 51
    /**
51 52
     * Defualt constructor
52 53
     */
53 54
    public SubTree()
54 55
    {
55
      
56

  
56 57
    }
57
    
58

  
58 59
    /**
59 60
     * Constructor of subtree
60 61
     */
61
    public SubTree(String myDocId, String mySubTreeId, 
62
    public SubTree(String myDocId, String mySubTreeId,
62 63
                  long myStartNodeId, long myEndNodeId)
63
                  throws McdbException
64 64
    {
65 65
      this.docId = myDocId;
66 66
      MetaCatUtil.debugMessage("Docid of Subtree: " + docId, 30);
......
70 70
      MetaCatUtil.debugMessage("start node id of Subtree: " + startNodeId, 30);
71 71
      this.endNodeId = myEndNodeId;
72 72
      MetaCatUtil.debugMessage("end node id of subtree: " + endNodeId, 30);
73
      subTreeNodeStack = getSubTreeNodeList();
73

  
74 74
    }
75
    
75

  
76 76
    /**
77 77
     * Get subtree node stack
78 78
     */
79
    public Stack getSubTreeNodeStack()
79
    public Stack getSubTreeNodeStack() throws SAXException
80 80
    {
81
      try
82
      {
83
        subTreeNodeStack = getSubTreeNodeList();
84
      }
85
      catch (McdbException e)
86
      {
87
        throw new SAXException(e.getMessage());
88
      }
81 89
      return this.subTreeNodeStack;
82 90
    }
83
    
91

  
84 92
    /**
85 93
     * Set subtree node stack
86 94
     */
......
88 96
    {
89 97
      this.subTreeNodeStack = myStack;
90 98
    }
91
 
99

  
92 100
    /** Set the a docId */
93 101
    public void setDocId(String myId)
94 102
    {
......
97 105
    }
98 106

  
99 107
    /** Get the docId */
100
    public String getDocId() 
108
    public String getDocId()
101 109
    {
102 110
      return this.docId;
103 111
    }
104 112

  
105
 
113

  
106 114
    /** Set the a subtreeId */
107 115
    public void setSubTreeId(String myId)
108 116
    {
......
111 119
    }
112 120

  
113 121
    /** Get the subTreeId */
114
    public String getSubTreeId() 
122
    public String getSubTreeId()
115 123
    {
116 124
      return this.subTreeId;
117 125
    }
118 126

  
119
    /** 
127
    /**
120 128
     * Set a startElementName
121 129
     */
122
    public void setStartElementName(String elementName) 
130
    public void setStartElementName(String elementName)
123 131
    {
124 132
      MetaCatUtil.debugMessage("set start elementname: "+elementName, 35);
125 133
      this.startElementName = elementName;
126 134
    }
127
    
135

  
128 136
    /**
129 137
     * Get startElementName
130 138
     */
......
132 140
    {
133 141
      return this.startElementName;
134 142
    }
135
    
143

  
136 144
    /** Set a start node id */
137 145
    public void setStartNodeId(long nodeId)
138 146
    {
139 147
      MetaCatUtil.debugMessage("set start node id: "+nodeId, 35);
140 148
      this.startNodeId = nodeId;
141 149
    }
142
    
150

  
143 151
    /** Get start node id */
144 152
    public long getStartNodeId()
145 153
    {
146 154
      return this.startNodeId;
147 155
    }
148
    
156

  
149 157
    /** Set a end node id */
150 158
    public void setEndNodeId(long nodeId)
151 159
    {
152 160
      MetaCatUtil.debugMessage("set end node id: "+nodeId, 35);
153 161
      this.endNodeId = nodeId;
154 162
    }
155
    
163

  
156 164
    /** Get end node id */
157 165
    public long getEndNodeId()
158 166
    {
159 167
      return this.endNodeId;
160 168
    }
161
    
169

  
162 170
    /* Put a subtree node into a stack, on top is the start point of subtree*/
163
    public Stack getSubTreeNodeList() throws McdbException
171
    private Stack getSubTreeNodeList() throws McdbException
164 172
    {
165 173
       Stack nodeRecordList = new Stack();
166 174
       // make sure it works
......
171 179
       PreparedStatement pstmt = null;
172 180
       DBConnection dbconn = null;
173 181
       int serialNumber = -1;
174
       
182

  
175 183
       long nodeid = 0;
176 184
       long parentnodeid = 0;
177 185
       long nodeindex = 0;
......
180 188
       String nodeprefix = null;
181 189
       String nodedata = null;
182 190
       String sql = "SELECT nodeid, parentnodeid, nodeindex, " +
183
                    "nodetype, nodename, nodeprefix, nodedata " +               
191
                    "nodetype, nodename, nodeprefix, nodedata " +
184 192
                    "FROM xml_nodes WHERE docid = ? AND nodeid >= ? AND " +
185 193
                    "nodeid <= ? ORDER BY nodeid DESC";
186
       try 
194
       try
187 195
       {
188 196
         dbconn=DBConnectionPool.
189 197
                    getDBConnection("SubTree.getSubTreeNodeList");
......
198 206
         pstmt.execute();
199 207
         ResultSet rs = pstmt.getResultSet();
200 208
         boolean tableHasRows = rs.next();
201
        
202
         while (tableHasRows) 
209

  
210
         while (tableHasRows)
203 211
         {
204 212
           nodeid = rs.getLong(1);
205 213
           parentnodeid = rs.getLong(2);
......
220 228
         pstmt.close();
221 229

  
222 230
      } //try
223
      catch (SQLException e) 
231
      catch (SQLException e)
224 232
      {
225 233
        throw new McdbException("Error in SubTree.getSubTreeNodeList 1 " +
226 234
                              e.getMessage());
......
241 249
          DBConnectionPool.returnDBConnection(dbconn, serialNumber);
242 250
        }
243 251
      }//finally
244
  
252

  
245 253
      return nodeRecordList;
246
   
254

  
247 255
    }//getSubtreeNodeList
248
    
256

  
249 257
   /** methods from Comparator interface */
250 258
   public int compare(Object o1, Object o2)
251 259
   {
......
263 271
     {
264 272
       return 0;
265 273
     }
266
     
274

  
267 275
   }//cpmpare
268
   
276

  
269 277
   /** method from Comparator interface */
270 278
   public boolean equals(Object obj)
271 279
   {
......
279 287
       return false;
280 288
     }
281 289
   }
282
   
290

  
283 291
}
src/edu/ucsb/nceas/metacat/MetaCatServlet.java
823 823
            PermissionController controller = new PermissionController(docId);
824 824
            // check top level read permission
825 825
            if (!controller.hasPermission(user, groups,
826
                    AccessControlInterface.READSTRING)) {
826
                    AccessControlInterface.READSTRING))
827
            {
827 828
                throw new Exception("User " + user
828 829
                        + " doesn't have permission " + " to read document "
829 830
                        + docId);
830
            } else if (controller.hasSubTreeAccessControl()) {
831
                // if the document has subtree control, we need to check
832
                // subtree control
833
                // get node id for inlinedata
834
                long nodeId = getInlineDataNodeId(inlineDataId, docId);
835
                if (!controller.hasPermissionForSubTreeNode(user, groups,
836
                    AccessControlInterface.READSTRING, nodeId)) {
837
                    throw new Exception(
838
                    "User " + user + " doesn't have permission "
839
                    + " to read inlinedata " + inlineDataId);
840
                }
841 831
            }
832
            else
833
            {
834
              //check data access level
835
              try
836
              {
837
                Hashtable unReadableInlineDataList =
838
                PermissionController.getUnReadableInlineDataIdList(docId,user,groups);
839
                if (unReadableInlineDataList.containsValue(inlineDataId))
840
                {
841
                  throw new Exception("User " + user
842
                       + " doesn't have permission " + " to read inlinedata "
843
                       + inlineDataId);
842 844

  
845
                }//if
846
              }//try
847
              catch (Exception e)
848
              {
849
                throw e;
850
              }//catch
851
            }//else
852

  
843 853
            // Get output stream
844 854
            out = response.getOutputStream();
845 855
            // read the inline data from the file
......
972 982
                qformat = ((String[]) params.get("qformat"))[0];
973 983
            }
974 984
            // the param for only metadata (eml)
975
            if (params.containsKey("inlinedata")) {
985
            // we don't support read a eml document without inline data now.
986
            /*if (params.containsKey("inlinedata")) {
976 987

  
977 988
                String inlineData = ((String[]) params.get("inlinedata"))[0];
978 989
                if (inlineData.equalsIgnoreCase("false")) {
979 990
                    withInlineData = false;
980 991
                }
981
            }
992
            }*/
982 993
            if ((docs.length > 1) || qformat.equals("zip")) {
983 994
                zip = true;
984 995
                out = response.getOutputStream();
src/edu/ucsb/nceas/metacat/PermissionController.java
1 1
/**
2 2
 *  '$RCSfile$'
3 3
 *    Purpose: A class that handles checking permssision for a document
4
               and subtree in a document 
5
 *             
4
               and subtree in a document
5
 *
6 6
 *  Copyright: 2000 Regents of the University of California and the
7 7
 *             National Center for Ecological Analysis and Synthesis
8 8
 *    Authors: Chad Berkley
......
27 27
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28 28
 */
29 29
package edu.ucsb.nceas.metacat;
30
 
30

  
31 31
import java.sql.*;
32 32
import java.util.Enumeration;
33 33
import java.util.Hashtable;
34 34
import java.util.Stack;
35 35
import java.util.Vector;
36
import java.util.Iterator;
36 37

  
37 38
public class PermissionController
38 39
{
......
40 41
   private boolean hasSubTreeAccessControl = false; // flag if has a subtree
41 42
                                                    // access for this docid
42 43
   private Vector subTreeList = new Vector();
43
   
44

  
44 45
   private long TOPLEVELSTARTNODEID = 0; //if start node is 0, means it is top
45 46
                                         //level document
46
   
47
   
47

  
48

  
48 49
   /**
49 50
    * Constructor for PermissionController
50 51
    * @param myDocid      the docid need to access
......
53 54
   {
54 55
     // Get rid of rev number
55 56
     docId = MetaCatUtil.getSmartDocId(myDocid);
56
     hasSubTreeAccessControl = checkSubTreeAccessControl();
57
     //hasSubTreeAccessControl = checkSubTreeAccessControl();
57 58
   }
58
   
59

  
59 60
   /**
61
    * Constructor for PermssionController
62
    * @param myDocid String
63
    * @param needDeleteRev boolean
64
    */
65
   public PermissionController(String myDocid, boolean needDeleteRevFromDocid)
66
   {
67
     if (!needDeleteRevFromDocid)
68
     {
69
       docId = myDocid;
70
     }
71
     else
72
     {
73
         docId = MetaCatUtil.getDocIdFromAccessionNumber(myDocid);
74
     }
75
   }
76

  
77
   /**
60 78
    * Return if a document has subtree access control
61 79
    */
62 80
   public boolean hasSubTreeAccessControl()
63 81
   {
64 82
     return hasSubTreeAccessControl;
65 83
   }
66
   
67
   /*
68
    * Go through the access table and find if has subtree access control
69
    * if has, store the subtree into list and return true. Otherwise, 
70
    * return false
71
    */
72
  private boolean checkSubTreeAccessControl() throws McdbException
73
  {
74
    boolean flag = false;
75
    PreparedStatement pStmt=null;
76
    ResultSet rs=null;
77
    DBConnection conn=null;
78
    int serialNumber=-1;
79
    String query="SELECT subtreeid, startnodeid, endnodeid from xml_access " +
80
                 "where docid =? ";
81
    
82
    try
83
    {
84
      //check out DBConnection
85
      conn=DBConnectionPool.getDBConnection("PermissionController.hasSubTreeA");
86
      serialNumber=conn.getCheckOutSerialNumber();
87
      
88
      pStmt=conn.prepareStatement(query);
89
      //bind the value to query
90
      pStmt.setString(1, docId);
91
      //execute the query
92
      pStmt.execute();
93
      rs=pStmt.getResultSet();
94
      //process the result
95
      while (rs.next()) //
96
      {
97
        String subTreeId = rs.getString(1);
98
        MetaCatUtil.debugMessage("subtreeid: "+subTreeId, 35);
99
        long startNodeId = rs.getLong(2);
100
        MetaCatUtil.debugMessage("subtree startNodeId: "+startNodeId, 35);
101
        long endNodeId = rs.getLong(3);
102
        MetaCatUtil.debugMessage("subtree endNodeId: "+endNodeId, 35);
103
        // if startnodeid field in table is empty,startNodeId will be 0
104
        if (subTreeId != null && startNodeId != 0 && startNodeId != 0)
105
        {
106
          flag = true;// has subtree control
107
          SubTree tree = new SubTree();
108
          tree.setSubTreeId(subTreeId);
109
          tree.setStartNodeId(startNodeId);
110
          tree.setEndNodeId(endNodeId);
111
          subTreeList.add(tree);
112
         }
113
      }
114
    }//try
115
    catch (SQLException e)
116
    {
117
      throw new McdbException(e);
118
    }
119
    finally
120
    {
121
      try
122
      {
123
        pStmt.close();
124
      }
125
      catch(SQLException ee)
126
      {
127
        throw new McdbException(ee);
128
      }
129
      finally
130
      {
131
        DBConnectionPool.returnDBConnection(conn, serialNumber);
132
      }
133
    }
134
    return flag;
135
  }
136
   
84

  
85

  
137 86
  /**
138 87
    * Check from db connection if at least one of the list of @principals
139 88
    * @param user  the user name
140 89
    * @param groups  the groups which the use is in
141 90
    * @param myPermission  permission type to check for
142 91
    */
143
  public boolean hasPermission(String user, String[]groups, String myPermission) 
92
  public boolean hasPermission(String user, String[]groups, String myPermission)
144 93
                              throws SQLException, Exception
145 94
  {
146 95
    boolean hasPermission=false;
147 96
    String [] userPackage=null;
148 97
    int permission =AccessControlList.intValue(myPermission);
149
       
98

  
150 99
    //for the commnad line invocation
151 100
    if ((user==null) && (groups==null || groups.length==0))
152 101
    {
153 102
      return true;
154 103
    }
155
   
104

  
156 105
    //create a userpackage including user, public and group member
157 106
    userPackage=createUsersPackage(user, groups);
158
    
107

  
159 108
    //if the requested document is access documents and requested permission
160 109
    //is "write", the user should have "all" right
161
    
110

  
162 111
    if (isAccessDocument(docId) && (permission == AccessControlInterface.WRITE))
163 112
    {
164
      
113

  
165 114
      hasPermission = hasPermission(userPackage,docId, 7);// 7 is all permission
166 115
    }//if
167 116
    else //in other situation, just check the request permission
168 117
    {
169
    
170
      
118

  
119

  
171 120
      // Check for @permission on @docid for @user and/or @groups
172 121
      hasPermission = hasPermission(userPackage,docId, permission);
173
     
122

  
174 123
    }//else
175
    
124

  
176 125
    return hasPermission;
177 126
  }
178
  
179
 
127

  
128

  
180 129
  /**
181 130
    * Check from db connection if the users in String array @principals has
182
    * @permission on @docid* 
131
    * @permission on @docid*
183 132
    * @param principals, names in userPakcage need to check for @permission
184 133
    * @param docid, document identifier to check on
185
    * @param permission, permission (write or all...) to check for 
134
    * @param permission, permission (write or all...) to check for
186 135
    */
187 136
  private boolean hasPermission(String [] principals, String docId,
188 137
                                           int permission)
189 138
                         throws SQLException
190 139
  {
191 140
    long startId = TOPLEVELSTARTNODEID;// this is for top level, so startid is 0
192
    try 
141
    try
193 142
    {
194 143
      //first, if there is a docid owner in user package, return true
195
      //because doc owner has all permssion 
144
      //because doc owner has all permssion
196 145
      if (containDocumentOwner(principals, docId))
197 146
      {
198
          
147

  
199 148
          return true;
200 149
      }
201
      
150

  
202 151
      //If there is no owner in user package, checking the table
203 152
      //check perm_order
204 153
      if (isAllowFirst(principals, docId, startId))
205 154
      {
206
        
155

  
207 156
        if (hasExplicitDenyRule(principals, docId, permission, startId))
208 157
        {
209 158
          //if it is allowfirst and has deny rule(either explicit )
210 159
          //deny access
211
         
160

  
212 161
          return false;
213 162
        }//if
214 163
        else if ( hasAllowRule(principals, docId, permission, startId))
215 164
        {
216 165
          //if it is allowfirst and hasn't deny rule and has allow rule
217 166
          //allow access
218
          
167

  
219 168
          return true;
220 169
        }//else if
221 170
        else
222 171
        {
223 172
          //other situation deny access
224
         
173

  
225 174
          return false;
226 175
        }//else
227 176
     }//if isAllowFirst
......
244 193
      MetaCatUtil.debugMessage("There is a exception in hasPermission method: "
245 194
                         +e.getMessage(), 50);
246 195
    }
247
   
196

  
248 197
    return false;
249 198
  }//hasPermission
250
  
199

  
251 200
  /**
201
   *  Method to check if a person has permission to a inline data file
202
   * @param user String
203
   * @param groups String[]
204
   * @param myPermission String
205
   * @param inlineDataId String
206
   * @throws McdbException
207
   * @return boolean
208
   */
209
  private boolean hasPermissionForInlineData(String user, String[] groups,
210
                                      String myPermission, String inlineDataId)
211
                                      throws Exception
212
  {
213
     // this method can call the public method - hasPermission(...)
214
     // the only difference is about the ownership, you couldn't find the owner
215
     // from inlinedataId directly. You should get it from eml document itself
216
     String []userPackage = createUsersPackage(user, groups);
217
     if (containDocumentOwner(userPackage, docId))
218
     {
219
       return true;
220
     }
221
     else
222
     {
223
         PermissionController controller =
224
                               new PermissionController(inlineDataId, false);
225
         return controller.hasPermission(user, groups, myPermission);
226
     }
227
  }
228

  
229
  /**
252 230
   * The method to determine of a node can be access by a user just by subtree
253 231
   * access control
254 232
   */
......
258 236
  {
259 237
    boolean flag = true;
260 238
    // Get unaccessble subtree for this user
261
    Hashtable unaccessableSubTree = hasUnaccessableSubTree(user, groups, 
239
    Hashtable unaccessableSubTree = hasUnaccessableSubTree(user, groups,
262 240
                                                           myPermission);
263 241
    Enumeration en = unaccessableSubTree.elements();
264 242
    while (en.hasMoreElements())
......
276 254
    return flag;
277 255
  }
278 256
  /**
279
   * This method will return a hasTable of subtree which user doesn't has the 
257
   * This method will return a hasTable of subtree which user doesn't has the
280 258
   * permssion to access
281 259
   * @param user  the user name
282 260
   * @param groups  the groups which the use is in
283 261
   * @param myPermission  permission type to check for
284 262
   */
285
  public Hashtable hasUnaccessableSubTree(String user, String[] groups, 
263
  public Hashtable hasUnaccessableSubTree(String user, String[] groups,
286 264
                                       String myPermission) throws McdbException
287 265
  {
288 266
    Hashtable resultUnaccessableSubTree = new Hashtable();
289 267
    String [] principals=null;
290 268
    int permission =AccessControlList.intValue(myPermission);
291
    
269

  
292 270
    //for the commnad line invocation return null(no unaccessable subtree)
293 271
    if ((user==null) && (groups==null || groups.length==0))
294 272
    {
295 273
      return resultUnaccessableSubTree;
296 274
    }
297
    
275

  
298 276
    //create a userpackage including user, public and group member
299 277
    principals=createUsersPackage(user, groups);
300 278
    //for the document owner return null(no unaccessable subtree)
......
309 287
    {
310 288
      throw new McdbException(ee);
311 289
    }
312
    
290

  
313 291
    // go through every subtree which has access control
314 292
    for (int i = 0; i< subTreeList.size(); i++)
315 293
    {
316 294
      SubTree tree = (SubTree)subTreeList.elementAt(i);
317 295
      long startId = tree.getStartNodeId();
318
     
319
     
296

  
297

  
320 298
        try
321 299
        {
322 300
          if (isAllowFirst(principals, docId, startId))
323 301
          {
324
           
302

  
325 303
            if (hasExplicitDenyRule(principals, docId, permission, startId ))
326 304
            {
327
             
305

  
328 306
             //if it is allowfirst and has deny rule
329 307
              // put the subtree into unaccessable vector
330 308
              if (!resultUnaccessableSubTree.containsKey(new Long(startId)))
......
336 314
            {
337 315
              //if it is allowfirst and hasn't deny rule and has allow rule
338 316
              //allow access do nothing
339
              
317

  
340 318
            }//else if
341 319
            else
342 320
            {
......
345 323
              {
346 324
                resultUnaccessableSubTree.put(new Long(startId), tree);
347 325
              }
348
             
326

  
349 327
            }//else
350 328
          }//if isAllowFirst
351 329
          else //denyFirst
......
353 331
            if (hasAllowRule(principals, docId, permission,startId))
354 332
            {
355 333
              //if it is denyFirst and has allow rule, allow access, do nothing
356
           
334

  
357 335
            }
358 336
            else
359 337
            {
......
372 350
                                   "UnaccessableSubTree "+e.getMessage(), 30);
373 351
          throw new McdbException(e);
374 352
        }
375
     
353

  
376 354
    }//for
377
    // merge the subtree if a subtree is another subtree'subtree    
355
    // merge the subtree if a subtree is another subtree'subtree
378 356
    resultUnaccessableSubTree = mergeEquivalentSubtree(resultUnaccessableSubTree);
379 357
    return resultUnaccessableSubTree;
380 358
  }//hasUnaccessableSubtree
381
  
382
  
383
  /* 
359

  
360

  
361
  /*
384 362
   * A method to merge nested subtree into bigger one. For example subtree b
385 363
   * is a subtree of subtree a. And user doesn't have read permission for both
386 364
   * so we only use subtree a is enough.
......
406 384
        String  treeId  = tree.getSubTreeId();
407 385
        long    startId = tree.getStartNodeId();
408 386
        long    endId   = tree.getEndNodeId();
409
        
387

  
410 388
        Enumeration enu = unAccessSubTree.elements();
411 389
        while (enu.hasMoreElements())
412 390
        {
......
419 397
          if (startId > subTreeStartId && endId < subTreeEndId)
420 398
          {
421 399
            needDelete = true;
422
            MetaCatUtil.debugMessage("the subtree: "+ treeId + 
400
            MetaCatUtil.debugMessage("the subtree: "+ treeId +
423 401
                                     " need to be get rid of from unaccessable"+
424 402
                                     " subtree list becuase it is a subtree of"+
425 403
                                     " another subtree in the list", 45);
......
437 415
      return newSubTreeHash;
438 416
    }//else
439 417
  }
440
  
418

  
441 419
  /**
442 420
    * Check if a document id is a access document. Access document need user
443 421
    * has "all" permission to access it.
......
461 439
        ResultSet rs = pStmt.getResultSet();
462 440
        boolean hasRow = rs.next();
463 441
        String doctype = null;
464
        if (hasRow) 
442
        if (hasRow)
465 443
        {
466 444
          doctype = rs.getString(1);
467
         
445

  
468 446
        }
469 447
        pStmt.close();
470
      
448

  
471 449
        // if it is an access document
472 450
        if (doctype != null && ((MetaCatUtil.getOptionList(MetaCatUtil.
473 451
           getOption("accessdoctype")).contains(doctype))))
474 452
        {
475
          
453

  
476 454
          return true;
477 455
        }
478
        
456

  
479 457
      }
480 458
      catch(SQLException e)
481 459
      {
482
       
460

  
483 461
        throw new SQLException("PermissionControl.isAccessDocument " +
484 462
                     "Error checking" +
485 463
                     " on document " + docId + ". " + e.getMessage());
......
495 473
          DBConnectionPool.returnDBConnection(conn, serialNumber);
496 474
        }
497 475
      }
498
      
476

  
499 477
      return false;
500 478
    }//isAccessDocument
501
     
502
  
503
  
479

  
480

  
481

  
504 482
  /**
505 483
    * Check if a stirng array contains a given documents' owner
506 484
    * @param principals, a string array storing the username, groups name and
507 485
    * public.
508
    * @param docid, the id of given documents 
509
    */ 
486
    * @param docid, the id of given documents
487
    */
510 488
  private boolean containDocumentOwner( String[] principals, String docId)
511 489
                    throws SQLException
512 490
  {
513 491
    int lengthOfArray=principals.length;
514
    boolean hasRow; 
492
    boolean hasRow;
515 493
    PreparedStatement pStmt=null;
516 494
    DBConnection conn = null;
517 495
    int serialNumber = -1;
518
    
496

  
519 497
    try
520 498
    {
521 499
      //check out DBConnection
......
523 501
      serialNumber=conn.getCheckOutSerialNumber();
524 502
      pStmt = conn.prepareStatement(
525 503
                "SELECT 'x' FROM xml_documents " +
526
                "WHERE docid = ? AND lower(user_owner) = ?"); 
504
                "WHERE docid = ? AND lower(user_owner) = ?");
527 505
      //check every element in the string array too see if it conatains
528 506
      //the owner of document
529 507
      for (int i=0; i<lengthOfArray; i++)
530 508
      {
531
             
509

  
532 510
        // Bind the values to the query
533 511
        pStmt.setString(1, docId);
534 512
        pStmt.setString(2, principals[i]);
535
        MetaCatUtil.debugMessage("the principle stack is : " +  
513
        MetaCatUtil.debugMessage("the principle stack is : " +
536 514
                                  principals[i], 40);
537 515

  
538 516
        pStmt.execute();
539 517
        ResultSet rs = pStmt.getResultSet();
540 518
        hasRow = rs.next();
541
        if (hasRow) 
519
        if (hasRow)
542 520
        {
543 521
          pStmt.close();
544 522
           MetaCatUtil.debugMessage("find the owner", 40);
545 523
          return true;
546
        }//if    
547
     
524
        }//if
525

  
548 526
      }//for
549 527
    }//try
550
    catch (SQLException e) 
528
    catch (SQLException e)
551 529
    {
552 530
        pStmt.close();
553
       
554
        throw new 
531

  
532
        throw new
555 533
        SQLException("PermissionControl.hasPermission(). " +
556 534
                     "Error checking ownership for " + principals[0] +
557 535
                     " on document #" + docId + ". " + e.getMessage());
......
567 545
        DBConnectionPool.returnDBConnection(conn, serialNumber);
568 546
      }
569 547
    }
570
    return false; 
548
    return false;
571 549
  }//containDocumentOwner
572
  
550

  
573 551
  /**
574 552
    * Check if the permission order for user at that documents is allowFirst
575
    * @param principals, list of names of principals to check for 
553
    * @param principals, list of names of principals to check for
576 554
    * @param docid, document identifier to check for
577 555
    */
578
  private boolean isAllowFirst(String [] principals, String docId, 
556
  private boolean isAllowFirst(String [] principals, String docId,
579 557
                               long startId)
580 558
                  throws SQLException, Exception
581 559
  {
......
599 577
      sql = "SELECT perm_order FROM xml_access " +
600 578
        "WHERE lower(principal_name)= ? AND docid = ? AND startnodeid = ?";
601 579
    }
602
    
580

  
603 581
    try
604 582
    {
605 583
      //check out DBConnection
606 584
      conn=DBConnectionPool.getDBConnection("AccessControlList.isAllowFirst");
607 585
      serialNumber=conn.getCheckOutSerialNumber();
608
    
586

  
609 587
      //select permission order from database
610 588
      pStmt = conn.prepareStatement(sql);
611
   
589

  
612 590
      //check every name in the array
613 591
      for (int i=0; i<lengthOfArray;i++)
614 592
      {
615 593
        //bind value
616 594
        pStmt.setString(1, principals[i]);//user name
617 595
        pStmt.setString(2, docId);//docid
618
        
619
        // if subtree, we need set subtree id 
596

  
597
        // if subtree, we need set subtree id
620 598
        if (!topLever)
621 599
        {
622 600
          pStmt.setLong(3, startId);
623 601
        }
624
    
602

  
625 603
        pStmt.execute();
626 604
        ResultSet rs = pStmt.getResultSet();
627 605
        hasRow=rs.next();
......
658 636
        DBConnectionPool.returnDBConnection(conn, serialNumber);
659 637
      }
660 638
    }
661
    
662
    //if reach here, means there is no permssion record for given names and 
639

  
640
    //if reach here, means there is no permssion record for given names and
663 641
    //docid. So throw a exception.
664
    
642

  
665 643
    throw new Exception("There is no permission record for user"+principals[0]+
666 644
                        "at document "+docId);
667
        
645

  
668 646
  }//isAllowFirst
669
  
647

  
670 648
  /**
671
    * Check if the users array has allow rules for given users, docid and 
649
    * Check if the users array has allow rules for given users, docid and
672 650
    * permission.
673 651
    * If it has permission rule and ticket count is greater than 0, the ticket
674 652
    * number will decrease one for every allow rule
675
    * @param principals, list of names of principals to check for 
653
    * @param principals, list of names of principals to check for
676 654
    * @param docid, document identifier to check for
677 655
    * @param permission, the permssion need to check
678 656
    */
679
  private boolean hasAllowRule(String [] principals, String docId, 
657
  private boolean hasAllowRule(String [] principals, String docId,
680 658
                                  int permission, long startId)
681 659
                  throws SQLException, Exception
682 660
 {
......
695 673
   {
696 674
     // for toplevel
697 675
     topLever = true;
698
     sql = "SELECT permission FROM xml_access WHERE docid = ? " +  
676
     sql = "SELECT permission FROM xml_access WHERE docid = ? " +
699 677
   "AND lower(principal_name) = ? AND perm_type = ? AND startnodeid is NULL";
700 678
   }
701 679
   else
702 680
   {
703 681
     topLever =false;
704
     sql = "SELECT permission FROM xml_access WHERE docid = ? " +  
682
     sql = "SELECT permission FROM xml_access WHERE docid = ? " +
705 683
      "AND lower(principal_name) = ? AND perm_type = ? AND startnodeid = ?";
706 684
   }
707 685
   try
......
709 687
     //check out DBConnection
710 688
     conn=DBConnectionPool.getDBConnection("AccessControlList.hasAllowRule");
711 689
     serialNumber=conn.getCheckOutSerialNumber();
712
    //This sql statement will select entry with 
690
    //This sql statement will select entry with
713 691
    //begin_time<=currentTime<=end_time in xml_access table
714 692
    //If begin_time or end_time is null in table, isnull(begin_time, sysdate)
715 693
    //function will assign begin_time=sysdate
......
717 695
    //bind docid, perm_type
718 696
    pStmt.setString(1, docId);
719 697
    pStmt.setString(3, AccessControlInterface.ALLOW);
720
    
698

  
721 699
    // if subtree lever, need to set subTreeId
722 700
    if (!topLever)
723 701
    {
724 702
      pStmt.setLong(4, startId);
725 703
    }
726
   
704

  
727 705
    //bind every elenment in user name array
728 706
    for (int i=0;i<lengthOfArray; i++)
729 707
    {
......
733 711
      while (rs.next())//check every entry for one user
734 712
      {
735 713
        permissionValueInTable=rs.getInt(1);
736
    
737
        //permission is ok  
714

  
715
        //permission is ok
738 716
        //the user have a permission to access the file
739 717
        if (( permissionValueInTable & permissionValue )== permissionValue )
740 718
        {
741
           
719

  
742 720
           allow=true;//has allow rule entry
743 721
        }//if
744 722
      }//while
......
765 743
   }
766 744
    return allow;
767 745
 }//hasAllowRule
768
 
769
 
770
   
746

  
747

  
748

  
771 749
   /**
772
    * Check if the users array has explicit deny rules for given users, docid 
750
    * Check if the users array has explicit deny rules for given users, docid
773 751
    * and permission. That means the perm_type is deny and current time is
774 752
    * less than end_time and greater than begin time, or no time limit.
775
    * @param principals, list of names of principals to check for 
753
    * @param principals, list of names of principals to check for
776 754
    * @param docid, document identifier to check for
777 755
    * @param permission, the permssion need to check
778 756
    */
779
  private boolean hasExplicitDenyRule(String [] principals, String docId, 
757
  private boolean hasExplicitDenyRule(String [] principals, String docId,
780 758
                                      int permission, long startId)
781 759
                  throws SQLException
782 760
 {
......
789 767
   int serialNumber = -1;
790 768
   String sql = null;
791 769
   boolean topLevel = false;
792
   
770

  
793 771
   // decide top level or subtree level
794 772
   if (startId == TOPLEVELSTARTNODEID)
795 773
   {
796 774
     topLevel = true;
797
     sql = "SELECT permission FROM xml_access WHERE docid = ? " + 
775
     sql = "SELECT permission FROM xml_access WHERE docid = ? " +
798 776
    "AND lower(principal_name) = ? AND perm_type = ? AND startnodeid is NULL";
799 777
   }
800 778
   else
801 779
   {
802 780
     topLevel = false;
803
     sql = "SELECT permission FROM xml_access WHERE docid = ? " + 
781
     sql = "SELECT permission FROM xml_access WHERE docid = ? " +
804 782
     "AND lower(principal_name) = ? AND perm_type = ? AND startnodeid = ?";
805 783
   }
806
   
784

  
807 785
   try
808 786
   {
809 787
     //check out DBConnection
810 788
     conn=DBConnectionPool.getDBConnection("PermissionControl.hasExplicitDeny");
811 789
     serialNumber=conn.getCheckOutSerialNumber();
812
   
790

  
813 791
     pStmt = conn.prepareStatement(sql);
814 792
    //bind docid, perm_type
815 793
    pStmt.setString(1, docId);
816 794
    pStmt.setString(3, AccessControlInterface.DENY);
817
    
795

  
818 796
    // subtree level need to set up subtreeid
819 797
    if (!topLevel)
820 798
    {
821 799
      pStmt.setLong(4, startId);
822 800
    }
823
   
801

  
824 802
    //bind every elenment in user name array
825 803
    for (int i=0;i<lengthOfArray; i++)
826 804
    {
......
830 808
      while (rs.next())//check every entry for one user
831 809
      {
832 810
        permissionValueInTable=rs.getInt(1);
833
        
811

  
834 812
        //permission is ok the user doesn't have permission to access the file
835 813
        if (( permissionValueInTable & permissionValue )== permissionValue )
836
             
814

  
837 815
        {
838 816
           pStmt.close();
839 817
           return true;
......
857 835
     }
858 836
   }//finally
859 837
   return false;//no deny rule
860
  }//hasExplicitDenyRule 
861
   
838
  }//hasExplicitDenyRule
862 839

  
840

  
863 841
  /**
864 842
    * Creat a users pakages to check permssion rule, user itself, public and
865 843
    * the gourps the user belong will be include in this package
......
870 848
  {
871 849
    String [] usersPackage=null;
872 850
    int lengthOfPackage;
873
    
851

  
874 852
    if (groups!=null)
875 853
    {
876
      //if gouprs is not null and user is not public, we should create a array 
877
      //to store the groups and user and public. 
854
      //if gouprs is not null and user is not public, we should create a array
855
      //to store the groups and user and public.
878 856
      //So the length of userPackage is the length of group plus two
879 857
      if (!user.equalsIgnoreCase(AccessControlInterface.PUBLIC))
880 858
      {
......
886 864
        {
887 865
          usersPackage[0]= user.toLowerCase();
888 866
          MetaCatUtil.debugMessage("after transfer to lower case(not null): "+
889
                                     usersPackage[0], 45); 
867
                                     usersPackage[0], 45);
890 868
        }
891 869
        else
892 870
        {
......
923 901
          }
924 902
        } //for
925 903
      }//else user=public
926
       
904

  
927 905
    }//if groups!=null
928 906
    else
929 907
    {
......
953 931
    }//else groups==null
954 932
    return usersPackage;
955 933
  }//createUsersPackage
956
 
934

  
935

  
957 936
  /**
958
    * This method will return a data set id for given access id.
959
    * @param accessDocId, the accessDocId which need to be found data set id
937
   * A static method to get Hashtable which cointains a inlinedata object list that
938
   * user can't read it. The key is subtree id of inlinedata, the data is
939
   * internal file name for the inline data which is stored as docid
940
   * in xml_access table or data object doc id.
941
   * @param docidWithoutRev, metadata docid which should be the accessfileid
942
   *                         in the table
943
   * @param user , the name of user
944
   * @param groups, the group which the user belong to
960 945
   */
961
  private String getDataSetId(String accessDocId) 
962
                              throws SQLException
946
   public static Hashtable getUnReadableInlineDataIdList(String docidWithoutRev,
947
                                                   String user, String[] groups)
948
                                                throws Exception
949
   {
950
     Hashtable inlineDataList = getUnAccessableInlineDataIdList(docidWithoutRev,
951
                              user, groups, AccessControlInterface.READSTRING);
952

  
953
     return inlineDataList;
954
   }
955

  
956
   /**
957
  * A static method to get Hashtable which cointains a inline  data object list that
958
  * user can't overwrite it. The key is subtree id of inline data distrubition,
959
  * the value is internal file name for the inline data which is stored as docid
960
  * in xml_access table or data object doc id.
961
  * @param docidWithoutRev, metadata docid which should be the accessfileid
962
  *                         in the table
963
  * @param user , the name of user
964
  * @param groups, the group which the user belong to
965
  */
966
  public static Hashtable getUnWritableInlineDataIdList(String docidWithoutRev,
967
                                                  String user, String[] groups)
968
                                               throws Exception
963 969
  {
964
    String dataSetId=null;
965
    PreparedStatement pStmt=null;
966
    ResultSet rs=null;
967
    DBConnection conn=null;
968
    int serialNumber=-1;
969
    String query="SELECT docId from xml_relation where subject = ? or "
970
                                                +"object = ?";
971
    
972
    try
973
    {
974
      //check out DBConnection
975
      conn=DBConnectionPool.getDBConnection("PermissionControl.getDataSetId");
976
      serialNumber=conn.getCheckOutSerialNumber();
977
      
978
      pStmt=conn.prepareStatement(query);
979
      //bind the value to query
980
      pStmt.setString(1, accessDocId);
981
      pStmt.setString(2, accessDocId);
982
      //execute the query
983
      pStmt.execute();
984
      rs=pStmt.getResultSet();
985
      //process the result
986
      if (rs.next()) //There are some records for the data set id for access id
970
    Hashtable inlineDataList = getUnAccessableInlineDataIdList(docidWithoutRev,
971
                            user, groups, AccessControlInterface.WRITESTRING);
972

  
973
    return inlineDataList;
974
  }
975

  
976

  
977
   /*
978
    * This method will get hashtable which contains a unaccessable distribution
979
    * inlinedata object list
980
    */
981
   private static Hashtable getUnAccessableInlineDataIdList(String docid,
982
                               String user, String[] groups, String permission)
983
                             throws SQLException,McdbException, Exception
984
   {
985
      Hashtable unAccessbleIdList = new Hashtable();
986
      Hashtable allIdList = getAllInlineDataIdList(docid);
987
      Enumeration en = allIdList.keys();
988
      while (en.hasMoreElements())
987 989
      {
988
        dataSetId=rs.getString(1);
990
        String subTreeId = (String) en.nextElement();
991
        String fileId = (String) allIdList.get(subTreeId);
992
        //Here fileid is internal file id for line data. It stored in docid
993
        // field in xml_access table. so we don't need to delete rev
994
        PermissionController controller = new PermissionController(docid, false);
995
        if (!controller.hasPermissionForInlineData(user, groups, permission, fileId))
996
        {
997
          MetaCatUtil.debugMessage("Put subtree id " +subTreeId + " and " +
998
                                   "inline data file name " + fileId + " into "+
999
                                   "un"+permission+" hash", 20);
1000
          unAccessbleIdList.put(subTreeId, fileId);
1001
        }
989 1002
      }
990
      else //No data set id for the given access id in xml_relation table
991
      {
992
        dataSetId=null;
993
      }
994
    }//try
995
    finally
996
    {
997
      try
998
      {
999
        pStmt.close();
1000
      }
1001
      finally
1002
      {
1003
        DBConnectionPool.returnDBConnection(conn, serialNumber);
1004
      }
1005
    }
1006
    return dataSetId;
1007
  }//getDataPackageId() 
1008
  
1009
  
1003
      return unAccessbleIdList;
1004
   }
1005

  
1006

  
1007
   /*
1008
    * This method will get a hash table from xml_access table for all records
1009
    * about the inline data. The key is subtree id and data is a inline internal
1010
    * file name
1011
    */
1012
   private static Hashtable getAllInlineDataIdList(String docid) throws SQLException
1013
   {
1014
     Hashtable inlineDataList = new Hashtable();
1015
     String sql = "SELECT subtreeid, docid FROM xml_access WHERE " +
1016
                   "accessfileid = ? AND subtreeid  IS NOT NULL";
1017
     PreparedStatement pStmt=null;
1018
     ResultSet rs=null;
1019
     DBConnection conn=null;
1020
     int serialNumber=-1;
1021
     try
1022
     {
1023
       //check out DBConnection
1024
       conn=DBConnectionPool.getDBConnection("PermissionControl.getDataSetId");
1025
       serialNumber=conn.getCheckOutSerialNumber();
1026
       pStmt=conn.prepareStatement(sql);
1027
       //bind the value to query
1028
       pStmt.setString(1, docid);
1029
       //execute the query
1030
       pStmt.execute();
1031
       rs=pStmt.getResultSet();
1032
       //process the result
1033
       while(rs.next())
1034
       {
1035
         String subTreeId = rs.getString(1);
1036
         String inlineDataId = rs.getString(2);
1037
         if (subTreeId != null && !subTreeId.trim().equals("") &&
1038
            inlineDataId != null && !inlineDataId.trim().equals(""))
1039
         {
1040
           inlineDataList.put(subTreeId, inlineDataId);
1041
         }
1042
      }//while
1043
     }//try
1044
     finally
1045
     {
1046
       try
1047
       {
1048
         pStmt.close();
1049
       }
1050
       finally
1051
       {
1052
         DBConnectionPool.returnDBConnection(conn, serialNumber);
1053
       }
1054
     }//finally
1055
     return inlineDataList;
1056
   }//getAllInlineDataIdList
1010 1057
}
src/edu/ucsb/nceas/metacat/DocumentImpl.java
798 798
    StringWriter docwriter = new StringWriter();
799 799
    String userName = null;
800 800
    String[] groupNames = null;
801
    boolean withInlineData = false;
801
    boolean withInlineData = true;
802 802
    try
803 803
    {
804 804
      this.toXml(docwriter, userName, groupNames, withInlineData);
......
853 853
   * Print a text representation of the XML document to a Writer
854 854
   *
855 855
   * @param pw the Writer to which we print the document
856
   *  Now we decide no matter withinInlineData's value, the document will
857
   *
856 858
   */
857 859
  public void toXml(Writer pw, String user, String[] groups, boolean withInLineData)
858 860
                                                          throws McdbException
......
884 886
    MetaCatUtil util = new MetaCatUtil();
885 887

  
886 888
    // Here add code to handle subtree access control
887
    PermissionController control = new PermissionController(docid);
889
    /*PermissionController control = new PermissionController(docid);
888 890
    Hashtable unaccessableSubTree =control.hasUnaccessableSubTree(user, groups,
889 891
                                             AccessControlInterface.READSTRING);
890 892

  
......
897 899
    else
898 900
    {
899 901
      nodeRecordLists = getNodeRecordList(rootnodeid);
900
    }
901

  
902
    }*/
903
    nodeRecordLists = getNodeRecordList(rootnodeid);
902 904
    Stack openElements = new Stack();
903 905
    boolean atRootElement = true;
904 906
    boolean previousNodeWasElement = false;
......
1018 1020

  
1019 1021
      // Handle the TEXT nodes
1020 1022
      } else if (currentNode.nodetype.equals("TEXT")) {
1021
        if (previousNodeWasElement) {
1023
        if (previousNodeWasElement)
1024
        {
1022 1025
          out.print(">");
1023 1026
        }
1024
        if (!prcocessInlineData || !withInLineData)
1027
        if (!prcocessInlineData)
1025 1028
        {
1026
          // if it is not inline data just out put data or it is line data
1027
          // but user don't want it, just put local id in inlinedata
1029
          // if it is not inline data just out put data
1028 1030
          out.print(currentNode.nodedata);
1029 1031
        }
1030 1032
        else
1031 1033
        {
1032
          // if it is inline data and user want to see it, pull out from
1033
          // file system and output it
1034
          // for inline data, the data base only store the file name, so we
1035
          // can combine the file name and inline data file path, to get it
1034
          // if it is inline data first to get the inline data internal id
1036 1035
          String fileName = currentNode.nodedata;
1037
          Reader reader =
1038
                        Eml200SAXHandler.readInlineDataFromFileSystem(fileName);
1039
          char [] characterArray = new char [4*1024];
1036
          String accessfileName = MetaCatUtil.getDocIdWithoutRevFromInlineDataID(fileName);
1037
          // check if user has read permision for this inline data
1038
          boolean readInlinedata = false;
1040 1039
          try
1041 1040
          {
1042
            int length = reader.read(characterArray);
1043
            while ( length != -1)
1041
            Hashtable unReadableInlineDataList =
1042
                PermissionController.getUnReadableInlineDataIdList(accessfileName,user,groups);
1043
            if (!unReadableInlineDataList.containsValue(fileName))
1044 1044
            {
1045
              out.print(new String(characterArray, 0, length));
1046
              out.flush();
1047
              length = reader.read(characterArray);
1045
              readInlinedata = true;
1048 1046
            }
1049
            reader.close();
1050 1047
          }
1051
          catch (IOException e)
1048
          catch (Exception e)
1052 1049
          {
1053 1050
            throw new McdbException(e.getMessage());
1054 1051
          }
1055
        }
1056 1052

  
1057
        // reset proccess inline data false
1058
        prcocessInlineData =false;
1053
          if (readInlinedata)
1054
          {
1055
            //user want to see it, pull out from
1056
            // file system and output it
1057
            // for inline data, the data base only store the file name, so we
1058
            // can combine the file name and inline data file path, to get it
1059

  
1060
            Reader reader =
1061
                Eml200SAXHandler.readInlineDataFromFileSystem(fileName);
1062
            char[] characterArray = new char[4 * 1024];
1063
            try
1064
            {
1065
              int length = reader.read(characterArray);
1066
              while (length != -1)
1067
              {
1068
                out.print(new String(characterArray, 0, length));
1069
                out.flush();
1070
                length = reader.read(characterArray);
1071
              }
1072
              reader.close();
1073
            }
1074
            catch (IOException e)
1075
            {
1076
              throw new McdbException(e.getMessage());
1077
            }
1078
          }//if can read inline data
1079
          else
1080
          {
1081
             // if user can't read it, we only send it back a empty string
1082
             // in inline element.
1083
             out.print("");
1084
          }// else can't read inlinedata
1085
          // reset proccess inline data false
1086
          prcocessInlineData =false;
1087
        }// in inlinedata part
1059 1088
        previousNodeWasElement = false;
1060

  
1061 1089
      // Handle the COMMENT nodes
1062 1090
      } else if (currentNode.nodetype.equals("COMMENT")) {
1063 1091
        if (previousNodeWasElement) {
......
2197 2225

  
2198 2226
      MetaCatUtil.debugMessage("Start deleting doc "+docid+ "...", 20);
2199 2227
    // check for 'write' permission for 'user' to delete this document
2200
      if ( !hasWritePermission(user, groups, accnum) ) {
2228
      if ( !hasAllPermission(user, groups, accnum) ) {
2201 2229
        throw new Exception("User " + user +
2202 2230
              " does not have permission to delete XML Document #" + accnum);
2203 2231
      }
......
2226 2254
      pstmt.close();
2227 2255
      conn.increaseUsageCount(1);
2228 2256

  
2257
      pstmt = conn.
2258
             prepareStatement("DELETE FROM xml_access WHERE docid = ?");
2259
      pstmt.setString(1, docid);
2260
      pstmt.execute();
2261
      pstmt.close();
2262
      conn.increaseUsageCount(1);
2263

  
2264

  
2229 2265
      // Delete it from relation table
2230 2266
      pstmt = conn.
2231 2267
               prepareStatement("DELETE FROM xml_relation WHERE docid = ?");
......
2314 2350
                                            AccessControlInterface.READSTRING);
2315 2351
  }
2316 2352

  
2353
  /**
2354
   * Check for "WRITE" permission on @docid for @user and/or @groups
2355
   * from DB connection
2356
   */
2357
  private static boolean hasAllPermission (String user,
2358
                                 String[] groups, String docid )
2359
                 throws SQLException, Exception
2360
  {
2361
    // Check for WRITE permission on @docid for @user and/or @groups
2362
    PermissionController controller = new PermissionController(docid);
2363
    return controller.hasPermission(user,groups,
2364
                                   AccessControlInterface.ALLSTRING);
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff