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+
26+package com.sun.jmx.remote.security;
27+
28+import java.io.FileInputStream;
29+import java.io.IOException;
30+import java.security.AccessControlContext;
31+import java.security.AccessController;
32+import java.security.Principal;
33+import java.security.PrivilegedAction;
34+import java.util.ArrayList;
35+import java.util.HashMap;
36+import java.util.Iterator;
37+import java.util.List;
38+import java.util.Map;
39+import java.util.Properties;
40+import java.util.Set;
41+import java.util.StringTokenizer;
42+import java.util.regex.Pattern;
43+import javax.management.MBeanServer;
44+import javax.management.ObjectName;
45+import 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+ */
82+public 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+}