summaryrefslogtreecommitdiffstats
path: root/base/util/src/netscape/security/acl/AclImpl.java
blob: 76750b7b9fa907678c9dffb2c362fb07c9d39cc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package netscape.security.acl;

import java.security.Principal;
import java.security.acl.Acl;
import java.security.acl.AclEntry;
import java.security.acl.Group;
import java.security.acl.NotOwnerException;
import java.security.acl.Permission;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Vector;

/**
 * An Access Control List (ACL) is encapsulated by this class.
 * 
 * @author Satish Dharmaraj
 */
public class AclImpl extends OwnerImpl implements Acl {
    //
    // Maintain four tables. one each for positive and negative
    // ACLs. One each depending on whether the entity is a group
    // or principal.
    //
    private Hashtable<Principal, AclEntry> allowedUsersTable = new Hashtable<Principal, AclEntry>(23);
    private Hashtable<Principal, AclEntry> allowedGroupsTable = new Hashtable<Principal, AclEntry>(23);
    private Hashtable<Principal, AclEntry> deniedUsersTable = new Hashtable<Principal, AclEntry>(23);
    private Hashtable<Principal, AclEntry> deniedGroupsTable = new Hashtable<Principal, AclEntry>(23);
    private String aclName = null;
    private Vector<Permission> zeroSet = new Vector<Permission>(1, 1);

    /**
     * Constructor for creating an empty ACL.
     */
    public AclImpl(Principal owner, String name) {
        super(owner);
        try {
            setName(owner, name);
        } catch (Exception e) {
        }
    }

    /**
     * Sets the name of the ACL.
     * 
     * @param caller the principal who is invoking this method.
     * @param name the name of the ACL.
     * @exception NotOwnerException if the caller principal is
     *                not on the owners list of the Acl.
     */
    public void setName(Principal caller, String name)
            throws NotOwnerException {
        if (!isOwner(caller))
            throw new NotOwnerException();

        aclName = name;
    }

    /**
     * Returns the name of the ACL.
     * 
     * @return the name of the ACL.
     */
    public String getName() {
        return aclName;
    }

    /**
     * Adds an ACL entry to this ACL. An entry associates a
     * group or a principal with a set of permissions. Each
     * user or group can have one positive ACL entry and one
     * negative ACL entry. If there is one of the type (negative
     * or positive) already in the table, a false value is returned.
     * The caller principal must be a part of the owners list of
     * the ACL in order to invoke this method.
     * 
     * @param caller the principal who is invoking this method.
     * @param entry the ACL entry that must be added to the ACL.
     * @return true on success, false if the entry is already present.
     * @exception NotOwnerException if the caller principal
     *                is not on the owners list of the Acl.
     */
    public synchronized boolean addEntry(Principal caller, AclEntry entry)
            throws NotOwnerException {
        if (!isOwner(caller))
            throw new NotOwnerException();

        Hashtable<Principal, AclEntry> aclTable = findTable(entry);
        Principal key = entry.getPrincipal();

        if (aclTable.get(key) != null)
            return false;

        aclTable.put(key, entry);
        return true;
    }

    /**
     * Removes an ACL entry from this ACL.
     * The caller principal must be a part of the owners list of the ACL
     * in order to invoke this method.
     * 
     * @param caller the principal who is invoking this method.
     * @param entry the ACL entry that must be removed from the ACL.
     * @return true on success, false if the entry is not part of the ACL.
     * @exception NotOwnerException if the caller principal is not
     *                the owners list of the Acl.
     */
    public synchronized boolean removeEntry(Principal caller, AclEntry entry)
            throws NotOwnerException {
        if (!isOwner(caller))
            throw new NotOwnerException();

        Hashtable<Principal, AclEntry> aclTable = findTable(entry);
        Object key = entry.getPrincipal();

        Object o = aclTable.remove(key);
        return (o != null);
    }

