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
26package javax.security.auth;
27
28import java.util.*;
29import java.text.MessageFormat;
30import java.security.Permission;
31import java.security.PermissionCollection;
32import java.security.Principal;
33import 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 */
105public 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().length() == 0) {
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}
549