History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/com/sun/jmx/remote/security/MBeanServerFileAccessController.java (1 of 18352 files). Show all

Unified · Split

java/pkg/com/sun/jmx/remote/security/MBeanServerFileAccessController.java+544−0
@@ -0,0 +1,544 @@
1/*
2 * Copyright (c) 2003, 2008, 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 com.sun.jmx.remote.security;
27
28import java.io.FileInputStream;
29import java.io.IOException;
30import java.security.AccessControlContext;
31import java.security.AccessController;
32import java.security.Principal;
33import java.security.PrivilegedAction;
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.List;
38import java.util.Map;
39import java.util.Properties;
40import java.util.Set;
41import java.util.StringTokenizer;
42import java.util.regex.Pattern;
43import javax.management.MBeanServer;
44import javax.management.ObjectName;
45import javax.security.auth.Subject;
46
47/**
48 * <p>An object of this class implements the MBeanServerAccessController
49 * interface and, for each of its methods, calls an appropriate checking
50 * method and then forwards the request to a wrapped MBeanServer object.
51 * The checking method may throw a SecurityException if the operation is
52 * not allowed; in this case the request is not forwarded to the
53 * wrapped object.</p>
54 *
55 * <p>This class implements the {@link #checkRead()}, {@link #checkWrite()},
56 * {@link #checkCreate(String)}, and {@link #checkUnregister(ObjectName)}
57 * methods based on an access level properties file containing username/access
58 * level pairs. The set of username/access level pairs is passed either as a
59 * filename which denotes a properties file on disk, or directly as an instance
60 * of the {@link Properties} class. In both cases, the name of each property
61 * represents a username, and the value of the property is the associated access
62 * level. Thus, any given username either does not exist in the properties or
63 * has exactly one access level. The same access level can be shared by several
64 * usernames.</p>
65 *
66 * <p>The supported access level values are {@code readonly} and
67 * {@code readwrite}. The {@code readwrite} access level can be
68 * qualified by one or more <i>clauses</i>, where each clause looks
69 * like <code>create <i>classNamePattern</i></code> or {@code
70 * unregister}. For example:</p>
71 *
72 * <pre>
73 * monitorRole readonly
74 * controlRole readwrite \
75 * create javax.management.timer.*,javax.management.monitor.* \
76 * unregister
77 * </pre>
78 *
79 * <p>(The continuation lines with {@code \} come from the parser for
80 * Properties files.)</p>
81 */
82public class MBeanServerFileAccessController
83 extends MBeanServerAccessController {
84
85 static final String READONLY = "readonly";
86 static final String READWRITE = "readwrite";
87
88 static final String CREATE = "create";
89 static final String UNREGISTER = "unregister";
90
91 private enum AccessType {READ, WRITE, CREATE, UNREGISTER};
92
93 private static class Access {
94 final boolean write;
95 final String[] createPatterns;
96 private boolean unregister;
97
98 Access(boolean write, boolean unregister, List<String> createPatternList) {
99 this.write = write;
100 int npats = (createPatternList == null) ? 0 : createPatternList.size();
101 if (npats == 0)
102 this.createPatterns = NO_STRINGS;
103 else
104 this.createPatterns = createPatternList.toArray(new String[npats]);
105 this.unregister = unregister;
106 }
107
108 private final String[] NO_STRINGS = new String[0];
109 }
110
111 /**
112 * <p>Create a new MBeanServerAccessController that forwards all the
113 * MBeanServer requests to the MBeanServer set by invoking the {@link
114 * #setMBeanServer} method after doing access checks based on read and
115 * write permissions.</p>
116 *
117 * <p>This instance is initialized from the specified properties file.</p>
118 *
119 * @param accessFileName name of the file which denotes a properties
120 * file on disk containing the username/access level entries.
121 *
122 * @exception IOException if the file does not exist, is a
123 * directory rather than a regular file, or for some other
124 * reason cannot be opened for reading.
125 *
126 * @exception IllegalArgumentException if any of the supplied access
127 * level values differs from "readonly" or "readwrite".
128 */
129 public MBeanServerFileAccessController(String accessFileName)
130 throws IOException {
131 super();
132 this.accessFileName = accessFileName;
133 Properties props = propertiesFromFile(accessFileName);
134 parseProperties(props);
135 }
136
137 /**
138 * <p>Create a new MBeanServerAccessController that forwards all the
139 * MBeanServer requests to <code>mbs</code> after doing access checks
140 * based on read and write permissions.</p>
141 *
142 * <p>This instance is initialized from the specified properties file.</p>
143 *
144 * @param accessFileName name of the file which denotes a properties
145 * file on disk containing the username/access level entries.
146 *
147 * @param mbs the MBeanServer object to which requests will be forwarded.
148 *
149 * @exception IOException if the file does not exist, is a
150 * directory rather than a regular file, or for some other
151 * reason cannot be opened for reading.
152 *
153 * @exception IllegalArgumentException if any of the supplied access
154 * level values differs from "readonly" or "readwrite".
155 */
156 public MBeanServerFileAccessController(String accessFileName,
157 MBeanServer mbs)
158 throws IOException {
159 this(accessFileName);
160 setMBeanServer(mbs);
161 }
162
163 /**
164 * <p>Create a new MBeanServerAccessController that forwards all the
165 * MBeanServer requests to the MBeanServer set by invoking the {@link
166 * #setMBeanServer} method after doing access checks based on read and
167 * write permissions.</p>
168 *
169 * <p>This instance is initialized from the specified properties
170 * instance. This constructor makes a copy of the properties
171 * instance and it is the copy that is consulted to check the
172 * username and access level of an incoming connection. The
173 * original properties object can be modified without affecting
174 * the copy. If the {@link #refresh} method is then called, the
175 * <code>MBeanServerFileAccessController</code> will make a new
176 * copy of the properties object at that time.</p>
177 *
178 * @param accessFileProps properties list containing the username/access
179 * level entries.
180 *
181 * @exception IllegalArgumentException if <code>accessFileProps</code> is
182 * <code>null</code> or if any of the supplied access level values differs
183 * from "readonly" or "readwrite".
184 */
185 public MBeanServerFileAccessController(Properties accessFileProps)
186 throws IOException {
187 super();
188 if (accessFileProps == null)
189 throw new IllegalArgumentException("Null properties");
190 originalProps = accessFileProps;
191 parseProperties(accessFileProps);
192 }
193
194 /**
195 * <p>Create a new MBeanServerAccessController that forwards all the
196 * MBeanServer requests to the MBeanServer set by invoking the {@link
197 * #setMBeanServer} method after doing access checks based on read and
198 * write permissions.</p>
199 *
200 * <p>This instance is initialized from the specified properties
201 * instance. This constructor makes a copy of the properties
202 * instance and it is the copy that is consulted to check the
203 * username and access level of an incoming connection. The
204 * original properties object can be modified without affecting
205 * the copy. If the {@link #refresh} method is then called, the
206 * <code>MBeanServerFileAccessController</code> will make a new
207 * copy of the properties object at that time.</p>
208 *
209 * @param accessFileProps properties list containing the username/access
210 * level entries.
211 *
212 * @param mbs the MBeanServer object to which requests will be forwarded.
213 *
214 * @exception IllegalArgumentException if <code>accessFileProps</code> is
215 * <code>null</code> or if any of the supplied access level values differs
216 * from "readonly" or "readwrite".
217 */
218 public MBeanServerFileAccessController(Properties accessFileProps,
219 MBeanServer mbs)
220 throws IOException {
221 this(accessFileProps);
222 setMBeanServer(mbs);
223 }
224
225 /**
226 * Check if the caller can do read operations. This method does
227 * nothing if so, otherwise throws SecurityException.
228 */
229 @Override
230 public void checkRead() {
231 checkAccess(AccessType.READ, null);
232 }
233
234 /**
235 * Check if the caller can do write operations. This method does
236 * nothing if so, otherwise throws SecurityException.
237 */
238 @Override
239 public void checkWrite() {
240 checkAccess(AccessType.WRITE, null);
241 }
242
243 /**
244 * Check if the caller can create MBeans or instances of the given class.
245 * This method does nothing if so, otherwise throws SecurityException.
246 */
247 @Override
248 public void checkCreate(String className) {
249 checkAccess(AccessType.CREATE, className);
250 }
251
252 /**
253 * Check if the caller can do unregister operations. This method does
254 * nothing if so, otherwise throws SecurityException.
255 */
256 @Override
257 public void checkUnregister(ObjectName name) {
258 checkAccess(AccessType.UNREGISTER, null);
259 }
260
261 /**
262 * <p>Refresh the set of username/access level entries.</p>
263 *
264 * <p>If this instance was created using the
265 * {@link #MBeanServerFileAccessController(String)} or
266 * {@link #MBeanServerFileAccessController(String,MBeanServer)}
267 * constructors to specify a file from which the entries are read,
268 * the file is re-read.</p>
269 *
270 * <p>If this instance was created using the
271 * {@link #MBeanServerFileAccessController(Properties)} or
272 * {@link #MBeanServerFileAccessController(Properties,MBeanServer)}
273 * constructors then a new copy of the <code>Properties</code> object
274 * is made.</p>
275 *
276 * @exception IOException if the file does not exist, is a
277 * directory rather than a regular file, or for some other
278 * reason cannot be opened for reading.
279 *
280 * @exception IllegalArgumentException if any of the supplied access
281 * level values differs from "readonly" or "readwrite".
282 */
283 public synchronized void refresh() throws IOException {
284 Properties props;
285 if (accessFileName == null)
286 props = (Properties) originalProps;
287 else
288 props = propertiesFromFile(accessFileName);
289 parseProperties(props);
290 }
291
292 private static Properties propertiesFromFile(String fname)
293 throws IOException {
294 FileInputStream fin = new FileInputStream(fname);
295 try {
296 Properties p = new Properties();
297 p.load(fin);
298 return p;
299 } finally {
300 fin.close();
301 }
302 }
303
304 private synchronized void checkAccess(AccessType requiredAccess, String arg) {
305 final AccessControlContext acc = AccessController.getContext();
306 final Subject s =
307 AccessController.doPrivileged(new PrivilegedAction<Subject>() {
308 public Subject run() {
309 return Subject.getSubject(acc);
310 }
311 });
312 if (s == null) return; /* security has not been enabled */
313 final Set principals = s.getPrincipals();
314 String newPropertyValue = null;
315 for (Iterator i = principals.iterator(); i.hasNext(); ) {
316 final Principal p = (Principal) i.next();
317 Access access = accessMap.get(p.getName());
318 if (access != null) {
319 boolean ok;
320 switch (requiredAccess) {
321 case READ:
322 ok = true; // all access entries imply read
323 break;
324 case WRITE:
325 ok = access.write;
326 break;
327 case UNREGISTER:
328 ok = access.unregister;
329 if (!ok && access.write)
330 newPropertyValue = "unregister";
331 break;
332 case CREATE:
333 ok = checkCreateAccess(access, arg);
334 if (!ok && access.write)
335 newPropertyValue = "create " + arg;
336 break;
337 default:
338 throw new AssertionError();
339 }
340 if (ok)
341 return;
342 }
343 }
344 SecurityException se = new SecurityException("Access denied! Invalid " +
345 "access level for requested MBeanServer operation.");
346 // Add some more information to help people with deployments that
347 // worked before we required explicit create clauses. We're not giving
348 // any information to the bad guys, other than that the access control
349 // is based on a file, which they could have worked out from the stack
350 // trace anyway.
351 if (newPropertyValue != null) {
352 SecurityException se2 = new SecurityException("Access property " +
353 "for this identity should be similar to: " + READWRITE +
354 " " + newPropertyValue);
355 se.initCause(se2);
356 }
357 throw se;
358 }
359
360 private static boolean checkCreateAccess(Access access, String className) {
361 for (String classNamePattern : access.createPatterns) {
362 if (classNameMatch(classNamePattern, className))
363 return true;
364 }
365 return false;
366 }
367
368 private static boolean classNameMatch(String pattern, String className) {
369 // We studiously avoided regexes when parsing the properties file,
370 // because that is done whenever the VM is started with the
371 // appropriate -Dcom.sun.management options, even if nobody ever
372 // creates an MBean. We don't want to incur the overhead of loading
373 // all the regex code whenever those options are specified, but if we
374 // get as far as here then the VM is already running and somebody is
375 // doing the very unusual operation of remotely creating an MBean.
376 // Because that operation is so unusual, we don't try to optimize
377 // by hand-matching or by caching compiled Pattern objects.
378 StringBuilder sb = new StringBuilder();
379 StringTokenizer stok = new StringTokenizer(pattern, "*", true);
380 while (stok.hasMoreTokens()) {
381 String tok = stok.nextToken();
382 if (tok.equals("*"))
383 sb.append("[^.]*");
384 else
385 sb.append(Pattern.quote(tok));
386 }
387 return className.matches(sb.toString());
388 }
389
390 private void parseProperties(Properties props) {
391 this.accessMap = new HashMap<String, Access>();
392 for (Map.Entry<Object, Object> entry : props.entrySet()) {
393 String identity = (String) entry.getKey();
394 String accessString = (String) entry.getValue();
395 Access access = Parser.parseAccess(identity, accessString);
396 accessMap.put(identity, access);
397 }
398 }
399
400 private static class Parser {
401 private final static int EOS = -1; // pseudo-codepoint "end of string"
402 static {
403 assert !Character.isWhitespace(EOS);
404 }
405
406 private final String identity; // just for better error messages
407 private final String s; // the string we're parsing
408 private final int len; // s.length()
409 private int i;
410 private int c;
411 // At any point, either c is s.codePointAt(i), or i == len and
412 // c is EOS. We use int rather than char because it is conceivable
413 // (if unlikely) that a classname in a create clause might contain
414 // "supplementary characters", the ones that don't fit in the original
415 // 16 bits for Unicode.
416
417 private Parser(String identity, String s) {
418 this.identity = identity;
419 this.s = s;
420 this.len = s.length();
421 this.i = 0;
422 if (i < len)
423 this.c = s.codePointAt(i);
424 else
425 this.c = EOS;
426 }
427
428 static Access parseAccess(String identity, String s) {
429 return new Parser(identity, s).parseAccess();
430 }
431
432 private Access parseAccess() {
433 skipSpace();
434 String type = parseWord();
435 Access access;
436 if (type.equals(READONLY))
437 access = new Access(false, false, null);
438 else if (type.equals(READWRITE))
439 access = parseReadWrite();
440 else {
441 throw syntax("Expected " + READONLY + " or " + READWRITE +
442 ": " + type);
443 }
444 if (c != EOS)
445 throw syntax("Extra text at end of line");
446 return access;
447 }
448
449 private Access parseReadWrite() {
450 List<String> createClasses = new ArrayList<String>();
451 boolean unregister = false;
452 while (true) {
453 skipSpace();
454 if (c == EOS)
455 break;
456 String type = parseWord();
457 if (type.equals(UNREGISTER))
458 unregister = true;
459 else if (type.equals(CREATE))
460 parseCreate(createClasses);
461 else
462 throw syntax("Unrecognized keyword " + type);
463 }
464 return new Access(true, unregister, createClasses);
465 }
466
467 private void parseCreate(List<String> createClasses) {
468 while (true) {
469 skipSpace();
470 createClasses.add(parseClassName());
471 skipSpace();
472 if (c == ',')
473 next();
474 else
475 break;
476 }
477 }
478
479 private String parseClassName() {
480 // We don't check that classname components begin with suitable
481 // characters (so we accept 1.2.3 for example). This means that
482 // there are only two states, which we can call dotOK and !dotOK
483 // according as a dot (.) is legal or not. Initially we're in
484 // !dotOK since a classname can't start with a dot; after a dot
485 // we're in !dotOK again; and after any other characters we're in
486 // dotOK. The classname is only accepted if we end in dotOK,
487 // so we reject an empty name or a name that ends with a dot.
488 final int start = i;
489 boolean dotOK = false;
490 while (true) {
491 if (c == '.') {
492 if (!dotOK)
493 throw syntax("Bad . in class name");
494 dotOK = false;
495 } else if (c == '*' || Character.isJavaIdentifierPart(c))
496 dotOK = true;
497 else
498 break;
499 next();
500 }
501 String className = s.substring(start, i);
502 if (!dotOK)
503 throw syntax("Bad class name " + className);
504 return className;
505 }
506
507 // Advance c and i to the next character, unless already at EOS.
508 private void next() {
509 if (c != EOS) {
510 i += Character.charCount(c);
511 if (i < len)
512 c = s.codePointAt(i);
513 else
514 c = EOS;
515 }
516 }
517
518 private void skipSpace() {
519 while (Character.isWhitespace(c))
520 next();
521 }
522
523 private String parseWord() {
524 skipSpace();
525 if (c == EOS)
526 throw syntax("Expected word at end of line");
527 final int start = i;
528 while (c != EOS && !Character.isWhitespace(c))
529 next();
530 String word = s.substring(start, i);
531 skipSpace();
532 return word;
533 }
534
535 private IllegalArgumentException syntax(String msg) {
536 return new IllegalArgumentException(
537 msg + " [" + identity + " " + s + "]");
538 }
539 }
540
541 private Map<String, Access> accessMap;
542 private Properties originalProps;
543 private String accessFileName;
544}