    /**
     * This method returns the set of allowed permissions for the
     * specified principal. This set of allowed permissions is calculated
     * as follows:
     * 
     * If there is no entry for a group or a principal an empty permission
     * set is assumed.
     * 
     * The group positive permission set is the union of all
     * the positive permissions of each group that the individual belongs to.
     * The group negative permission set is the union of all
     * the negative permissions of each group that the individual belongs to.
     * If there is a specific permission that occurs in both
     * the postive permission set and the negative permission set,
     * it is removed from both. The group positive and negatoive permission
     * sets are calculated.
     * 
     * The individial positive permission set and the individual negative
     * permission set is then calculated. Again abscence of an entry means
     * the empty set.
     * 
     * The set of permissions granted to the principal is then calculated using
     * the simple rule: Individual permissions always override the Group permissions.
     * Specifically, individual negative permission set (specific
     * denial of permissions) overrides the group positive permission set.
     * And the individual positive permission set override the group negative
     * permission set.
     * 
     * @param user the principal for which the ACL entry is returned.
     * @return The resulting permission set that the principal is allowed.
     */
    public synchronized Enumeration<Permission> getPermissions(Principal user) {

        Enumeration<Permission> individualPositive;
        Enumeration<Permission> individualNegative;
        Enumeration<Permission> groupPositive;
        Enumeration<Permission> groupNegative;

        //
        // canonicalize the sets. That is remove common permissions from
        // positive and negative sets.
        //
        groupPositive = subtract(getGroupPositive(user), getGroupNegative(user));
        groupNegative = subtract(getGroupNegative(user), getGroupPositive(user));
        individualPositive = subtract(getIndividualPositive(user), getIndividualNegative(user));
        individualNegative = subtract(getIndividualNegative(user), getIndividualPositive(user));

        //
        // net positive permissions is individual positive permissions
        // plus (group positive - individual negative).
        //
        Enumeration<Permission> temp1 = subtract(groupPositive, individualNegative);
        Enumeration<Permission> netPositive = union(individualPositive, temp1);

        // recalculate the enumeration since we lost it in performing the
        // subtraction
        //
        individualPositive = subtract(getIndividualPositive(user), getIndividualNegative(user));
        individualNegative = subtract(getIndividualNegative(user), getIndividualPositive(user));

        //
        // net negative permissions is individual negative permissions
        // plus (group negative - individual positive).
        //
        temp1 = subtract(groupNegative, individualPositive);
        Enumeration<Permission> netNegative = union(individualNegative, temp1);

        return subtract(netPositive, netNegative);
    }

    /**
     * This method checks whether or not the specified principal
     * has the required permission. If permission is denied
     * permission false is returned, a true value is returned otherwise.
     * This method does not authenticate the principal. It presumes that
     * the principal is a valid authenticated principal.
     * 
     * @param principal the name of the authenticated principal
     * @param permission the permission that the principal must have.
     * @return true of the principal has the permission desired, false
     *         otherwise.
     */
    public boolean checkPermission(Principal principal, Permission permission) {
        Enumeration<Permission> permSet = getPermissions(principal);
        while (permSet.hasMoreElements()) {
            Permission p = (Permission) permSet.nextElement();
            if (p.equals(permission))
                return true;
        }
        return false;
    }

    /**
     * returns an enumeration of the entries in this ACL.
     */
    public synchronized Enumeration<AclEntry> entries() {
        return new AclEnumerator(this,
                 allowedUsersTable, allowedGroupsTable,
                 deniedUsersTable, deniedGroupsTable);
    }

