      1 /*
      2  * Copyright (c) 1999, 2019, 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     @java.io.Serial
    108     private static final long serialVersionUID = 5284372143517237068L;
    109
    110     private static final CredOwner[] EMPTY_PRINCIPALS = new CredOwner[0];
    111
    112     /**
    113      * @serial
    114      */
    115     private String credentialClass;
    116
    117     /**
    118      * @serial The Principals associated with this permission.
    119      *          The set contains elements of type,
    120      *          {@code PrivateCredentialPermission.CredOwner}.
    121      */
    122     @SuppressWarnings("serial") // Not statically typed as Serializable
    123     private Set<Principal> principals;  // ignored - kept around for compatibility
    124     private transient CredOwner[] credOwners;
    125
    126     /**
    127      * @serial
    128      */
    129     private boolean testing = false;
    130
    131     /**
    132      * Create a new {@code PrivateCredentialPermission}
    133      * with the specified {@code credentialClass} and Principals.
    134      */
    135     PrivateCredentialPermission(String credentialClass,
    136                         Set<Principal> principals) {
    137
    138         super(credentialClass);
    139         this.credentialClass = credentialClass;
    140
    141         synchronized(principals) {
    142             if (principals.size() == 0) {
    143                 this.credOwners = EMPTY_PRINCIPALS;
    144             } else {
    145                 this.credOwners = new CredOwner[principals.size()];
    146                 int index = 0;
    147                 Iterator<Principal> i = principals.iterator();
    148                 while (i.hasNext()) {
    149                     Principal p = i.next();
    150                     this.credOwners[index++] = new CredOwner
    151                                                 (p.getClass().getName(),
    152                                                 p.getName());
    153                 }
    154             }
    155         }
    156     }
    157
    158     /**
    159      * Creates a new {@code PrivateCredentialPermission}
    160      * with the specified {@code name}.  The {@code name}
    161      * specifies both a Credential class and a {@code Principal} Set.
    162      *
    163      * @param name the name specifying the Credential class and
    164      *          {@code Principal} Set.
    165      *
    166      * @param actions the actions specifying that the Credential can be read.
    167      *
    168      * @throws IllegalArgumentException if {@code name} does not conform
    169      *          to the correct syntax or if {@code actions} is not "read".
    170      */
    171     public PrivateCredentialPermission(String name, String actions) {
    172         super(name);
    173
    174         if (!"read".equalsIgnoreCase(actions))
    175             throw new IllegalArgumentException
    176                 (ResourcesMgr.getString("actions.can.only.be.read."));
    177         init(name);
    178     }
    179
    180     /**
    181      * Returns the Class name of the Credential associated with this
    182      * {@code PrivateCredentialPermission}.
    183      *
    184      * @return the Class name of the Credential associated with this
    185      *          {@code PrivateCredentialPermission}.
    186      */
    187     public String getCredentialClass() {
    188         return credentialClass;
    189     }
    190
    191     /**
    192      * Returns the {@code Principal} classes and names
    193      * associated with this {@code PrivateCredentialPermission}.
    194      * The information is returned as a two-dimensional array (array[x][y]).
    195      * The 'x' value corresponds to the number of {@code Principal}
    196      * class and name pairs.  When (y==0), it corresponds to
    197      * the {@code Principal} class value, and when (y==1),
    198      * it corresponds to the {@code Principal} name value.
    199      * For example, array[0][0] corresponds to the class name of
    200      * the first {@code Principal} in the array.  array[0][1]
    201      * corresponds to the {@code Principal} name of the
    202      * first {@code Principal} in the array.
    203      *
    204      * @return the {@code Principal} class and names associated
    205      *          with this {@code PrivateCredentialPermission}.
    206      */
    207     public String[][] getPrincipals() {
    208
    209         if (credOwners == null || credOwners.length == 0) {
    210             return new String[0][0];
    211         }
    212
    213         String[][] pArray = new String[credOwners.length][2];
    214         for (int i = 0; i < credOwners.length; i++) {
    215             pArray[i][0] = credOwners[i].principalClass;
    216             pArray[i][1] = credOwners[i].principalName;
    217         }
    218         return pArray;
    219     }
    220
    221     /**
    222      * Checks if this {@code PrivateCredentialPermission} implies
    223      * the specified {@code Permission}.
    224      *
    225      * <p>
    226      *
    227      * This method returns true if:
    228      * <ul>
    229      * <li> {@code p} is an instanceof PrivateCredentialPermission and
    230      * <li> the target name for {@code p} is implied by this object's
    231      *          target name.  For example:
    232      * <pre>
    233      *  [* P1 "duke"] implies [a.b.Credential P1 "duke"].
    234      *  [C1 P1 "duke"] implies [C1 P1 "duke" P2 "dukette"].
    235      *  [C1 P2 "dukette"] implies [C1 P1 "duke" P2 "dukette"].
    236      * </pre>
    237      * </ul>
    238      *
    239      * @param p the {@code Permission} to check against.
    240      *
    241      * @return true if this {@code PrivateCredentialPermission} implies
    242      * the specified {@code Permission}, false if not.
    243      */
    244     public boolean implies(Permission p) {
    245
    246         if (p == null || !(p instanceof PrivateCredentialPermission))
    247             return false;
    248
    249         PrivateCredentialPermission that = (PrivateCredentialPermission)p;
    250
    251         if (!impliesCredentialClass(credentialClass, that.credentialClass))
    252             return false;
    253
    254         return impliesPrincipalSet(credOwners, that.credOwners);
    255     }
    256
    257     /**
    258      * Checks two {@code PrivateCredentialPermission} objects for
    259      * equality.  Checks that {@code obj} is a
    260      * {@code PrivateCredentialPermission},
    261      * and has the same credential class as this object,
    262      * as well as the same Principals as this object.
    263      * The order of the Principals in the respective Permission's
    264      * target names is not relevant.
    265      *
    266      * @param obj the object we are testing for equality with this object.
    267      *
    268      * @return true if obj is a {@code PrivateCredentialPermission},
    269      *          has the same credential class as this object,
    270      *          and has the same Principals as this object.
    271      */
    272     public boolean equals(Object obj) {
    273         if (obj == this)
    274             return true;
    275
    276         if (! (obj instanceof PrivateCredentialPermission))
    277             return false;
    278
    279         PrivateCredentialPermission that = (PrivateCredentialPermission)obj;
    280
    281         return (this.implies(that) && that.implies(this));
    282     }
    283
    284     /**
    285      * Returns the hash code value for this object.
    286      *
    287      * @return a hash code value for this object.
    288      */
    289     public int hashCode() {
    290         return this.credentialClass.hashCode();
    291     }
    292
    293     /**
    294      * Returns the "canonical string representation" of the actions.
    295      * This method always returns the String, "read".
    296      *
    297      * @return the actions (always returns "read").
    298      */
    299     public String getActions() {
    300         return "read";
    301     }
    302
    303     /**
    304      * Return a homogeneous collection of PrivateCredentialPermissions
    305      * in a {@code PermissionCollection}.
    306      * No such {@code PermissionCollection} is defined,
    307      * so this method always returns {@code null}.
    308      *
    309      * @return null in all cases.
    310      */
    311     public PermissionCollection newPermissionCollection() {
    312         return null;
    313     }
    314
    315     private void init(String name) {
    316
    317         if (name == null || name.trim().isEmpty()) {
    318             throw new IllegalArgumentException("invalid empty name");
    319         }
    320
    321         ArrayList<CredOwner> pList = new ArrayList<>();
    322         StringTokenizer tokenizer = new StringTokenizer(name, " ", true);
    323         String principalClass = null;
    324         String principalName = null;
    325
    326         if (testing)
    327             System.out.println("whole name = " + name);
    328
    329         // get the Credential Class
    330         credentialClass = tokenizer.nextToken();
    331         if (testing)
    332             System.out.println("Credential Class = " + credentialClass);
    333
    334         if (tokenizer.hasMoreTokens() == false) {
    335             MessageFormat form = new MessageFormat(ResourcesMgr.getString
    336                 ("permission.name.name.syntax.invalid."));
    337             Object[] source = {name};
    338             throw new IllegalArgumentException
    339                 (form.format(source) + ResourcesMgr.getString
    340                         ("Credential.Class.not.followed.by.a.Principal.Class.and.Name"));
    341         }
    342
    343         while (tokenizer.hasMoreTokens()) {
    344
    345             // skip delimiter
    346             tokenizer.nextToken();
    347
    348             // get the Principal Class
    349             principalClass = tokenizer.nextToken();
    350             if (testing)
    351                 System.out.println("    Principal Class = " + principalClass);
    352
    353             if (tokenizer.hasMoreTokens() == false) {
    354                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
    355                         ("permission.name.name.syntax.invalid."));
    356                 Object[] source = {name};
    357                 throw new IllegalArgumentException
    358                         (form.format(source) + ResourcesMgr.getString
    359                         ("Principal.Class.not.followed.by.a.Principal.Name"));
    360             }
    361
    362             // skip delimiter
    363             tokenizer.nextToken();
    364
    365             // get the Principal Name
    366             principalName = tokenizer.nextToken();
    367
    368             if (!principalName.startsWith("\"")) {
    369                 MessageFormat form = new MessageFormat(ResourcesMgr.getString
    370                         ("permission.name.name.syntax.invalid."));
    371                 Object[] source = {name};
    372                 throw new IllegalArgumentException
    373                         (form.format(source) + ResourcesMgr.getString
    374                         ("Principal.Name.must.be.surrounded.by.quotes"));
    375             }
    376
    377             if (!principalName.endsWith("\"")) {
    378
    379                 // we have a name with spaces in it --
    380                 // keep parsing until we find the end quote,
    381                 // and keep the spaces in the name
    382
    383                 while (tokenizer.hasMoreTokens()) {
    384                     principalName = principalName + tokenizer.nextToken();
    385                     if (principalName.endsWith("\""))
    386                         break;
    387                 }
    388
    389                 if (!principalName.endsWith("\"")) {
    390                     MessageFormat form = new MessageFormat
    391                         (ResourcesMgr.getString
    392                         ("permission.name.name.syntax.invalid."));
    393                     Object[] source = {name};
    394                     throw new IllegalArgumentException
    395                         (form.format(source) + ResourcesMgr.getString
    396                                 ("Principal.Name.missing.end.quote"));
    397                 }
    398             }
    399
    400             if (testing)
    401                 System.out.println("\tprincipalName = '" + principalName + "'");
    402
    403             principalName = principalName.substring
    404                                         (1, principalName.length() - 1);
    405
    406             if (principalClass.equals("*") &&
    407                 !principalName.equals("*")) {
    408                     throw new IllegalArgumentException(ResourcesMgr.getString
    409                         ("PrivateCredentialPermission.Principal.Class.can.not.be.a.wildcard.value.if.Principal.Name.is.not.a.wildcard.value"));
    410             }
    411
    412             if (testing)
    413                 System.out.println("\tprincipalName = '" + principalName + "'");
    414
    415             pList.add(new CredOwner(principalClass, principalName));
    416         }
    417
    418         this.credOwners = new CredOwner[pList.size()];
    419         pList.toArray(this.credOwners);
    420     }
    421
    422     private boolean impliesCredentialClass(String thisC, String thatC) {
    423
    424         // this should never happen
    425         if (thisC == null || thatC == null)
    426             return false;
    427
    428         if (testing)
    429             System.out.println("credential class comparison: " +
    430                                 thisC + "/" + thatC);
    431
    432         if (thisC.equals("*"))
    433             return true;
    434
    435         /**
    436          * XXX let's not enable this for now --
    437          *      if people want it, we'll enable it later
    438          */
    439         /*
    440         if (thisC.endsWith("*")) {
    441             String cClass = thisC.substring(0, thisC.length() - 2);
    442             return thatC.startsWith(cClass);
    443         }
    444         */
    445
    446         return thisC.equals(thatC);
    447     }
    448
    449     private boolean impliesPrincipalSet(CredOwner[] thisP, CredOwner[] thatP) {
    450
    451         // this should never happen
    452         if (thisP == null || thatP == null)
    453             return false;
    454
    455         if (thatP.length == 0)
    456             return true;
    457
    458         if (thisP.length == 0)
    459             return false;
    460
    461         for (int i = 0; i < thisP.length; i++) {
    462             boolean foundMatch = false;
    463             for (int j = 0; j < thatP.length; j++) {
    464                 if (thisP[i].implies(thatP[j])) {
    465                     foundMatch = true;
    466                     break;
    467                 }
    468             }
    469             if (!foundMatch) {
    470                 return false;
    471             }
    472         }
    473         return true;
    474     }
    475
    476     /**
    477      * Reads this object from a stream (i.e., deserializes it)
    478      */
    479     @java.io.Serial
    480     private void readObject(java.io.ObjectInputStream s) throws
    481                                         java.io.IOException,
    482                                         ClassNotFoundException {
    483
    484         s.defaultReadObject();
    485
    486         // perform new initialization from the permission name
    487
    488         if (getName().indexOf(' ') == -1 && getName().indexOf('"') == -1) {
    489
    490             // name only has a credential class specified
    491             credentialClass = getName();
    492             credOwners = EMPTY_PRINCIPALS;
    493
    494         } else {
    495
    496             // perform regular initialization
    497             init(getName());
    498         }
    499     }
    500
    501     /**
    502      * @serial include
    503      */
    504     static class CredOwner implements java.io.Serializable {
    505
    506         @java.io.Serial
    507         private static final long serialVersionUID = -5607449830436408266L;
    508
    509         /**
    510          * @serial
    511          */
    512         String principalClass;
    513         /**
    514          * @serial
    515          */
    516         String principalName;
    517
    518         CredOwner(String principalClass, String principalName) {
    519             this.principalClass = principalClass;
    520             this.principalName = principalName;
    521         }
    522
    523         public boolean implies(Object obj) {
    524             if (obj == null || !(obj instanceof CredOwner))
    525                 return false;
    526
    527             CredOwner that = (CredOwner)obj;
    528
    529             if (principalClass.equals("*") ||
    530                 principalClass.equals(that.principalClass)) {
    531
    532                 if (principalName.equals("*") ||
    533                     principalName.equals(that.principalName)) {
    534                     return true;
    535                 }
    536             }
    537
    538             /**
    539              * XXX no code yet to support a.b.*
    540              */
    541
    542             return false;
    543         }
    544
    545         public String toString() {
    546             MessageFormat form = new MessageFormat(ResourcesMgr.getString
    547                 ("CredOwner.Principal.Class.class.Principal.Name.name"));
    548             Object[] source = {principalClass, principalName};
    549             return (form.format(source));
    550         }
    551     }
    552 }