1 /* 2 * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.security.auth; 27 28 import java.util.*; 29 import java.text.MessageFormat; 30 import java.security.Permission; 31 import java.security.PermissionCollection; 32 import java.security.Principal; 33 import sun.security.util.ResourcesMgr; 34 35 /** 36 * This class is used to protect access to private Credentials 37 * belonging to a particular {@code Subject}. The {@code Subject} 38 * is represented by a Set of Principals. 39 * 40 * <p> The target name of this {@code Permission} specifies 41 * a Credential class name, and a Set of Principals. 42 * The only valid value for this Permission's actions is, "read". 43 * The target name must abide by the following syntax: 44 * 45 * <pre> 46 * CredentialClass {PrincipalClass "PrincipalName"}* 47 * </pre> 48 * 49 * For example, the following permission grants access to the 50 * com.sun.PrivateCredential owned by Subjects which have 51 * a com.sun.Principal with the name, "duke". Note that although 52 * this example, as well as all the examples below, do not contain 53 * Codebase, SignedBy, or Principal information in the grant statement 54 * (for simplicity reasons), actual policy configurations should 55 * specify that information when appropriate. 56 * 57 * <pre> 58 * 59 * grant { 60 * permission javax.security.auth.PrivateCredentialPermission 61 * "com.sun.PrivateCredential com.sun.Principal \"duke\"", 62 * "read"; 63 * }; 64 * </pre> 65 * 66 * If CredentialClass is "*", then access is granted to 67 * all private Credentials belonging to the specified 68 * {@code Subject}. 69 * If "PrincipalName" is "*", then access is granted to the 70 * specified Credential owned by any {@code Subject} that has the 71 * specified {@code Principal} (the actual PrincipalName doesn't matter). 72 * For example, the following grants access to the 73 * a.b.Credential owned by any {@code Subject} that has 74 * an a.b.Principal. 75 * 76 * <pre> 77 * grant { 78 * permission javax.security.auth.PrivateCredentialPermission 79 * "a.b.Credential a.b.Principal "*"", 80 * "read"; 81 * }; 82 * </pre> 83 * 84 * If both the PrincipalClass and "PrincipalName" are "*", 85 * then access is granted to the specified Credential owned by 86 * any {@code Subject}. 87 * 88 * <p> In addition, the PrincipalClass/PrincipalName pairing may be repeated: 89 * 90 * <pre> 91 * grant { 92 * permission javax.security.auth.PrivateCredentialPermission 93 * "a.b.Credential a.b.Principal "duke" c.d.Principal "dukette"", 94 * "read"; 95 * }; 96 * </pre> 97 * 98 * The above grants access to the private Credential, "a.b.Credential", 99 * belonging to a {@code Subject} with at least two associated Principals: 100 * "a.b.Principal" with the name, "duke", and "c.d.Principal", with the name, 101 * "dukette". 102 * 103 * @since 1.4 104 */ 105 public final class PrivateCredentialPermission extends Permission { 106 107 private static final long serialVersionUID = 5284372143517237068L; 108 109 private static final CredOwner[] EMPTY_PRINCIPALS = new CredOwner[0]; 110 111 /** 112 * @serial 113 */ 114 private String credentialClass; 115 116 /** 117 * @serial The Principals associated with this permission. 118 * The set contains elements of type, 119 * {@code PrivateCredentialPermission.CredOwner}. 120 */ 121 private Set<Principal> principals; // ignored - kept around for compatibility 122 private transient CredOwner[] credOwners; 123 124 /** 125 * @serial 126 */ 127 private boolean testing = false; 128 129 /** 130 * Create a new {@code PrivateCredentialPermission} 131 * with the specified {@code credentialClass} and Principals. 132 */ 133 PrivateCredentialPermission(String credentialClass, 134 Set<Principal> principals) { 135 136 super(credentialClass); 137 this.credentialClass = credentialClass; 138 139 synchronized(principals) { 140 if (principals.size() == 0) { 141 this.credOwners = EMPTY_PRINCIPALS; 142 } else { 143 this.credOwners = new CredOwner[principals.size()]; 144 int index = 0; 145 Iterator<Principal> i = principals.iterator(); 146 while (i.hasNext()) { 147 Principal p = i.next(); 148 this.credOwners[index++] = new CredOwner 149 (p.getClass().getName(), 150 p.getName()); 151 } 152 } 153 } 154 } 155 156 /** 157 * Creates a new {@code PrivateCredentialPermission} 158 * with the specified {@code name}. The {@code name} 159 * specifies both a Credential class and a {@code Principal} Set. 160 * 161 * @param name the name specifying the Credential class and 162 * {@code Principal} Set. 163 * 164 * @param actions the actions specifying that the Credential can be read. 165 * 166 * @throws IllegalArgumentException if {@code name} does not conform 167 * to the correct syntax or if {@code actions} is not "read". 168 */ 169 public PrivateCredentialPermission(String name, String actions) { 170 super(name); 171 172 if (!"read".equalsIgnoreCase(actions)) 173 throw new IllegalArgumentException 174 (ResourcesMgr.getString("actions.can.only.be.read.")); 175 init(name); 176 } 177 178 /** 179 * Returns the Class name of the Credential associated with this 180 * {@code PrivateCredentialPermission}. 181 * 182 * @return the Class name of the Credential associated with this 183 * {@code PrivateCredentialPermission}. 184 */ 185 public String getCredentialClass() { 186 return credentialClass; 187 } 188 189 /** 190 * Returns the {@code Principal} classes and names 191 * associated with this {@code PrivateCredentialPermission}. 192 * The information is returned as a two-dimensional array (array[x][y]). 193 * The 'x' value corresponds to the number of {@code Principal} 194 * class and name pairs. When (y==0), it corresponds to 195 * the {@code Principal} class value, and when (y==1), 196 * it corresponds to the {@code Principal} name value. 197 * For example, array[0][0] corresponds to the class name of 198 * the first {@code Principal} in the array. array[0][1] 199 * corresponds to the {@code Principal} name of the 200 * first {@code Principal} in the array. 201 * 202 * @return the {@code Principal} class and names associated 203 * with this {@code PrivateCredentialPermission}. 204 */ 205 public String[][] getPrincipals() { 206 207 if (credOwners == null || credOwners.length == 0) { 208 return new String[0][0]; 209 } 210 211 String[][] pArray = new String[credOwners.length][2]; 212 for (int i = 0; i < credOwners.length; i++) { 213 pArray[i][0] = credOwners[i].principalClass; 214 pArray[i][1] = credOwners[i].principalName; 215 } 216 return pArray; 217 } 218 219 /** 220 * Checks if this {@code PrivateCredentialPermission} implies 221 * the specified {@code Permission}. 222 * 223 * <p> 224 * 225 * This method returns true if: 226 * <ul> 227 * <li> {@code p} is an instanceof PrivateCredentialPermission and 228 * <li> the target name for {@code p} is implied by this object's 229 * target name. For example: 230 * <pre> 231 * [* P1 "duke"] implies [a.b.Credential P1 "duke"]. 232 * [C1 P1 "duke"] implies [C1 P1 "duke" P2 "dukette"]. 233 * [C1 P2 "dukette"] implies [C1 P1 "duke" P2 "dukette"]. 234 * </pre> 235 * </ul> 236 * 237 * @param p the {@code Permission} to check against. 238 * 239 * @return true if this {@code PrivateCredentialPermission} implies 240 * the specified {@code Permission}, false if not. 241 */ 242 public boolean implies(Permission p) { 243 244 if (p == null || !(p instanceof PrivateCredentialPermission)) 245 return false; 246 247 PrivateCredentialPermission that = (PrivateCredentialPermission)p; 248 249 if (!impliesCredentialClass(credentialClass, that.credentialClass)) 250 return false; 251 252 return impliesPrincipalSet(credOwners, that.credOwners); 253 } 254 255 /** 256 * Checks two {@code PrivateCredentialPermission} objects for 257 * equality. Checks that {@code obj} is a 258 * {@code PrivateCredentialPermission}, 259 * and has the same credential class as this object, 260 * as well as the same Principals as this object. 261 * The order of the Principals in the respective Permission's 262 * target names is not relevant. 263 * 264 * @param obj the object we are testing for equality with this object. 265 * 266 * @return true if obj is a {@code PrivateCredentialPermission}, 267 * has the same credential class as this object, 268 * and has the same Principals as this object. 269 */ 270 public boolean equals(Object obj) { 271 if (obj == this) 272 return true; 273 274 if (! (obj instanceof PrivateCredentialPermission)) 275 return false; 276 277 PrivateCredentialPermission that = (PrivateCredentialPermission)obj; 278 279 return (this.implies(that) && that.implies(this)); 280 } 281 282 /** 283 * Returns the hash code value for this object. 284 * 285 * @return a hash code value for this object. 286 */ 287 public int hashCode() { 288 return this.credentialClass.hashCode(); 289 } 290 291 /** 292 * Returns the "canonical string representation" of the actions. 293 * This method always returns the String, "read". 294 * 295 * @return the actions (always returns "read"). 296 */ 297 public String getActions() { 298 return "read"; 299 } 300 301 /** 302 * Return a homogeneous collection of PrivateCredentialPermissions 303 * in a {@code PermissionCollection}. 304 * No such {@code PermissionCollection} is defined, 305 * so this method always returns {@code null}. 306 * 307 * @return null in all cases. 308 */ 309 public PermissionCollection newPermissionCollection() { 310 return null; 311 } 312 313 private void init(String name) { 314 315 if (name == null || name.trim().isEmpty()) { 316 throw new IllegalArgumentException("invalid empty name"); 317 } 318 319 ArrayList<CredOwner> pList = new ArrayList<>(); 320 StringTokenizer tokenizer = new StringTokenizer(name, " ", true); 321 String principalClass = null; 322 String principalName = null; 323 324 if (testing) 325 System.out.println("whole name = " + name); 326 327 // get the Credential Class 328 credentialClass = tokenizer.nextToken(); 329 if (testing) 330 System.out.println("Credential Class = " + credentialClass); 331 332 if (tokenizer.hasMoreTokens() == false) { 333 MessageFormat form = new MessageFormat(ResourcesMgr.getString 334 ("permission.name.name.syntax.invalid.")); 335 Object[] source = {name}; 336 throw new IllegalArgumentException 337 (form.format(source) + ResourcesMgr.getString 338 ("Credential.Class.not.followed.by.a.Principal.Class.and.Name")); 339 } 340 341 while (tokenizer.hasMoreTokens()) { 342 343 // skip delimiter 344 tokenizer.nextToken(); 345 346 // get the Principal Class 347 principalClass = tokenizer.nextToken(); 348 if (testing) 349 System.out.println(" Principal Class = " + principalClass); 350 351 if (tokenizer.hasMoreTokens() == false) { 352 MessageFormat form = new MessageFormat(ResourcesMgr.getString 353 ("permission.name.name.syntax.invalid.")); 354 Object[] source = {name}; 355 throw new IllegalArgumentException 356 (form.format(source) + ResourcesMgr.getString 357 ("Principal.Class.not.followed.by.a.Principal.Name")); 358 } 359 360 // skip delimiter 361 tokenizer.nextToken(); 362 363 // get the Principal Name 364 principalName = tokenizer.nextToken(); 365 366 if (!principalName.startsWith("\"")) { 367 MessageFormat form = new MessageFormat(ResourcesMgr.getString 368 ("permission.name.name.syntax.invalid.")); 369 Object[] source = {name}; 370 throw new IllegalArgumentException 371 (form.format(source) + ResourcesMgr.getString 372 ("Principal.Name.must.be.surrounded.by.quotes")); 373 } 374 375 if (!principalName.endsWith("\"")) { 376 377 // we have a name with spaces in it -- 378 // keep parsing until we find the end quote, 379 // and keep the spaces in the name 380 381 while (tokenizer.hasMoreTokens()) { 382 principalName = principalName + tokenizer.nextToken(); 383 if (principalName.endsWith("\"")) 384 break; 385 } 386 387 if (!principalName.endsWith("\"")) { 388 MessageFormat form = new MessageFormat 389 (ResourcesMgr.getString 390 ("permission.name.name.syntax.invalid.")); 391 Object[] source = {name}; 392 throw new IllegalArgumentException 393 (form.format(source) + ResourcesMgr.getString 394 ("Principal.Name.missing.end.quote")); 395 } 396 } 397 398 if (testing) 399 System.out.println("\tprincipalName = '" + principalName + "'"); 400 401 principalName = principalName.substring 402 (1, principalName.length() - 1); 403 404 if (principalClass.equals("*") && 405 !principalName.equals("*")) { 406 throw new IllegalArgumentException(ResourcesMgr.getString 407 ("PrivateCredentialPermission.Principal.Class.can.not.be.a.wildcard.value.if.Principal.Name.is.not.a.wildcard.value")); 408 } 409 410 if (testing) 411 System.out.println("\tprincipalName = '" + principalName + "'"); 412 413 pList.add(new CredOwner(principalClass, principalName)); 414 } 415 416 this.credOwners = new CredOwner[pList.size()]; 417 pList.toArray(this.credOwners); 418 } 419 420 private boolean impliesCredentialClass(String thisC, String thatC) { 421 422 // this should never happen 423 if (thisC == null || thatC == null) 424 return false; 425 426 if (testing) 427 System.out.println("credential class comparison: " + 428 thisC + "/" + thatC); 429 430 if (thisC.equals("*")) 431 return true; 432 433 /** 434 * XXX let's not enable this for now -- 435 * if people want it, we'll enable it later 436 */ 437 /* 438 if (thisC.endsWith("*")) { 439 String cClass = thisC.substring(0, thisC.length() - 2); 440 return thatC.startsWith(cClass); 441 } 442 */ 443 444 return thisC.equals(thatC); 445 } 446 447 private boolean impliesPrincipalSet(CredOwner[] thisP, CredOwner[] thatP) { 448 449 // this should never happen 450 if (thisP == null || thatP == null) 451 return false; 452 453 if (thatP.length == 0) 454 return true; 455 456 if (thisP.length == 0) 457 return false; 458 459 for (int i = 0; i < thisP.length; i++) { 460 boolean foundMatch = false; 461 for (int j = 0; j < thatP.length; j++) { 462 if (thisP[i].implies(thatP[j])) { 463 foundMatch = true; 464 break; 465 } 466 } 467 if (!foundMatch) { 468 return false; 469 } 470 } 471 return true; 472 } 473 474 /** 475 * Reads this object from a stream (i.e., deserializes it) 476 */ 477 private void readObject(java.io.ObjectInputStream s) throws 478 java.io.IOException, 479 ClassNotFoundException { 480 481 s.defaultReadObject(); 482 483 // perform new initialization from the permission name 484 485 if (getName().indexOf(' ') == -1 && getName().indexOf('"') == -1) { 486 487 // name only has a credential class specified 488 credentialClass = getName(); 489 credOwners = EMPTY_PRINCIPALS; 490 491 } else { 492 493 // perform regular initialization 494 init(getName()); 495 } 496 } 497 498 /** 499 * @serial include 500 */ 501 static class CredOwner implements java.io.Serializable { 502 503 private static final long serialVersionUID = -5607449830436408266L; 504 505 /** 506 * @serial 507 */ 508 String principalClass; 509 /** 510 * @serial 511 */ 512 String principalName; 513 514 CredOwner(String principalClass, String principalName) { 515 this.principalClass = principalClass; 516 this.principalName = principalName; 517 } 518 519 public boolean implies(Object obj) { 520 if (obj == null || !(obj instanceof CredOwner)) 521 return false; 522 523 CredOwner that = (CredOwner)obj; 524 525 if (principalClass.equals("*") || 526 principalClass.equals(that.principalClass)) { 527 528 if (principalName.equals("*") || 529 principalName.equals(that.principalName)) { 530 return true; 531 } 532 } 533 534 /** 535 * XXX no code yet to support a.b.* 536 */ 537 538 return false; 539 } 540 541 public String toString() { 542 MessageFormat form = new MessageFormat(ResourcesMgr.getString 543 ("CredOwner.Principal.Class.class.Principal.Name.name")); 544 Object[] source = {principalClass, principalName}; 545 return (form.format(source)); 546 } 547 } 548 }