    /**
     * return a stringified version of the
     * ACL.
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        Enumeration<AclEntry> entries = entries();
        while (entries.hasMoreElements()) {
            AclEntry entry = (AclEntry) entries.nextElement();
            sb.append(entry.toString().trim());
            sb.append("\n");
        }

        return sb.toString();
    }

    //
    // Find the table that this entry belongs to. There are 4 
    // tables that are maintained. One each for postive and 
    // negative ACLs and one each for groups and users. 
    // This method figures out which 
    // table is the one that this AclEntry belongs to.
    //
    private Hashtable<Principal, AclEntry> findTable(AclEntry entry) {
        Hashtable<Principal, AclEntry> aclTable = null;

        Principal p = entry.getPrincipal();
        if (p instanceof Group) {
            if (entry.isNegative())
                aclTable = deniedGroupsTable;
            else
                aclTable = allowedGroupsTable;
        } else {
            if (entry.isNegative())
                aclTable = deniedUsersTable;
            else
                aclTable = allowedUsersTable;
        }
        return aclTable;
    }

    //
    // returns the set e1 U e2.
    //
    private <T> Enumeration<T> union(Enumeration<T> e1, Enumeration<T> e2) {
        Vector<T> v = new Vector<T>(20, 20);

        while (e1.hasMoreElements())
            v.addElement(e1.nextElement());

        while (e2.hasMoreElements()) {
            T o = e2.nextElement();
            if (!v.contains(o))
                v.addElement(o);
        }

        return v.elements();
    }

    //
    // returns the set e1 - e2.
    //
    private <T> Enumeration<T> subtract(Enumeration<T> e1, Enumeration<T> e2) {
        Vector<T> v = new Vector<T>(20, 20);

        while (e1.hasMoreElements())
            v.addElement(e1.nextElement());

        while (e2.hasMoreElements()) {
            T o = e2.nextElement();
            if (v.contains(o))
                v.removeElement(o);
        }

        return v.elements();
    }

    private Enumeration<Permission> getGroupPositive(Principal user) {
        Enumeration<Permission> groupPositive = zeroSet.elements();
        Enumeration<Principal> e = allowedGroupsTable.keys();
        while (e.hasMoreElements()) {
            Group g = (Group) e.nextElement();
            if (g.isMember(user)) {
                AclEntry ae = (AclEntry) allowedGroupsTable.get(g);
                groupPositive = union(ae.permissions(), groupPositive);
            }
        }
        return groupPositive;
    }

    private Enumeration<Permission> getGroupNegative(Principal user) {
        Enumeration<Permission> groupNegative = zeroSet.elements();
        Enumeration<Principal> e = deniedGroupsTable.keys();
        while (e.hasMoreElements()) {
            Group g = (Group) e.nextElement();
            if (g.isMember(user)) {
                AclEntry ae = (AclEntry) deniedGroupsTable.get(g);
                groupNegative = union(ae.permissions(), groupNegative);
            }
        }
        return groupNegative;
    }

    private Enumeration<Permission> getIndividualPositive(Principal user) {
        Enumeration<Permission> individualPositive = zeroSet.elements();
        AclEntry ae = (AclEntry) allowedUsersTable.get(user);
        if (ae != null)
            individualPositive = ae.permissions();
        return individualPositive;
    }

    private Enumeration<Permission> getIndividualNegative(Principal user) {
        Enumeration<Permission> individualNegative = zeroSet.elements();
        AclEntry ae = (AclEntry) deniedUsersTable.get(user);
        if (ae != null)
            individualNegative = ae.permissions();
        return individualNegative;
    }
}

final class AclEnumerator implements Enumeration<AclEntry> {
    Acl acl;
    Enumeration<AclEntry> u1, u2, g1, g2;

    AclEnumerator(Acl acl, Hashtable<Principal, AclEntry> u1, Hashtable<Principal, AclEntry> g1,
            Hashtable<Principal, AclEntry> u2, Hashtable<Principal, AclEntry> g2) {
        this.acl = acl;
        this.u1 = u1.elements();
        this.u2 = u2.elements();
        this.g1 = g1.elements();
        this.g2 = g2.elements();
    }

    public boolean hasMoreElements() {
        return (u1.hasMoreElements() ||
                u2.hasMoreElements() ||
                g1.hasMoreElements() || g2.hasMoreElements());
    }

    public AclEntry nextElement() {
        synchronized (acl) {
            if (u1.hasMoreElements())
                return u1.nextElement();
            if (u2.hasMoreElements())
                return u2.nextElement();
            if (g1.hasMoreElements())
                return g1.nextElement();
            if (g2.hasMoreElements())
                return g2.nextElement();
        }
        throw new NoSuchElementException("Acl Enumerator");
    }
}