1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.apache.harmony.javax.security.auth;
19
20import java.io.IOException;
21import java.io.ObjectInputStream;
22import java.io.Serializable;
23import java.security.Permission;
24import java.security.PermissionCollection;
25import java.security.Principal;
26import java.util.Set;
27
28
29
30/**
31 * Protects private credential objects belonging to a {@code Subject}. It has
32 * only one action which is "read". The target name of this permission has a
33 * special syntax:
34 *
35 * <pre>
36 * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
37 * </pre>
38 *
39 * First it states a credential class and is followed then by a list of one or
40 * more principals identifying the subject.
41 * <p>
42 * The principals on their part are specified as the name of the {@code
43 * Principal} class followed by the principal name in quotes. For example, the
44 * following file may define permission to read the private credentials of a
45 * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\""
46 * <p>
47 * The syntax also allows the use of the wildcard "*" in place of {@code
48 * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}.
49 *
50 * @see Principal
51 */
52public final class PrivateCredentialPermission extends Permission {
53
54    private static final long serialVersionUID = 5284372143517237068L;
55
56    // allowed action
57    private static final String READ = "read"; //$NON-NLS-1$
58
59    private String credentialClass;
60
61    // current offset
62    private transient int offset;
63
64    // owners set
65    private transient CredOwner[] set;
66
67    /**
68     * Creates a new permission for private credentials specified by the target
69     * name {@code name} and an {@code action}. The action is always
70     * {@code "read"}.
71     *
72     * @param name
73     *            the target name of the permission.
74     * @param action
75     *            the action {@code "read"}.
76     */
77    public PrivateCredentialPermission(String name, String action) {
78        super(name);
79        if (READ.equalsIgnoreCase(action)) {
80            initTargetName(name);
81        } else {
82            throw new IllegalArgumentException("auth.11"); //$NON-NLS-1$
83        }
84    }
85
86    /**
87     * Creates a {@code PrivateCredentialPermission} from the {@code Credential}
88     * class and set of principals.
89     *
90     * @param credentialClass
91     *            the credential class name.
92     * @param principals
93     *            the set of principals.
94     */
95    PrivateCredentialPermission(String credentialClass, Set<Principal> principals) {
96        super(credentialClass);
97        this.credentialClass = credentialClass;
98
99        set = new CredOwner[principals.size()];
100        for (Principal p : principals) {
101            CredOwner element = new CredOwner(p.getClass().getName(), p.getName());
102            // check for duplicate elements
103            boolean found = false;
104            for (int ii = 0; ii < offset; ii++) {
105                if (set[ii].equals(element)) {
106                    found = true;
107                    break;
108                }
109            }
110            if (!found) {
111                set[offset++] = element;
112            }
113        }
114    }
115
116    /**
117     * Initialize a PrivateCredentialPermission object and checks that a target
118     * name has a correct format: CredentialClass 1*(PrincipalClass
119     * "PrincipalName")
120     */
121    private void initTargetName(String name) {
122
123        if (name == null) {
124            throw new NullPointerException("auth.0E"); //$NON-NLS-1$
125        }
126
127        // check empty string
128        name = name.trim();
129        if (name.length() == 0) {
130            throw new IllegalArgumentException("auth.0F"); //$NON-NLS-1$
131        }
132
133        // get CredentialClass
134        int beg = name.indexOf(' ');
135        if (beg == -1) {
136            throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
137        }
138        credentialClass = name.substring(0, beg);
139
140        // get a number of pairs: PrincipalClass "PrincipalName"
141        beg++;
142        int count = 0;
143        int nameLength = name.length();
144        for (int i, j = 0; beg < nameLength; beg = j + 2, count++) {
145            i = name.indexOf(' ', beg);
146            j = name.indexOf('"', i + 2);
147
148            if (i == -1 || j == -1 || name.charAt(i + 1) != '"') {
149                throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
150            }
151        }
152
153        // name MUST have one pair at least
154        if (count < 1) {
155            throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
156        }
157
158        beg = name.indexOf(' ');
159        beg++;
160
161        // populate principal set with instances of CredOwner class
162        String principalClass;
163        String principalName;
164
165        set = new CredOwner[count];
166        for (int index = 0, i, j; index < count; beg = j + 2, index++) {
167            i = name.indexOf(' ', beg);
168            j = name.indexOf('"', i + 2);
169
170            principalClass = name.substring(beg, i);
171            principalName = name.substring(i + 2, j);
172
173            CredOwner element = new CredOwner(principalClass, principalName);
174            // check for duplicate elements
175            boolean found = false;
176            for (int ii = 0; ii < offset; ii++) {
177                if (set[ii].equals(element)) {
178                    found = true;
179                    break;
180                }
181            }
182            if (!found) {
183                set[offset++] = element;
184            }
185        }
186    }
187
188    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
189        ois.defaultReadObject();
190        initTargetName(getName());
191    }
192
193    /**
194     * Returns the principal's classes and names associated with this {@code
195     * PrivateCredentialPermission} as a two dimensional array. The first
196     * dimension of the array corresponds to the number of principals. The
197     * second dimension defines either the name of the {@code PrincipalClass}
198     * [x][0] or the value of {@code PrincipalName} [x][1].
199     * <p>
200     * This corresponds to the the target name's syntax:
201     *
202     * <pre>
203     * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
204     * </pre>
205     *
206     * @return the principal classes and names associated with this {@code
207     *         PrivateCredentialPermission}.
208     */
209    public String[][] getPrincipals() {
210
211        String[][] s = new String[offset][2];
212
213        for (int i = 0; i < s.length; i++) {
214            s[i][0] = set[i].principalClass;
215            s[i][1] = set[i].principalName;
216        }
217        return s;
218    }
219
220    @Override
221    public String getActions() {
222        return READ;
223    }
224
225    /**
226     * Returns the class name of the credential associated with this permission.
227     *
228     * @return the class name of the credential associated with this permission.
229     */
230    public String getCredentialClass() {
231        return credentialClass;
232    }
233
234    @Override
235    public int hashCode() {
236        int hash = 0;
237        for (int i = 0; i < offset; i++) {
238            hash = hash + set[i].hashCode();
239        }
240        return getCredentialClass().hashCode() + hash;
241    }
242
243    @Override
244    public boolean equals(Object obj) {
245        if (obj == this) {
246            return true;
247        }
248
249        if (obj == null || this.getClass() != obj.getClass()) {
250            return false;
251        }
252
253        PrivateCredentialPermission that = (PrivateCredentialPermission) obj;
254
255        return credentialClass.equals(that.credentialClass) && (offset == that.offset)
256                && sameMembers(set, that.set, offset);
257    }
258
259    @Override
260    public boolean implies(Permission permission) {
261
262        if (permission == null || this.getClass() != permission.getClass()) {
263            return false;
264        }
265
266        PrivateCredentialPermission that = (PrivateCredentialPermission) permission;
267
268        if (!("*".equals(credentialClass) || credentialClass //$NON-NLS-1$
269                .equals(that.getCredentialClass()))) {
270            return false;
271        }
272
273        if (that.offset == 0) {
274            return true;
275        }
276
277        CredOwner[] thisCo = set;
278        CredOwner[] thatCo = that.set;
279        int thisPrincipalsSize = offset;
280        int thatPrincipalsSize = that.offset;
281        for (int i = 0, j; i < thisPrincipalsSize; i++) {
282            for (j = 0; j < thatPrincipalsSize; j++) {
283                if (thisCo[i].implies(thatCo[j])) {
284                    break;
285                }
286            }
287            if (j == thatCo.length) {
288                return false;
289            }
290        }
291        return true;
292    }
293
294    @Override
295    public PermissionCollection newPermissionCollection() {
296        return null;
297    }
298
299    /**
300     * Returns true if the two arrays have the same length, and every member of
301     * one array is contained in another array
302     */
303    private boolean sameMembers(Object[] ar1, Object[] ar2, int length) {
304        if (ar1 == null && ar2 == null) {
305            return true;
306        }
307        if (ar1 == null || ar2 == null) {
308            return false;
309        }
310        boolean found;
311        for (int i = 0; i < length; i++) {
312            found = false;
313            for (int j = 0; j < length; j++) {
314                if (ar1[i].equals(ar2[j])) {
315                    found = true;
316                    break;
317                }
318            }
319            if (!found) {
320                return false;
321            }
322        }
323        return true;
324    }
325
326    private static final class CredOwner implements Serializable {
327
328        private static final long serialVersionUID = -5607449830436408266L;
329
330        String principalClass;
331
332        String principalName;
333
334        // whether class name contains wildcards
335        private transient boolean isClassWildcard;
336
337        // whether pname contains wildcards
338        private transient boolean isPNameWildcard;
339
340        // Creates a new CredOwner with the specified Principal Class and Principal Name
341        CredOwner(String principalClass, String principalName) {
342            super();
343            if ("*".equals(principalClass)) { //$NON-NLS-1$
344                isClassWildcard = true;
345            }
346
347            if ("*".equals(principalName)) { //$NON-NLS-1$
348                isPNameWildcard = true;
349            }
350
351            if (isClassWildcard && !isPNameWildcard) {
352                throw new IllegalArgumentException("auth.12"); //$NON-NLS-1$
353            }
354
355            this.principalClass = principalClass;
356            this.principalName = principalName;
357        }
358
359        // Checks if this CredOwner implies the specified Object.
360        boolean implies(Object obj) {
361            if (obj == this) {
362                return true;
363            }
364
365            CredOwner co = (CredOwner) obj;
366
367            if (isClassWildcard || principalClass.equals(co.principalClass)) {
368                if (isPNameWildcard || principalName.equals(co.principalName)) {
369                    return true;
370                }
371            }
372            return false;
373        }
374
375        // Checks two CredOwner objects for equality.
376        @Override
377        public boolean equals(Object obj) {
378            if (obj == this) {
379                return true;
380            }
381            if (obj instanceof CredOwner) {
382                CredOwner that = (CredOwner) obj;
383                return principalClass.equals(that.principalClass)
384                    && principalName.equals(that.principalName);
385            }
386            return false;
387        }
388
389        // Returns the hash code value for this object.
390        @Override
391        public int hashCode() {
392            return principalClass.hashCode() + principalName.hashCode();
393        }
394    }
395}
396