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 }