blob: fb3eb4e1f541ba2fcd7e3f3cd408785406167227 [file] [log] [blame]
Felix Meschberger007c50e2011-10-20 12:39:38 +00001/*
2 * Copyright (c) OSGi Alliance (2004, 2011). All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.osgi.service.cm;
18
19import java.io.IOException;
20import java.io.ObjectInputStream;
21import java.io.ObjectOutputStream;
22import java.io.ObjectStreamField;
23import java.security.BasicPermission;
24import java.security.Permission;
25import java.security.PermissionCollection;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.Enumeration;
30import java.util.HashMap;
31import java.util.Iterator;
32import java.util.List;
33import java.util.Map;
34
35/**
36 * Indicates a bundle's authority to configure bundles or be updated by
37 * Configuration Admin.
38 *
39 * @ThreadSafe
40 * @version $Id: 0d700c494f2dc2bbe05165bf5c79fe185c9f0a4a $
41 * @since 1.2
42 */
43
44public final class ConfigurationPermission extends BasicPermission {
45 static final long serialVersionUID = 5716868734811965383L;
46 /**
47 * Provides permission to create new configurations for other bundles as
48 * well as manipulate them. The action string {@value #CONFIGURE}.
49 */
50 public final static String CONFIGURE = "configure";
51
52 /**
53 * The permission to be updated, that is, act as a Managed Service or
54 * Managed Service Factory. The action string {@value #TARGET}.
55 *
56 * @since 1.4
57 */
58 public final static String TARGET = "target";
59
60 private final static int ACTION_CONFIGURE = 0x00000001;
61 private final static int ACTION_TARGET = 0x00000002;
62 private final static int ACTION_ALL = ACTION_CONFIGURE
63 | ACTION_TARGET;
64 final static int ACTION_NONE = 0;
65
66 /**
67 * The actions mask.
68 */
69 transient int action_mask;
70
71 /**
72 * The actions in canonical form.
73 *
74 * @serial
75 */
76 private volatile String actions = null;
77
78 /**
79 * Parsed name if it includes wildcards: "*"
80 */
81 private transient List substrings;
82
83 /**
84 * Create a new ConfigurationPermission.
85 *
86 * @param name Name of the permission. Wildcards ({@code '*'}) are allowed
87 * in the name. During {@link #implies(Permission)}, the name is
88 * matched to the requested permission using the substring matching
89 * rules used by {@link Filter}s.
90 * @param actions Comma separated list of {@link #CONFIGURE},
91 * {@link #TARGET}.
92 */
93
94 public ConfigurationPermission(String name, String actions) {
95 this(name, parseActions(actions));
96 }
97
98 /**
99 * Package private constructor used by ConfigurationPermissionCollection.
100 *
101 * @param name location string
102 * @param mask action mask
103 */
104 ConfigurationPermission(String name, int mask) {
105 super(name);
106 setTransients(mask);
107 }
108
109 /**
110 * Called by constructors and when deserialized.
111 *
112 * @param mask action mask
113 */
114 private void setTransients(int mask) {
115 if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
116 throw new IllegalArgumentException("invalid action string");
117 }
118 action_mask = mask;
119 substrings = parseSubstring(getName());
120 }
121
122 /**
123 * Parse action string into action mask.
124 *
125 * @param actions Action string.
126 * @return action mask.
127 */
128 private static int parseActions(String actions) {
129 boolean seencomma = false;
130
131 int mask = ACTION_NONE;
132
133 if (actions == null) {
134 return mask;
135 }
136
137 char[] a = actions.toCharArray();
138
139 int i = a.length - 1;
140 if (i < 0)
141 return mask;
142
143 while (i != -1) {
144 char c;
145
146 // skip whitespace
147 while ((i != -1)
148 && ((c = a[i]) == ' ' || c == '\r' || c == '\n'
149 || c == '\f' || c == '\t'))
150 i--;
151
152 // check for the known strings
153 int matchlen;
154
155 if (i >= 5 && (a[i - 5] == 't' || a[i - 5] == 'T')
156 && (a[i - 4] == 'a' || a[i - 4] == 'A')
157 && (a[i - 3] == 'r' || a[i - 3] == 'R')
158 && (a[i - 2] == 'g' || a[i - 2] == 'G')
159 && (a[i - 1] == 'e' || a[i - 1] == 'E')
160 && (a[i] == 't' || a[i] == 'T')) {
161 matchlen = 6;
162 mask |= ACTION_TARGET;
163
164 }
165 else
166 if (i >= 8 && (a[i - 8] == 'c' || a[i - 8] == 'C')
167 && (a[i - 7] == 'o' || a[i - 7] == 'O')
168 && (a[i - 6] == 'n' || a[i - 6] == 'N')
169 && (a[i - 5] == 'f' || a[i - 5] == 'F')
170 && (a[i - 4] == 'i' || a[i - 4] == 'I')
171 && (a[i - 3] == 'g' || a[i - 3] == 'G')
172 && (a[i - 2] == 'u' || a[i - 2] == 'U')
173 && (a[i - 1] == 'r' || a[i - 1] == 'R')
174 && (a[i] == 'e' || a[i] == 'E')) {
175 matchlen = 9;
176 mask |= ACTION_CONFIGURE;
177
178 }
179 else {
180 // parse error
181 throw new IllegalArgumentException("invalid actions: "
182 + actions);
183 }
184
185 // make sure we didn't just match the tail of a word
186 // like "ackbarftarget". Also, skip to the comma.
187 seencomma = false;
188 while (i >= matchlen && !seencomma) {
189 switch (a[i - matchlen]) {
190 case ',' :
191 seencomma = true;
192 /* FALLTHROUGH */
193 case ' ' :
194 case '\r' :
195 case '\n' :
196 case '\f' :
197 case '\t' :
198 break;
199 default :
200 throw new IllegalArgumentException(
201 "invalid permission: " + actions);
202 }
203 i--;
204 }
205
206 // point i at the location of the comma minus one (or -1).
207 i -= matchlen;
208 }
209
210 if (seencomma) {
211 throw new IllegalArgumentException("invalid actions: " + actions);
212 }
213
214 return mask;
215 }
216
217 /**
218 * Parse the name for wildcard processing.
219 *
220 * @param name The name of the permission.
221 * @return {@code null} is the name has no wildcards or a
222 * {@code List<String>} where element is a substring to match or
223 * null for {@code '*'}.
224 */
225 private static List parseSubstring(String name) {
226 if (name.indexOf('*') < 0) {
227 return null;
228 }
229 char[] chars = name.toCharArray();
230 StringBuffer sb = new StringBuffer(chars.length);
231
232 List sub = new ArrayList(10);
233
234 for (int pos = 0; pos < chars.length; pos++) {
235 char c = chars[pos];
236
237 switch (c) {
238 case '*' : {
239 if (sb.length() > 0) {
240 sub.add(sb.toString());
241 }
242 sb.setLength(0);
243 sub.add(null);
244 break;
245 }
246
247 case '\\' : {
248 pos++;
249 if (pos < chars.length) {
250 c = chars[pos];
251 }
252 /* fall through into default */
253 }
254
255 default : {
256 sb.append(c);
257 break;
258 }
259 }
260 }
261 if (sb.length() > 0) {
262 sub.add(sb.toString());
263 }
264
265 int size = sub.size();
266
267 if (size == 0) {
268 return null;
269 }
270
271 if (size == 1) {
272 if (sub.get(0) != null) {
273 return null;
274 }
275 }
276 return sub;
277 }
278
279 /**
280 * Determines if a {@code ConfigurationPermission} object "implies" the
281 * specified permission.
282 *
283 * @param p The target permission to check.
284 * @return {@code true} if the specified permission is implied by this
285 * object; {@code false} otherwise.
286 */
287
288 public boolean implies(Permission p) {
289 if (!(p instanceof ConfigurationPermission)) {
290 return false;
291 }
292 ConfigurationPermission requested = (ConfigurationPermission) p;
293 return implies0(requested, ACTION_NONE);
294 }
295
296 /**
297 * Internal implies method. Used by the implies and the permission
298 * collection implies methods.
299 *
300 * @param requested The requested ConfigurationPermission which has already
301 * be validated as a proper argument.
302 * @param effective The effective actions with which to start.
303 * @return {@code true} if the specified permission is implied by this
304 * object; {@code false} otherwise.
305 */
306 boolean implies0(ConfigurationPermission requested, int effective) {
307 /* check actions first - much faster */
308 effective |= action_mask;
309 final int desired = requested.action_mask;
310 if ((effective & desired) != desired) {
311 return false;
312 }
313 String requestedName = requested.getName();
314 if (substrings == null) {
315 return getName().equals(requestedName);
316 }
317 for (int i = 0, pos = 0, size = substrings.size(); i < size; i++) {
318 String substr = (String) substrings.get(i);
319
320 if (i + 1 < size) /* if this is not that last substr */{
321 if (substr == null) /* * */{
322 String substr2 = (String) substrings.get(i + 1);
323
324 if (substr2 == null) /* ** */
325 continue; /* ignore first star */
326 /* xxx */
327 int index = requestedName.indexOf(substr2, pos);
328 if (index == -1) {
329 return false;
330 }
331
332 pos = index + substr2.length();
333 if (i + 2 < size) // if there are more
334 // substrings, increment
335 // over the string we just
336 // matched; otherwise need
337 // to do the last substr
338 // check
339 i++;
340 }
341 else /* xxx */{
342 int len = substr.length();
343 if (requestedName.regionMatches(pos, substr, 0, len)) {
344 pos += len;
345 }
346 else {
347 return false;
348 }
349 }
350 }
351 else /* last substr */{
352 if (substr == null) /* * */{
353 return true;
354 }
355 /* xxx */
356 return requestedName.endsWith(substr);
357 }
358 }
359
360 return false;
361 }
362
363 /**
364 * Determines the equality of two {@code ConfigurationPermission} objects.
365 * <p>
366 * Two {@code ConfigurationPermission} objects are equal.
367 *
368 * @param obj The object being compared for equality with this object.
369 * @return {@code true} if {@code obj} is equivalent to this
370 * {@code ConfigurationPermission}; {@code false} otherwise.
371 */
372 public boolean equals(Object obj) {
373 if (obj == this) {
374 return true;
375 }
376
377 if (!(obj instanceof ConfigurationPermission)) {
378 return false;
379 }
380
381 ConfigurationPermission cp = (ConfigurationPermission) obj;
382
383 return (action_mask == cp.action_mask)
384 && getName().equals(cp.getName());
385 }
386
387 /**
388 * Returns the hash code value for this object.
389 *
390 * @return Hash code value for this object.
391 */
392
393 public int hashCode() {
394 int h = 31 * 17 + getName().hashCode();
395 h = 31 * h + getActions().hashCode();
396 return h;
397 }
398
399 /**
400 * Returns the canonical string representation of the
401 * {@code ConfigurationPermission} actions.
402 *
403 * <p>
404 * Always returns present {@code ConfigurationPermission} actions in the
405 * following order: {@value #CONFIGURE}, {@value #TARGET}
406 *
407 * @return Canonical string representation of the
408 * {@code ConfigurationPermission} actions.
409 */
410 public String getActions() {
411 String result = actions;
412 if (result == null) {
413 StringBuffer sb = new StringBuffer();
414 boolean comma = false;
415
416 int mask = action_mask;
417 if ((mask & ACTION_CONFIGURE) == ACTION_CONFIGURE) {
418 sb.append(CONFIGURE);
419 comma = true;
420 }
421
422 if ((mask & ACTION_TARGET) == ACTION_TARGET) {
423 if (comma)
424 sb.append(',');
425 sb.append(TARGET);
426 }
427
428 actions = result = sb.toString();
429 }
430
431 return result;
432 }
433
434 /**
435 * Returns a new {@code PermissionCollection} object suitable for storing
436 * {@code ConfigurationPermission}s.
437 *
438 * @return A new {@code PermissionCollection} object.
439 */
440 public PermissionCollection newPermissionCollection() {
441 return new ConfigurationPermissionCollection();
442 }
443
444 /**
445 * WriteObject is called to save the state of this permission object to a
446 * stream. The actions are serialized, and the superclass takes care of the
447 * name.
448 */
449 private synchronized void writeObject(java.io.ObjectOutputStream s)
450 throws IOException {
451 // Write out the actions. The superclass takes care of the name
452 // call getActions to make sure actions field is initialized
453 if (actions == null)
454 getActions();
455 s.defaultWriteObject();
456 }
457
458 /**
459 * readObject is called to restore the state of this permission from a
460 * stream.
461 */
462 private synchronized void readObject(java.io.ObjectInputStream s)
463 throws IOException, ClassNotFoundException {
464 // Read in the data, then initialize the transients
465 s.defaultReadObject();
466 setTransients(parseActions(actions));
467 }
468}
469
470/**
471 * Stores a set of {@code ConfigurationPermission} permissions.
472 *
473 * @see java.security.Permission
474 * @see java.security.Permissions
475 * @see java.security.PermissionCollection
476 */
477final class ConfigurationPermissionCollection extends PermissionCollection {
478 static final long serialVersionUID = -6917638867081695839L;
479 /**
480 * Collection of permissions.
481 *
482 * @serial
483 * @GuardedBy this
484 */
485 private Map permissions;
486
487 /**
488 * Boolean saying if "*" is in the collection.
489 *
490 * @serial
491 * @GuardedBy this
492 */
493 private boolean all_allowed;
494
495 /**
496 * Creates an empty {@code ConfigurationPermissionCollection} object.
497 *
498 */
499 public ConfigurationPermissionCollection() {
500 permissions = new HashMap();
501 all_allowed = false;
502 }
503
504 /**
505 * Adds the specified permission to the
506 * {@code ConfigurationPermissionCollection}. The key for the hash is the
507 * interface name of the service.
508 *
509 * @param permission The {@code Permission} object to add.
510 *
511 * @exception IllegalArgumentException If the permission is not an
512 * {@code ConfigurationPermission}.
513 *
514 * @exception SecurityException If this ConfigurationPermissionCollection
515 * object has been marked read-only.
516 */
517
518 public void add(Permission permission) {
519 if (!(permission instanceof ConfigurationPermission)) {
520 throw new IllegalArgumentException("invalid permission: "
521 + permission);
522 }
523
524 if (isReadOnly())
525 throw new SecurityException("attempt to add a Permission to a "
526 + "readonly PermissionCollection");
527
528 final ConfigurationPermission cp = (ConfigurationPermission) permission;
529 final String name = cp.getName();
530 synchronized (this) {
531 Map pc = permissions;
532 final ConfigurationPermission existing = (ConfigurationPermission) pc.get(name);
533 if (existing != null) {
534 final int oldMask = existing.action_mask;
535 final int newMask = cp.action_mask;
536 if (oldMask != newMask) {
537 pc.put(name, new ConfigurationPermission(name, oldMask
538 | newMask));
539 }
540 }
541 else {
542 pc.put(name, cp);
543 }
544
545 if (!all_allowed) {
546 if (name.equals("*")) {
547 all_allowed = true;
548 }
549 }
550 }
551 }
552
553 /**
554 * Determines if the specified permissions implies the permissions expressed
555 * in {@code permission}.
556 *
557 * @param permission The Permission object to compare with this
558 * {@code ConfigurationPermission} object.
559 * @return {@code true} if {@code permission} is a proper subset of a
560 * permission in the set; {@code false} otherwise.
561 */
562 public boolean implies(Permission permission) {
563 if (!(permission instanceof ConfigurationPermission)) {
564 return false;
565 }
566 final ConfigurationPermission requested = (ConfigurationPermission) permission;
567 int effective = ConfigurationPermission.ACTION_NONE;
568
569 Collection perms;
570 synchronized (this) {
571 Map pc = permissions;
572 /* short circuit if the "*" Permission was added */
573 if (all_allowed) {
574 ConfigurationPermission cp = (ConfigurationPermission) pc.get("*");
575 if (cp != null) {
576 effective |= cp.action_mask;
577 final int desired = requested.action_mask;
578 if ((effective & desired) == desired) {
579 return true;
580 }
581 }
582 }
583 perms = pc.values();
584 }
585 /* iterate one by one over permissions */
586 for (Iterator permI = perms.iterator(); permI.hasNext(); ) {
587 ConfigurationPermission perm = (ConfigurationPermission) permI.next();
588 if (perm.implies0(requested, effective)) {
589 return true;
590 }
591 }
592 return false;
593 }
594
595 /**
596 * Returns an enumeration of all {@code ConfigurationPermission} objects in
597 * the container.
598 *
599 * @return Enumeration of all {@code ConfigurationPermission} objects.
600 */
601 public synchronized Enumeration elements() {
602 List all = new ArrayList(permissions.values());
603 return Collections.enumeration(all);
604 }
605
606 /* serialization logic */
607 private static final ObjectStreamField[] serialPersistentFields = {
608 new ObjectStreamField("hasElement", Boolean.TYPE),
609 new ObjectStreamField("permissions", HashMap.class),
610 new ObjectStreamField("all_allowed", Boolean.TYPE) };
611
612 private synchronized void writeObject(ObjectOutputStream out)
613 throws IOException {
614 ObjectOutputStream.PutField pfields = out.putFields();
615 pfields.put("hasElement", false);
616 pfields.put("permissions", permissions);
617 pfields.put("all_allowed", all_allowed);
618 out.writeFields();
619 }
620
621 private synchronized void readObject(java.io.ObjectInputStream in)
622 throws IOException, ClassNotFoundException {
623 ObjectInputStream.GetField gfields = in.readFields();
624 boolean hasElement = gfields.get("hasElement", false);
625 if (hasElement) { // old format
626 permissions = new HashMap();
627 permissions.put("*", new ConfigurationPermission("*",
628 ConfigurationPermission.CONFIGURE));
629 all_allowed = true;
630 }
631 else {
632 permissions = (HashMap) gfields
633 .get("permissions",
634 new HashMap());
635 all_allowed = gfields.get("all_allowed", false);
636 }
637 }
638}